ewo*_*wok 8 validation groovy json jsonslurper
我需要在Groovy中检查字符串是否是有效的JSON.我的第一个想法是发送它new JsonSlurper().parseText(myString)
,如果没有例外,则认为它是正确的.
但是,我发现Groovy很乐意接受尾随逗号JsonSlurper
,但JSON 不允许使用尾随逗号.有没有一种简单的方法来验证Groovy中的JSON是否符合官方JSON规范?
JsonSlurper
类使用JsonParser
接口实现(JsonParserCharArray
作为默认实现)。这些解析器逐个字符检查当前字符是什么以及它代表什么类型的标记类型。如果您查看JsonParserCharArray.decodeJsonObject()
第 139 行的方法,您会发现如果解析器看到}
字符,它会中断循环并完成 JSON 对象的解码并忽略}
.
这就是为什么如果您在 JSON 对象前面放置任何无法识别的字符,JsonSlurper
将引发异常。但是,如果您在 JSON 字符串之后以任何不正确的字符结尾}
,它将通过,因为解析器甚至不考虑这些字符。
JsonOutput.prettyPrint(String json)
如果涉及到它尝试打印的 JSON(它用于JsonLexer
以流方式读取 JSON 令牌),您可以考虑使用更严格的方法。如果你这样做:
def jsonString = '{"name": "John", "data": [{"id": 1},{"id": 2}]}...'
JsonOutput.prettyPrint(jsonString)
Run Code Online (Sandbox Code Playgroud)
它会抛出一个异常,如:
Exception in thread "main" groovy.json.JsonException: Lexing failed on line: 1, column: 48, while reading '.', no possible valid JSON value or punctuation could be recognized.
at groovy.json.JsonLexer.nextToken(JsonLexer.java:83)
at groovy.json.JsonLexer.hasNext(JsonLexer.java:233)
at groovy.json.JsonOutput.prettyPrint(JsonOutput.java:501)
at groovy.json.JsonOutput$prettyPrint.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at app.JsonTest.main(JsonTest.groovy:13)
Run Code Online (Sandbox Code Playgroud)
但是如果我们传递一个有效的 JSON 文档,例如:
def jsonString = '{"name": "John", "data": [{"id": 1},{"id": 2}]}'
JsonOutput.prettyPrint(jsonString)
Run Code Online (Sandbox Code Playgroud)
它会成功通过。
好处是你不需要任何额外的依赖来验证你的 JSON。
我做了更多调查并使用 3 种不同的解决方案运行测试:
JsonOutput.prettyJson(String json)
JsonSlurper.parseText(String json)
ObjectMapper.readValue(String json, Class<> type)
(它需要添加jackson-databind:2.9.3
依赖项)我使用以下 JSON 作为输入:
def json1 = '{"name": "John", "data": [{"id": 1},{"id": 2},]}'
def json2 = '{"name": "John", "data": [{"id": 1},{"id": 2}],}'
def json3 = '{"name": "John", "data": [{"id": 1},{"id": 2}]},'
def json4 = '{"name": "John", "data": [{"id": 1},{"id": 2}]}... abc'
def json5 = '{"name": "John", "data": [{"id": 1},{"id": 2}]}'
Run Code Online (Sandbox Code Playgroud)
预期结果是前 4 个 JSON 未通过验证,只有第 5 个是正确的。为了测试它,我创建了这个 Groovy 脚本:
@Grab(group='com.fasterxml.jackson.core', module='jackson-databind', version='2.9.3')
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.DeserializationFeature
def json1 = '{"name": "John", "data": [{"id": 1},{"id": 2},]}'
def json2 = '{"name": "John", "data": [{"id": 1},{"id": 2}],}'
def json3 = '{"name": "John", "data": [{"id": 1},{"id": 2}]},'
def json4 = '{"name": "John", "data": [{"id": 1},{"id": 2}]}... abc'
def json5 = '{"name": "John", "data": [{"id": 1},{"id": 2}]}'
def test1 = { String json ->
try {
JsonOutput.prettyPrint(json)
return "VALID"
} catch (ignored) {
return "INVALID"
}
}
def test2 = { String json ->
try {
new JsonSlurper().parseText(json)
return "VALID"
} catch (ignored) {
return "INVALID"
}
}
ObjectMapper mapper = new ObjectMapper()
mapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true)
def test3 = { String json ->
try {
mapper.readValue(json, Map)
return "VALID"
} catch (ignored) {
return "INVALID"
}
}
def jsons = [json1, json2, json3, json4, json5]
def tests = ['JsonOutput': test1, 'JsonSlurper': test2, 'ObjectMapper': test3]
def result = tests.collectEntries { name, test ->
[(name): jsons.collect { json ->
[json: json, status: test(json)]
}]
}
result.each {
println "${it.key}:"
it.value.each {
println " ${it.status}: ${it.json}"
}
println ""
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
JsonOutput:
VALID: {"name": "John", "data": [{"id": 1},{"id": 2},]}
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}],}
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]},
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}... abc
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}
JsonSlurper:
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2},]}
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}],}
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]},
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}... abc
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}
ObjectMapper:
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2},]}
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2}],}
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2}]},
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}... abc
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}
Run Code Online (Sandbox Code Playgroud)
如您所见,获胜者是杰克逊的ObjectMapper.readValue()
方法。重要的是 - 它适用于jackson-databind
>= 2.9.0
。在这个版本中,他们引入DeserializationFeature.FAIL_ON_TRAILING_TOKENS
了使 JSON 解析器按预期工作的方法。如果我们true
不按照上面的脚本设置这个配置特性,ObjectMapper 会产生错误的结果:
ObjectMapper:
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2},]}
INVALID: {"name": "John", "data": [{"id": 1},{"id": 2}],}
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]},
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}... abc
VALID: {"name": "John", "data": [{"id": 1},{"id": 2}]}
Run Code Online (Sandbox Code Playgroud)
我很惊讶 Groovy 的标准库在这个测试中失败了。幸运的是,它可以通过jackson-databind:2.9.x
依赖来完成。希望能帮助到你。
似乎是 groovy json 解析器中的一个错误或功能
尝试另一个 json 解析器
我使用 Snakeyaml 因为它支持 json 和 yaml,但是您可以通过互联网找到其他基于 java 的 json 解析器库
@Grab(group='org.yaml', module='snakeyaml', version='1.19')
def jsonString = '''{"a":1,"b":2}...'''
//no error in the next line
def json1 = new groovy.json.JsonSlurper().parseText( jsonString )
//the following line fails
def json2 = new org.yaml.snakeyaml.Yaml().load( jsonString )
Run Code Online (Sandbox Code Playgroud)