Groovy:验证JSON字符串

Groovy:验证JSON字符串,json,validation,groovy,jsonslurper,Json,Validation,Groovy,Jsonslurper,我需要检查一个字符串在Groovy中是否是有效的JSON。我的第一个想法是通过newjsonslurper().parseText(myString)发送它,如果没有异常,就假设它是正确的 然而,我发现Groovy很乐意接受带有JsonSlurper的尾随逗号,但不接受JSON。是否有一种简单的方法可以在Groovy中验证符合官方JSON规范的JSON?似乎是Groovy JSON解析器中的一个bug或特性 尝试另一个json解析器 我使用snakeyaml是因为它支持json和yaml,但是您

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


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

似乎是Groovy JSON解析器中的一个bug或特性

尝试另一个json解析器

我使用snakeyaml是因为它支持json和yaml,但是您可以在internet上找到其他基于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 )
类使用接口实现(默认为
jsonparsercharray
)。这些解析器逐字符检查当前字符是什么以及它代表什么类型的令牌类型。如果查看第139行的方法,您将看到,如果解析器看到
}
字符,它将中断循环并完成对JSON对象的解码,并忽略
}
之后存在的任何内容

这就是为什么如果在JSON对象前面放置任何无法识别的字符,
JsonSlurper
将抛出异常。但如果您在
}
之后用任何不正确的字符结束JSON字符串,它将通过,因为解析器甚至不考虑这些字符

解决方案

您可以考虑使用<代码> jSouth.PrimTyPress(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(字符串json)
  • JsonSlurper.parseText(字符串json)
  • ObjectMapper.readValue(字符串json,类类型)
    (需要添加
    jackson数据绑定: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数据绑定
=
2.9.0
一起工作。在这个版本中,他们引入了
反序列化特性。在跟踪标记上失败
,这使得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来完成。希望有帮助。

可以这样验证:

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()
String.metaClass.isJson
def normalize={it.replaceAll(“\\s”,”)}
试一试{
normalize(delegate)=normalize(JsonOutput.toJson(新的JsonSlurper().parseText(delegate)))
}捕获(e){
假的
}
}
断言“{”键“:“值”}”.isJson()
断言。isJson()

也许有用你帮了我大忙。谢谢。@Szymon感谢您的调查,我很惊讶Groovy的json解析器在很多情况下都会失败,直到我找到原因,我相信Groovy的动态特性和快速原型设计风格在解析错误格式的模式(json,xml…)时需要一些容差,我发现这在解析不同类型的HTML混合JS时非常有用,例如,在处理REST api时。依靠异常来判断某个内容是否无效,这总感觉像是一种反模式。很好!如果它是
JsonBuilder
中的方法
isValid()
,会是什么?