我需要在 Groovy 中检查一个字符串是否是有效的 JSON。我的第一个想法就是发送它new JsonSlurper().parseText(myString)
,如果没有例外,假设它是正确的。
但是,我发现 Groovy 很乐意接受带有 . 的尾随逗号JsonSlurper
,但 JSON不允许尾随逗号. 是否有一种简单的方法可以在符合官方 JSON 规范的 Groovy 中验证 JSON?
我需要在 Groovy 中检查一个字符串是否是有效的 JSON。我的第一个想法就是发送它new JsonSlurper().parseText(myString)
,如果没有例外,假设它是正确的。
但是,我发现 Groovy 很乐意接受带有 . 的尾随逗号JsonSlurper
,但 JSON不允许尾随逗号. 是否有一种简单的方法可以在符合官方 JSON 规范的 Groovy 中验证 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)
它会抛出一个异常,如:
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)
但是如果我们传递一个有效的 JSON 文档,例如:
def jsonString = '{"name": "John", "data": [{"id": 1},{"id": 2}]}'
JsonOutput.prettyPrint(jsonString)
它会成功通过。
好消息是您不需要任何额外的依赖项来验证您的 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}]}'
预期结果是前 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 ""
}
结果如下:
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}]}
如您所见,获胜者是杰克逊的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}]}
我很惊讶 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 )
可以这样验证:
assert JsonOutput.toJson(new JsonSlurper().parseText(myString)).replaceAll("\\s", "") ==
myString.replaceAll("\\s", "")
或者更干净一点:
String.metaClass.isJson << { ->
def normalize = { it.replaceAll("\\s", "") }
try {
normalize(delegate) == normalize(JsonOutput.toJson(new JsonSlurper().parseText(delegate)))
} catch (e) {
false
}
}
assert '{"key":"value"}'.isJson()
assert !''.isJson()