How to find the difference/mismatch between two JSON file?
Asked Answered
N

4

7

I have two json files, one is expected json and the another one is the result of GET API call. I need to compare and find out the mismatch in the file.

Expected Json:

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d",
    "e": "f"
  },
  "string": "Hello World"
}

Actual Json response:

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "null": null,
  "number": 456,
  "object": {
    "a": "b",
    "c": "d",
    "e": "f"
  },
  "string": "India"
}

Actually there are two mismatch: number received is 456 and string is India.

Is there a way to compare and get these two mismatch as results.

This need to be implemented in gatling/scala.

Natale answered 6/12, 2017 at 11:8 Comment(0)
R
1

You can use, for example, play-json library and recursively traverse both JSONs. For next input (a bit more sophisticated than yours input):

LEFT:

{
  "array" : [ 1, 2, 4 ],
  "boolean" : true,
  "null" : null,
  "number" : 123,
  "object" : {
    "a" : "b",
    "c" : "d",
    "e" : "f"
  },
  "string" : "Hello World",
  "absent-in-right" : true,
  "different-types" : 123
}

RIGHT:

{
  "array" : [ 1, 2, 3 ],
  "boolean" : true,
  "null" : null,
  "number" : 456,
  "object" : {
    "a" : "b",
    "c" : "d",
    "e" : "ff"
  },
  "string" : "India",
  "absent-in-left" : true,
  "different-types" : "YES"
}

It produces this output:

Next fields are absent in LEFT: 
     *\absent-in-left
Next fields are absent in RIGHT: 
     *\absent-in-right
'*\array\(2)' => 4 != 3
'*\number' => 123 != 456
'*\object\e' => f != ff
'*\string' => Hello World != India
Cannot compare JsNumber and JsString in '*\different-types'

Code:

val left = Json.parse("""{"array":[1,2,4],"boolean":true,"null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Hello World","absent-in-right":true,"different-types":123}""").asInstanceOf[JsObject]
val right = Json.parse("""{"array":[1,2,3],"boolean":true,"null":null,"number":456,"object":{"a":"b","c":"d","e":"ff"},"string":"India","absent-in-left":true,"different-types":"YES"}""").asInstanceOf[JsObject]

// '*' - for the root node
showJsDiff(left, right, "*", Seq.empty[String])

def showJsDiff(left: JsValue, right: JsValue, parent: String, path: Seq[String]): Unit = {
  val newPath = path :+ parent
  if (left.getClass != right.getClass) {
    println(s"Cannot compare ${left.getClass.getSimpleName} and ${right.getClass.getSimpleName} " +
      s"in '${getPath(newPath)}'")
  }
  else {
    left match {
      // Primitive types are pretty easy to handle
      case JsNull => logIfNotEqual(JsNull, right.asInstanceOf[JsNull.type], newPath)
      case JsBoolean(value) => logIfNotEqual(value, right.asInstanceOf[JsBoolean].value, newPath)
      case JsNumber(value) => logIfNotEqual(value, right.asInstanceOf[JsNumber].value, newPath)
      case JsString(value) => logIfNotEqual(value, right.asInstanceOf[JsString].value, newPath)
      case JsArray(value) =>
        // For array we have to call showJsDiff on each element of array
        val arr1 = value
        val arr2 = right.asInstanceOf[JsArray].value
        if (arr1.length != arr2.length) {
          println(s"Arrays in '${getPath(newPath)}' have different length. ${arr1.length} != ${arr2.length}")
        }
        else {
          arr1.indices.foreach { idx =>
            showJsDiff(arr1(idx), arr2(idx), s"($idx)", newPath)
          }
        }
      case JsObject(value) =>
        val leftFields = value.keys.toSeq
        val rightJsObject = right.asInstanceOf[JsObject]
        val rightFields = rightJsObject.fields.map { case (name, value) => name }

        val absentInLeft = rightFields.diff(leftFields)
        if (absentInLeft.nonEmpty) {
          println("Next fields are absent in LEFT: ")
          absentInLeft.foreach { fieldName =>
            println(s"\t ${getPath(newPath :+ fieldName)}")
          }
        }
        val absentInRight = leftFields.diff(rightFields)
        if (absentInRight.nonEmpty) {
          println("Next fields are absent in RIGHT: ")
          absentInRight.foreach { fieldName =>
            println(s"\t ${getPath(newPath :+ fieldName)}")
          }
        }
        // For common fields we have to call showJsDiff on them
        val commonFields = leftFields.intersect(rightFields)
        commonFields.foreach { field =>
          showJsDiff(value(field), rightJsObject(field), field, newPath)
        }

    }
  }
}


