Search code examples
playframeworkspecs2play-json

Play Json - Compare nested Json-Array with Specs2


With Play-Json and Specs2 I can match json-bodies like this:

contentAsJson(res) must equalTo(responseBody)

Is there a possiblity to ignore order (recursively) for json-arrays and instead treat equality for json-arrays like they were sets?


Solution

  • There is a bit of work involved, depending on how good you want the failure messages to be. You can do something like this

    import org.specs2._
    import execute._
    import matcher._
    import play.api.libs.json._
    
    trait JsValueMatchers extends MustMatchers {
      def beEqualj(expected: JsValue): Matcher[JsValue] = { actual: JsValue =>
        (actual, expected) match {
          case (JsArray(as), JsArray(es)) =>
            asPair(as must contain(allOf(es.map(beEqualj):_*)).exactly)
    
          case (JsObject(as), JsObject(es)) =>
            asPair(as must contain(allOf(es.toList.map(pairEqualj):_*)).exactly.inOrder)
    
          case (JsNull, JsNull) =>
            (true, "ko")
    
          case (JsBoolean(a), JsBoolean(e)) =>
            (a == e, s"ko: $a is not equal to $e")
    
          case (JsString(a), JsString(e)) =>
            (a == e, s"ko: $a is not equal to $e")
    
          case (JsNumber(a), JsNumber(e)) =>
            (a == e, s"ko: $a is not equal to $e")
    
          case _ =>
            (false, s"$actual and $expected don't have the same type")
        }
      }
    
      def pairEqualj(expected: (String, JsValue)): Matcher[(String, JsValue)] = { actual: (String, JsValue) =>
        val (key, value) = actual
        val result = (key must_== expected._1) and
          (value must beEqualj(expected._2))
        asPair(result)
      }
    
      def asPair[R : AsResult](r: R): (Boolean, String) = {
        val result = AsResult(r)
        (result.isSuccess, result.message)
      }
    }
    
    object JsValueMatchers