[spray-json] Optional field in case class

1,270 views
Skip to first unread message

Marcin Szekalski

unread,
Jun 30, 2014, 1:46:15 PM6/30/14
to spray...@googlegroups.com
Hey,

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}" 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?

Best,
Marcin

Marcin Szekalski

unread,
Jun 30, 2014, 1:47:44 PM6/30/14
to spray...@googlegroups.com
Ooops... Sorry, I posted the wrong curl command. The correct is (I'm invoking it on Window's cmd.exe):

curl.exe -H "Content-Type: application/json" -X PUT -d "{\"itemId\": 1, \"active\":true, \"name\" : \"test\"}" http://localhost:8080/items/

Mathias Doenitz

unread,
Jul 1, 2014, 4:04:35 AM7/1/14
to spray...@googlegroups.com
Marcin,

if you write a custom JsonFormat this is no problem.
If you wanted to stick with the `jsonFormat` helper you’d have to move the `_id` member out of the constructor:

case class Item(itemId: Int, var name: String, var active: Boolean) extends WithId {
val _id: String = (new ObjectId).toString
}

Cheers,
Mathias

---
mat...@spray.io
http://spray.io
> --
> You received this message because you are subscribed to the Google Groups "spray.io User List" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
> Visit this group at http://groups.google.com/group/spray-user.
> To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/5d8a7605-c723-4430-abe8-ebfc8b92aeae%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Marcin Szekalski

unread,
Jul 1, 2014, 11:06:28 AM7/1/14
to spray...@googlegroups.com
Hey,

What I'd want to achieve is:
Accept both:
  • {"itemId": 1, "active":true}
  • {"_id": "123456712345671234567890", "itemId": 1, "active":true}
while reading, and ALWAYS writing _id when serializing to JSON.

Is there any other way than writing custom JsonFormat?

Sorry, for newbie's questions, I'm really new.

Best,
Marcin

Mathias Doenitz

unread,
Jul 2, 2014, 7:31:31 AM7/2/14
to spray...@googlegroups.com
Marcin,

what you are looking for required custom logic being run during deserialisation.
Therefore the standard `jsonFormat` helper will not be able to solve things for you here.
You’ll have to resort to writing a custom `JsonFormat` as described here:
https://github.com/spray/spray-json#providing-jsonformats-for-other-types

Cheers,
Mathias

---
mat...@spray.io
http://spray.io

> To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/d2f6bdae-fb8b-4f35-a94c-1e1ce0df4331%40googlegroups.com.

Marcin Szekalski

unread,
Jul 2, 2014, 3:15:17 PM7/2/14
to spray...@googlegroups.com
Hey,

I did write those formats and it works now. One of them looks like this:

  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)
    )
  }

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

Kunal Parikh

unread,
Oct 28, 2014, 3:47:37 PM10/28/14
to spray...@googlegroups.com
Marcin,

I am having the same problem. I am trying to read curl HTTP POST message & parse /unmarshal json object.

Could you please share your complete code ?

I am not sure how unmarshalling works, I tried various way but i am not able to unmarshal JSON object.

Thanks
Kunal

Mathias Doenitz

unread,
Nov 14, 2014, 8:17:19 AM11/14/14
to spray...@googlegroups.com
> I am trying to read curl HTTP POST message & parse /unmarshal json object.

What exactly are you trying to do?
What code do you already have and in what way doesn’t it do what you think it does?

Cheers,
Mathias

---
mat...@spray.io
http://spray.io

> To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/d40626a3-68ee-4af6-97fc-b1679fa7cf40%40googlegroups.com.

Qoo Mak

unread,
Nov 19, 2014, 5:44:04 PM11/19/14
to spray...@googlegroups.com
Marcin

Or you could be just a little less orthodox and use json4s and enjoy having case class Item(_id: Option[String], itemId: Int, name: String, active: Boolean)
And never write customJsonFormat again

Sorry
--
qoomak
Reply all
Reply to author
Forward
0 new messages