基于jackson的kotlin json字符串对比器
package comparator import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeType import com.fasterxml.jackson.databind.node.NumericNode import java.util.* import kotlin.Comparator enum class CompareType { /* * both compare name and value, but if name missing found, compare stop, and print the result, or it will compare all values are the same until find difference. */ COMPARE_NAME, /* * if name missing found, compare stop and print the result. */ COMPARE_NAME_VALUE } internal class NumberComparator : Comparator<JsonNode> { override fun compare(o1: JsonNode, o2: JsonNode): Int { if (o1 == o2) { return 0 } if (o1 is NumericNode && o2 is NumericNode) { val d1 = o1.asDouble() val d2 = o2.asDouble() if (d1.compareTo(d2) == 0 ) { return 0 } } return 1 } } class Comparator { constructor(){ debug= false ; TYPE_COMPARE = CompareType.COMPARE_NAME_VALUE IGNORED_FILED_LIST = java.util.ArrayList<String>(); } constructor(debugI: Boolean?, compareType:String?, ignoreFieldList: String?){ if (debugI != null ) {debug=debugI;} var type = CompareType.COMPARE_NAME_VALUE if (compareType != null && compareType.trim { it <= ' ' } !== "" ) { if (CompareType.COMPARE_NAME_VALUE.toString().equals(compareType, ignoreCase = true )) { type = CompareType.COMPARE_NAME_VALUE } else if (CompareType.COMPARE_NAME.toString().equals(compareType, ignoreCase = true )) { type = CompareType.COMPARE_NAME } } TYPE_COMPARE = type var ignoredFields: ArrayList<String> = java.util.ArrayList<String>(); if (ignoreFieldList == null || ignoreFieldList.trim { it <= ' ' }.length == 0 ) { if (debug){ println( "no valid ignored field list" ) } } else { var fs:kotlin.collections.List<String> = ignoreFieldList.split( "," ); for (f:String in fs){ var fTrimed:String = f.trim(); if (fTrimed.length > 0 ){ ignoredFields.add(fTrimed) } } } IGNORED_FILED_LIST = ignoredFields; if (debug){ println( "selected compare type:" + TYPE_COMPARE) } if (debug){ println( "selected ignored field list:" + IGNORED_FILED_LIST) } } fun compare(path: String, o1: JsonNode?, o2: JsonNode?, res: ArrayList<Diff?>) { // basic input check if (o1 == null ) { if (o2 != null ) { res.add(Diff(pathGetter(path, "" ), MISS_NAME_TIP_WORD, "" )) } return } if (o2 == null ) { res.add(Diff(pathGetter(path, "" ), "" , MISS_NAME_TIP_WORD)) return } val typeO1 = o1.nodeType val typeO2 = o2.nodeType if (typeO1 != typeO2) { val diff = Diff(pathGetter(path, "" ), typeO1.name, typeO2.name) addAndfilterDiffRes(res, diff) return } val type = o1.nodeType if (type == JsonNodeType.ARRAY) { val nodeArrLeft: MutableList<JsonNode> = ArrayList() val iteratorLeft = o1.elements() while (iteratorLeft.hasNext()) { nodeArrLeft.add(iteratorLeft.next()) } val nodeArrRight: MutableList<JsonNode> = ArrayList() val iteratorRight = o2.elements() while (iteratorRight.hasNext()) { nodeArrRight.add(iteratorRight.next()) } if (nodeArrLeft.size == nodeArrRight.size) { for (i in 0 until o1.size()) { compare(pathGetter(path, o1.toString()+ "[" +i+ "]" ), nodeArrLeft[i], nodeArrRight[i], res) } } else if (nodeArrLeft.size > nodeArrRight.size) { val lefti = nodeArrLeft[nodeArrRight.size] val diff = Diff(pathGetter(path, "[" +nodeArrRight.size+ "]" ), lefti.toString(), MISS_NAME_TIP_WORD) addAndfilterDiffRes(res, diff) } else { val righti = nodeArrRight[nodeArrLeft.size] val diff = Diff(pathGetter(path, "[" +nodeArrLeft.size+ "]" ), MISS_NAME_TIP_WORD, righti.toString()) addAndfilterDiffRes(res, diff) } } else if (type == JsonNodeType.OBJECT) { val o1NameSet = getNamesSet(o1.fieldNames()) val o2NameSet = getNamesSet(o2.fieldNames()) val sameList: MutableList<String> = ArrayList() // compare node name size // check key is the same for (key in o1NameSet) { if (!o2NameSet.contains(key)) { val diff = Diff(pathGetter(path, key), o1[key].toString(), MISS_NAME_TIP_WORD) addAndfilterDiffRes(res, diff) } else { sameList.add(key) } } for (key in o2NameSet) { if (!o1NameSet.contains(key)) { val diff = Diff(pathGetter(path, key), MISS_NAME_TIP_WORD, o2[key].toString()) addAndfilterDiffRes(res, diff) } } // compare node value for (key in sameList) { val nodeleft = o1[key] val noderight = o2[key] val typeObjLeft = nodeleft.nodeType val typeObjRight = noderight.nodeType if (typeObjLeft != typeObjRight) { val diff = Diff(pathGetter(path, key), typeObjLeft.name, typeObjRight.name) addAndfilterDiffRes(res, diff) } else { if (typeObjLeft == JsonNodeType.ARRAY) { val nodeArrLeft: MutableList<JsonNode> = ArrayList() val iteratorLeft = nodeleft.elements() while (iteratorLeft.hasNext()) { nodeArrLeft.add(iteratorLeft.next()) } val nodeArrRight: MutableList<JsonNode> = ArrayList() val iteratorRight = noderight.elements() while (iteratorRight.hasNext()) { nodeArrRight.add(iteratorRight.next()) } if (nodeArrLeft.size == nodeArrRight.size) { for (i in nodeArrLeft.indices) { compare(pathGetter(path, key+ "[" +i+ "]" ), nodeArrLeft[i], nodeArrRight[i], res) } } else if (nodeArrLeft.size > nodeArrRight.size) { val diff = Diff( pathGetter(path, key+ "[" +nodeArrRight.size+ "]" ), nodeArrLeft[nodeArrRight.size].toString(), MISS_NAME_TIP_WORD ) addAndfilterDiffRes(res, diff) } else { val diff = Diff( pathGetter(path, key + "[" +nodeArrLeft.size+ "]" ), MISS_NAME_TIP_WORD, nodeArrRight[nodeArrLeft.size].toString() ) addAndfilterDiffRes(res, diff) } } else if (typeObjLeft == JsonNodeType.OBJECT) { compare(pathGetter(path, key), nodeleft, noderight, res) } else { if (TYPE_COMPARE == CompareType.COMPARE_NAME) { // just compare names continue } if (!nodeleft.equals(NumberComparator(), noderight)) { val diff = Diff(pathGetter(path, key), nodeleft.toString(), noderight.toString()) addAndfilterDiffRes(res, diff) } } } } } else { if (TYPE_COMPARE == CompareType.COMPARE_NAME) { // just compare names return } if (!o1.equals(NumberComparator(), o2)) { val diff = Diff(pathGetter(path, "" ), o1.toString(), o2.toString()) addAndfilterDiffRes(res, diff) } } return } fun getNamesSet(it: Iterator<String>): TreeSet<String> { val res = TreeSet<String>() while (it.hasNext()) { val name = it.next() res.add(name) } return res } fun pathGetter(parent: String, key: String?): String { val path = StringBuilder() if (parent == "" ) { path.append(key) } else if (key == null || "" == key.trim { it <= ' ' }) { path.append(parent) } else { path.append(parent).append( "." ).append(key) } return path.toString() } fun addAndfilterDiffRes(res: MutableList<Diff?>, diff: Diff) { if (!IGNORED_FILED_LIST.contains(diff.node)) { res.add(diff) } } /** * * @param compareType <br></br> * support two compare type set at agrs[0]: COMPARE_NAME_VALUE, COMPARE_NAME <br></br> * * * COMPARE_NAME_VALUE:both compare name and value, but if name missing found, compare stop, and print the result<br></br> * COMPARE_NAME:if name missing found, compare stop and print the result * */ fun getCompareResult(left: String?, right: String?): ArrayList<Diff?> { var leftNode: JsonNode? = null try { leftNode = mapper.readTree(left) } catch (e: Exception) { if (debug){ println( "left parse failed:" + e.message) } } var rightNode: JsonNode? = null try { rightNode = mapper.readTree(right) } catch (e: Exception) { if (debug){ println( "right parse failed:" + e.message) } } return getCompareResultWithJson(leftNode, rightNode) } fun getCompareResultWithJson( left: JsonNode?, right: JsonNode? ): ArrayList<Diff?> { val res: ArrayList<Diff?> = ArrayList<Diff?>() try { compare( "\$root" , left, right, res) } catch (e: Exception) { if (debug){ println( "compare failed:" + e.message) } } return res } companion object { // fields to ignore when compare lateinit var IGNORED_FILED_LIST:ArrayList<String>; var TYPE_COMPARE = CompareType.COMPARE_NAME_VALUE var MISS_NAME_TIP_WORD = "not exist" public val mapper = ObjectMapper() public var debug:Boolean = false ; init { mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true ) mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ) } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理