spray-json icon indicating copy to clipboard operation
spray-json copied to clipboard

How To Provide DefaultValues For Fields In JSON While Marshalling Or Unmarshalling?

Open shankarshastri opened this issue 6 years ago • 4 comments

I want to have default value for x2 which is optional in my request payload.

import spray.json._
import DefaultJsonProtocol._
case class X(x1: Int, x2: Option[Int])
object XApply {
  def apply(x1: Int, x2: Option[Int] = Some(10)): X = X(x1, x2)
}
object MyJsonProtocol extends DefaultJsonProtocol {
  implicit val XFormat = jsonFormat2(X)
}
import MyJsonProtocol._
"""{"x1":2 }""".parseJson.convertTo[X] // But looks like this will assign x2 to None

https://scastie.scala-lang.org/shankarshastri/kM9yeuDgSMqstJkqS91Lvw Any suggestions? @ktoso @jrudolph

shankarshastri avatar May 02 '18 17:05 shankarshastri

https://github.com/spray/spray-json/pull/56 did you checked this?

ramanmishra avatar May 08 '18 16:05 ramanmishra

hi how can use default parameters for case class spray Json ? for example case class Person(name: String,family:String, age: Int, mySelf: Option[String]=Some(generateID)) when i fill case class with json i have got mySelf=None

ehsanshah avatar Jul 03 '18 00:07 ehsanshah

@ehanshah, there's a PR, but we need to wait till it get merged. https://github.com/spray/spray-json/pull/93

shankarshastri avatar Jul 03 '18 01:07 shankarshastri

Here's a workaround for now:

import spray.json._

/** JsonFormat that offers backwards compatibility for newly-added fields
  * by splicing in a default value when the new field key is not present in the json.
  *
  * This is important because spray does not support reading case class default arguments in all case.
  * Without using this, reading old-format messages (for example from an external queue), will throw an exception.
  *
  * Currently, spray-json *will* use `= None` default values, so this class does not need to be used with `Option` fields.
  *
  * @see [[https://github.com/spray/spray-json/pull/93 Unmerged spray-json PR adding support for default arguments]]
  *
  * @example {{{
  *   case class Person(name: String, age: Int, newTagsField: Map[String, String] = Map.empty)
  *   object Person {
  *     implicit val sprayFormatForPerson: RootJsonFormat[Person] = {
  *       import JsonProtocol._
  *       MissingFieldFormat(jsonFormat3(Person.apply),
  *                          "newTagsField" -> Map.empty[String, String])
  *     }
  *   }
  * }}}
  */
class MissingFieldFormat[A](realFormat: RootJsonFormat[A],
                            defaults: Map[String, JsValue])
    extends RootJsonFormat[A] {

  override def read(json: JsValue): A = {
    val fixedJson = try {
      JsObject(defaults ++ json.asJsObject.fields)
    } catch {
      case _: DeserializationException =>
        // Was not a JsObject, return and let it fail later with better error
        json
    }
    realFormat.read(fixedJson)
  }

  override def write(obj: A): JsValue = realFormat.write(obj)

}

object MissingFieldFormat {

  def apply[A, B: JsonWriter](
      realFormat: RootJsonFormat[A],
      default: (String, B)
  ): MissingFieldFormat[A] =
    new MissingFieldFormat[A](realFormat, Map(default._1 -> default._2.toJson))

  def apply[A, B: JsonWriter, C: JsonWriter](
      realFormat: RootJsonFormat[A],
      defaultB: (String, B),
      defaultC: (String, C)
  ): MissingFieldFormat[A] =
    new MissingFieldFormat[A](realFormat,
                              Map(
                                defaultB._1 -> defaultB._2.toJson,
                                defaultC._1 -> defaultC._2.toJson
                              ))
}

Daenyth avatar Feb 06 '19 21:02 Daenyth