[英]Groovy: validate JSON string
我需要在Groovy中檢查字符串是否是有效的JSON。 我的第一個想法是通過new JsonSlurper().parseText(myString)
發送它,如果沒有異常,則認為它是正確的。
但是,我發現Groovy很樂意接受JsonSlurper
尾隨逗號,但JSON 不允許使用尾隨逗號 。 有沒有一種簡單的方法來驗證Groovy中的JSON是否符合官方JSON規范?
JsonSlurper
類使用JsonParser
接口實現( JsonParserCharArray
是默認接口)。 這些解析器通過char檢查char是什么是當前字符以及它代表什么類型的令牌類型。 如果你看看第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個JSON正確。 為了測試它,我創建了這個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
依賴。 希望能幫助到你。
似乎是groovy json解析器中的bug或功能
嘗試另一個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()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.