Groovy对比JSON响应差异(类JSON编辑器效果)及代码报错解决
报错原因分析
- 遍历参数不匹配:你提供的JSON字符串外层为数组结构,JsonSlurper解析后返回
List类型,而你使用了适用于Map的遍历写法each{k, v -> },List的each方法只会给闭包传递1个当前遍历的数组元素参数,和你定义的2个参数的闭包签名不匹配,因此触发参数异常。 - 预期值未解析:你仅将实际响应的字符串解析为JSON对象,预期值
removedE仍然是普通字符串,无法通过.key的方式读取属性。 - 多余的字符替换操作:全局替换所有下划线会修改原始JSON的键名(比如
__typename会被改为typename),如果业务中键名本身包含下划线会直接破坏JSON结构,无特殊要求不要做该操作。
可行解决方案
基础对比修复
如果仅需要校验两个JSON是否完全相等,可直接用以下代码实现:
import groovy.json.JsonSlurper def actual = " [{\"inputs\":[{\"__typename\":\"ElementalField\"}],\"constraint\":\"FILE-STATUS:REPORT-STATUS:REPORT-STATUS-1='0'\",\"source\":{\"sourceCodeFiles\":[{\"fileName\":\"/usr/src/workspace/PC/source/cobol/PCCTRE.cob\",\"sourceLines\":[{\"nodes\":[{\"id\":\"8326\"}],\"lineNumber\":620}]}],\"__typename\":\"Source\"},\"branchExecuted\":false},{\"inputs\":[{\"__typename\":\"ElementalField\"}],\"constraint\":\"FILE-STATUS:TRAND-STATUS:TRAND-STATUS-1='0'\",\"source\":{\"sourceCodeFiles\":[{\"fileName\":\"/usr/src/workspace/PC/source/cobol/PCCTRE.cob\",\"sourceLines\":[{\"nodes\":[{\"id\":\"8369\"}],\"lineNumber\":634}]}],\"__typename\":\"Source\"},\"branchExecuted\":false}]" def expected = "[{\"inputs\":[{\"__typename\":\"ElementalField\"}],\"constraint\":\"FILE-STATUS:REPORT-STATUS:REPORT-STATUS-1='0'\",\"source\":{\"sourceCodeFiles\":[{\"fileName\":\"/usr/src/workspace/PC/source/cobol/PCCTRE.cob\",\"sourceLines\":[{\"nodes\":[{\"id\":\"8326\"}],\"lineNumber\":620}]}],\"__typename\":\"Source\"},\"branchExecuted\":false},{\"inputs\":[{\"__typename\":\"ElementalField\"}],\"constraint\":\"FILE-STATUS:TRAND-STATUS:TRAND-STATUS-1='0'\",\"source\":{\"sourceCodeFiles\":[{\"fileName\":\"/usr/src/workspace/PC/source/cobol/PCCTRE.cob\",\"sourceLines\":[{\"nodes\":[{\"id\":\"8366\"}],\"lineNumber\":634}]}],\"__typename\":\"Source\"},\"branchExecuted\":false}]" def jsonSlurper = new JsonSlurper() def actualJson = jsonSlurper.parseText(actual.trim()) def expectedJson = jsonSlurper.parseText(expected.trim()) // 直接全量相等校验 assert actualJson == expectedJson : "两个JSON结构不相等"
类JSON编辑器差异识别实现
如果需要输出具体差异位置、差异值,实现可视化的差异对比效果,可使用以下递归对比代码:
// 入参:实际JSON对象、预期JSON对象、当前对比路径(递归用,调用时可不传) // 返回值:所有差异项列表 def diffJson(def actual, def expected, String path = 'root') { def diffs = [] // 类型差异 if (actual.class != expected.class) { diffs << [path: path, actual: actual, expected: expected, reason: "类型不一致"] return diffs } // 对比Map类型 if (actual instanceof Map) { (actual.keySet() - expected.keySet()).each { key -> diffs << [path: "${path}.${key}", actual: actual[key], expected: null, reason: "实际JSON存在额外键"] } (expected.keySet() - actual.keySet()).each { key -> diffs << [path: "${path}.${key}", actual: null, expected: expected[key], reason: "实际JSON缺少键"] } (actual.keySet() intersect expected.keySet()).each { key -> diffs.addAll(diffJson(actual[key], expected[key], "${path}.${key}")) } return diffs } // 对比List类型 if (actual instanceof List) { if (actual.size() != expected.size()) { diffs << [path: path, actual: "长度${actual.size()}", expected: "长度${expected.size()}", reason: "数组长度不一致"] return diffs } actual.eachWithIndex { item, index -> diffs.addAll(diffJson(item, expected[index], "${path}[${index}]")) } return diffs } // 对比基础类型值 if (actual != expected) { diffs << [path: path, actual: actual, expected: expected, reason: "值不一致"] } return diffs } // 调用并打印差异 def differences = diffJson(actualJson, expectedJson) if (differences) { println "识别到${differences.size()}处差异:" differences.each { diff -> println "- 差异路径:${diff.path}" println " 实际值:${diff.actual}" println " 期望值:${diff.expected}" println " 差异原因:${diff.reason}\n" } } else { println "两个JSON完全一致" }
运行上述代码会输出精准的差异定位:
识别到1处差异:
- 差异路径:root[1].source.sourceCodeFiles[0].sourceLines[0].nodes[0].id
实际值:8369
期望值:8366
差异原因:值不一致
内容的提问来源于stack exchange,提问作者Ekaterina
相关产品推荐
相关产品推荐

