Search code examples
scalaserializationoptional-parametersspray-json

Parsing a n Option[List[Object]] using SprayJson Scala


running into some Json quirks. I am writing serialize-deserialize logic for an incoming request which contains an object in form of JSON which is IndividualObject. I am using Spray Json for this. Tried using toVector, mapping list of items to resolve the following but not able to resolve the type mismatch.

case class IndividualObject(id: String, path: String)
object IndividualObjectJson extends DefaultJsonProtocol {
    implicit val individualObjectFormat = jsonFormat2(IndividualObject.apply)
}

case class IncomingRequest(anotherId: String, indivObjects: Option[List[IndividualObject]])
trait IncomingRequestJson extends SprayJsonSupport with DefaultJsonProtocol {
    implici val incomingRequestFormat: RootJsonFormat[IncomingRequest] = new RootJsonFormat[IncomingRequest] {
        override def read(json: JsValue): IncomingRequest = {
            val fields = json.asJsObject.fields
            IncomingRequest(
                anotherId = fields("anotherId").convertTo[String]
                indivObjects = fields("indivObjects").convertTo[Option[List[IndividualObject]]]
            )
         override def write(obj: IncomingRequest): JsValue = {
             val jsBuilder = Map.newBuilder[String,JsValue]
             jsBuilder += "anotherId" -> JsString(obj.anotherId)
             jsBuilder += "indivObjects" -> JsArray(obj.indivObjects.get map (IndividualObjectJson.individualObjectFormat))
             JsObject(jsBuilder.result())
         }

Found the following error in JsArray line: jsBuilder += "indivObjects" -> JsArray(obj.indivObjects.get map (IndividualObjectJson.individualObjectFormat))

Error: type mismatch
    found: spray.json.RootJsonFormat[IndividualObject]
    required: List[IndividualObject]
  

Any help is appreciated to understand why its not able to resolve Option[List[....]] for deserialization. Thanks


Solution

  • You can use the jsonFormat2 to create default json readers/writers for case classes with two parameters. The compiler will inject the code via implicit resolution for handling these. But this is not a JsValue(JsString, JsObject, ....). If you create your own Json encoder/encoder you must include JsValue instances manually to create the final JsValue. Something like that:

      override def write(obj: IncomingRequest): JsValue = {
        val jsBuilder = Map.newBuilder[String, JsValue]
        jsBuilder += "anotherId" -> JsString(obj.anotherId)
        jsBuilder += "indivObjects" -> JsArray({
        obj.indivObjects match {
            case Some(list) =>
              list
                .map(el =>
                  JsObject(
                    Map("id" -> JsString(el.id), "path" -> JsString(el.path))
                  )
                )
                .toVector
            case None => Vector.empty
          }
        })
        JsObject(jsBuilder.result())
      }
    

    Something like that should compile.