13

我需要在 Groovy 中检查一个字符串是否是有效的 JSON。我的第一个想法就是发送它new JsonSlurper().parseText(myString),如果没有例外,假设它是正确的。

但是,我发现 Groovy 很乐意接受带有 . 的尾随逗号JsonSlurper,但 JSON不允许尾随逗号. 是否有一种简单的方法可以在符合官方 JSON 规范的 Groovy 中验证 JSON?

4

3 回答 3

11

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依赖来完成。希望能帮助到你。

于 2018-01-26T22:14:22.437 回答
2

似乎是 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 )
于 2018-01-26T21:40:56.990 回答
1

可以这样验证:

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()
于 2019-03-24T12:41:03.737 回答