score:0

Accepted answer

So I solved the problem by writing a custom RootJsonFormat::

  implicit object ItemJsonFormat extends RootJsonFormat[Item] {
    override def read(json: JsValue): Item = json.asJsObject.getFields("_id", "itemId", "name", "active") match {
      case Seq(JsString(_id), JsNumber(itemId), JsString(name), JsBoolean(active)) => Item(_id = _id, itemId = itemId.toInt, name = name, active = active)
      case Seq(JsNumber(itemId), JsString(name), JsBoolean(active)) => Item(itemId = itemId.toInt, name = name, active = active)
      case _ => throw new DeserializationException("Item expected")
    }
    override def write(obj: Item): JsValue = JsObject(
      "_id" -> JsString(obj._id),
      "itemId" -> JsNumber(obj.itemId),
      "name" -> JsString(obj.name),
      "active" -> JsBoolean(obj.active)
    )
  }

Basically what it does is it checks if we received the _id in json, if we did then we're using it to construct the object, and in other case keep the auto-generated id field.

One other thing which might cause some trouble but in my opinion deserves mentioning somewhere - if anyone has a problem with nested objects ("non-primitive" types) - I advise using .toJson in write def (like obj.time.toJson, where obj.time is jodatime's DateTime) and JsValue's .convertTo[T] def in read, like time = JsString(time).convertTo[DateTime]. In order for this to work there have to be defined implicit json formats for those "non-primitive" objects.

Best, Marcin

score:0

I would use this solution:

case class Item(_id: Option[String], itemId: Int, var name: String, var active: Boolean)

implicit object ItemJsonFormat extends RootJsonFormat[Item] {
   override def read(value: JsValue) = {
     val _id = fromField[Option[String]](value, "_id")
     val itemId = fromField[Int](value, "itemId")
     val expires = fromField[Long](value, "expires")
     val name = fromField[String](value, "name")
     val active = fromField[Boolean](value, "active")
     Item(_id, itemId, name, active)
   }
   override def write(obj: Item): JsValue = JsObject(
     "_id" -> JsString(obj._id),
     "itemId" -> JsNumber(obj.itemId),
     "name" -> JsString(obj.name),
     "active" -> JsBoolean(obj.active)
   )
}

The advantage over the json.asJsObject.getFields solution is that you have better control over what gets accepted on the case of an undefined id. The example where that would fail is the following:

  • itemId is a string, same as id
  • id is defined but itemId is not

In this case the match case would interpret the specified id as a itemId and not catch the error.


Related Query

More Query from same tag