JsonSlurper 类使用JsonParser 接口实现(JsonParserCharArray 是默认实现)。这些解析器逐个字符地检查当前字符是什么以及它代表的令牌类型。如果您查看第 139 行的 JsonParserCharArray.decodeJsonObject() 方法,您将看到如果解析器看到 } 字符,它会中断循环并完成对 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}]}
如您所见,获胜者是 Jackson 的 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 依赖来完成。希望对您有所帮助。