Search code examples
jsonscalajson-deserializationspray-json

Spray JSON: Flexibility Array/Object


Using Spray JSON, I would like to be able to parse an array of String, but still be able to deserialize correctly if a single String comes. That is, with this field:

arrayval: List[String]

and this JSON:

arrayval: ["a", "b"]

it would create a List("a","b"), and with this JSON:

arrayval: "a"

it would create a List("a"). Using default listFormat it would complain in the second case. Is there a way to configure this kind of flexibility?


Solution

  • In case it helps anyone, I've solved it by overriding the listFormat in the CollectionFormats (which is used in the DefaultJsonProtocol).

    trait FlexibleCollectionFormats extends CollectionFormats {
    
      implicit override def listFormat[T: JsonFormat] = new RootJsonFormat[List[T]] {
        import spray.json._
    
        def write(list: List[T]) = JsArray(list.map(_.toJson).toVector)
        def read(value: JsValue): List[T] = value match {
          case JsArray(elements) => elements.map(_.convertTo[T])(collection.breakOut)
          case JsString(element) => List[T](new JsString(element).convertTo[T]) 
          case x                 => deserializationError("Expected List as JsArray, but got " + x)
        }
      }
    }
    

    Then I created my own protocol instead of DefaultJsonProtocol, that basically uses the same ones as Default but overriding the CollectionFormats:

    trait FlexibleDefaultJsonProtocol
            extends BasicFormats
            with StandardFormats
            // with CollectionFormats
            with FlexibleCollectionFormats
            with ProductFormats
            with AdditionalFormats
    
    object FlexibleDefaultJsonProtocol extends FlexibleDefaultJsonProtocol
    

    and later you use extends FlexibleDefaultJsonProtocol instead of DefaultJsonProtocol. You can always switch and use one or the other in your classes, so I like the flex it provides.