I'm using Salat library for serializing case classes as mongoDb objects. My Item.scala file looks like this:
case class Item(_id: String = (new ObjectId).toString, itemId: Int, var name: String, var active: Boolean) extends WithId {
override def id: ObjectId = new ObjectId(_id)
}
object Item extends MongoDb[Item] with MongoDao[Item] {
override def collectionName: String = "items"
}
object ItemJsonProtocol extends DefaultJsonProtocol {
implicit val itemFormat = jsonFormat4(Item.apply)
}
Now, I'm using it to post the Item entities as Json via Spray HTTP. I'd want to invoke it as follows:
curl.exe -H "Content-Type: application/json" -X PUT -d "{\"itemId\":
1, \"active\":true, \"name\" : \"test\"}" http://localhost:8080/items/
hoping it would provide generated id if I don't provide one.
However, after invoking curl command I'm getting an error:
The request content was malformed: Object is missing required member '_id'
Is there any way to mark the _id field as optional without making the Option out of it (this field will always be set) and defining custom JsonFormat thus (de)serializing the object by myself?
I've read this post: https://stackoverflow.com/a/10820293/1042869, but I was wondering if there's any other way to do that as I got many cases of the _id fields. There was a comment too saying "you but you can give that field a default value in the case class definition, so if the field is not in the json, it will assign the default value to it.", but as you can see here it doesn't seem to work.
Best, Marcin
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