0

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?

Tim Joseph
  • 847
  • 2
  • 14
  • 28

2 Answers2

2

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
Eric
  • 15,494
  • 38
  • 61
  • Thank you very much. Looks promising. I get just 1 error when trying to compile: ```found : Iterable[org.specs2.matcher.Matcher[(String, play.api.libs.json.JsValue)]] required: Seq[org.specs2.matcher.ValueCheck[?]] Read from stdout: asPair(as must contain(allOf(es.map(pairEqualj):_*)).exactly.inOrder)``` – Tim Joseph Nov 22 '15 at 16:53
  • Sorry about that, I fixed the compilation error now. – Eric Nov 22 '15 at 22:17
0

You can convert the json into a case class and compare them as sets:

contentAsJson(res).as[Set[User]] must equalTo(Json.parse(responseBody).as[Set[User]])
Todor Kolev
  • 1,432
  • 1
  • 16
  • 33
  • What about optional fields? Let's assume that our JSON can have an optional "time" field, assume that we have two JSONs that are completely the same but one field. `json1` does not have a `time` field and `json2` has a `null` `time` value like `{..., "time": null, ...}`. In this case, the case classes will be the same but JSONs are not exactly the same. – Alperen Sep 09 '21 at 19:45
  • Having a `null` value in JSON may not make sense but this is possible :) – Alperen Sep 09 '21 at 20:22
  • True that the json documents will not be exactly the same but null is still a valid json document. If the parser is json compliant they're semantically the same. You have a point though. It was more of a quick and dirty way of doing it. – Todor Kolev Sep 14 '21 at 10:40