def logIfNotEqual[T](left: T, right: T, path: Seq[String]): Unit = {
  if (left != right) {
    println(s"'${getPath(path)}' => $left != $right")
  }
}

def getPath(path: Seq[String]): String = path.mkString("\\")
Remora answered 6/12, 2017 at 20:22 Comment(0)
G
1

Use diffson - a Scala implementation of RFC-6901 and RFC-6902: https://github.com/gnieh/diffson

Galimatias answered 18/1, 2018 at 20:51 Comment(0)
G
0

json4s has a handy diff function described here: https://github.com/json4s/json4s (search for Merging & Diffing) and API doc here: https://static.javadoc.io/org.json4s/json4s-core_2.9.1/3.0.0/org/json4s/Diff.html

Gosselin answered 6/12, 2017 at 20:57 Comment(0)
C
0

This is a slightly modified version of Artavazd's answer (which is amazing btw thank you so much!). This version outputs the differences into a convenient object instead of only logging them.

import play.api.Logger
import play.api.libs.json.{JsArray, JsBoolean, JsError, JsNull, JsNumber, JsObject, JsString, JsSuccess, JsValue, Json, OFormat, Reads}

case class JsDifferences(
  differences: List[JsDifference] = List()
)

object JsDifferences {
  implicit val format: OFormat[JsDifferences] = Json.format[JsDifferences]
}

case class JsDifference(
  key: String,
  path: Seq[String],
  oldValue: Option[String],
  newValue: Option[String]
)

object JsDifference {
  implicit val format: OFormat[JsDifference] = Json.format[JsDifference]
}

object JsonUtils {
  val logger: Logger = Logger(this.getClass)

  def findDiff(left: JsValue, right: JsValue, parent: String = "*", path: List[String] = List()): JsDifferences = {
    val newPath = path :+ parent
    if (left.getClass != right.getClass) {
      logger.debug(s"Cannot compare ${left.getClass.getSimpleName} and ${right.getClass.getSimpleName} in '${getPath(newPath)}'")
      JsDifferences()
    } else left match {
      case JsNull => logIfNotEqual(JsNull, right.asInstanceOf[JsNull.type], newPath)
      case JsBoolean(value) => logIfNotEqual(value, right.asInstanceOf[JsBoolean].value, newPath)
      case JsNumber(value) => logIfNotEqual(value, right.asInstanceOf[JsNumber].value, newPath)
      case JsString(value) => logIfNotEqual(value, right.asInstanceOf[JsString].value, newPath)
      case JsArray(value) =>
        val arr1 = value
        val arr2 = right.asInstanceOf[JsArray].value
        if (arr1.length != arr2.length) {
          logger.debug(s"Arrays in '${getPath(newPath)}' have different length. ${arr1.length} != ${arr2.length}")
          JsDifferences()
        } else JsDifferences(arr1.indices.flatMap(idx => findDiff(arr1(idx), arr2(idx), s"($idx)", newPath).differences).toList)
      case leftJsObject: JsObject => {
        val leftFields = leftJsObject.keys.toSeq
        val rightJsObject = right.asInstanceOf[JsObject]
        val rightFields = rightJsObject.fields.map { case (name, value) => name }

        val keysAbsentInLeft = rightFields.diff(leftFields)
        val leftDifferences = keysAbsentInLeft.map(fieldName => JsDifference(
          key = fieldName, path = newPath :+ fieldName, oldValue = None, newValue = Some(rightJsObject(fieldName).toString)
        ))

        val keysAbsentInRight = leftFields.diff(rightFields)
        val rightDifferences = keysAbsentInRight.map(fieldName => JsDifference(
          key = fieldName, path = newPath :+ fieldName, oldValue = Some(leftJsObject(fieldName).toString), newValue = None
        ))

        val commonKeys = leftFields.intersect(rightFields)
        val commonDifferences = commonKeys.flatMap(field => findDiff(leftJsObject(field), rightJsObject(field), field, newPath).differences).toList

        JsDifferences((leftDifferences ++ rightDifferences ++ commonDifferences).toList)
      }
    }
  }

  def logIfNotEqual[T](left: T, right: T, path: Seq[String]): JsDifferences = {
    if (left != right) {
      JsDifferences(List(JsDifference(
        key = path.last, path = path, oldValue = Some(left.toString), newValue = Some(right.toString)
      )))
    } else JsDifferences()
  }

  def getPath(path: Seq[String]): String = path.mkString("\\")
}
Chobot answered 1/7, 2022 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.