Search code examples
scalaplayframeworkdeserializationplay-json

Play JSON JsPath Conditional Types


I receive JSON response from an upstream server that I do not have a control on, for modification.

The JSON could have the following format

{"data":[["Some_text","boolean",["key_string1","key_string2"]]]}

Or it could also show up as

{"data":[["Some_text","boolean","key_string2"]]}

Or it could show up as a combination of the two.

{"data":[["Some_text","boolean",["key_string1","key_string2"]],["Some_text","boolean","key_string2"]]}

Individually I can define the READS for each format if they don't mix. However, given that the data may be of mixed format, I am unable to wrap my head around how the Reads should be written so as to check if the underlying type is a string or array before transforming it?

Is it possible to say something like

  (
    (JsPath)(0).read[String] and
    (JsPath)(1).read[Boolean] and
    (JsPath)(2).read( **if type is simple, String OR if type is array, then seq** )
  )(SomeGloriousCaseClass)

How can I approach this problem of deserializing?


Solution

  • For fun I implemented a solution. You might need to tweak it a bit :

    private def toSeq(jsValue: JsValue): JsResult[Seq[String]] = {
        jsValue match {
          case JsArray(es:Seq[JsValue]) ⇒ sequence(es.map(e ⇒ toSeq(jsValue))).map(_.flatten)
          case JsString(s) ⇒ JsSuccess(s :: Nil)
          case _ ⇒ JsError("INVALID")
        }
      }
    
      private def sequence[A](seq:Seq[JsResult[A]]):JsResult[Seq[A]] = {
        seq.foldLeft(JsResult.applicativeJsResult.pure(Seq[A]()))((acc, next) ⇒ for(acc2 ← acc; next2 ← next) yield acc2 :+ next2)
      }
    

    Hope it helps and would be glad to provide further explanations if needed.