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) } } }