Spray JSON formats

467 views
Skip to first unread message

Steffen Fritzsche

unread,
Sep 7, 2011, 1:23:14 PM9/7/11
to spray...@googlegroups.com
Hi,

I've got a custom spray json format. On the client side the ordering of some attributes changed and now my spray backend wasn't able to deserialize the json message anymore. It became clear that the order of the attributes is important but shouldn't spray-json decode the messages if all fields are included regardless of the field ordering?

Steffen

Mathias

unread,
Sep 9, 2011, 3:28:31 AM9/9/11
to spray...@googlegroups.com
Steffen,

yes, the attribute order shouldn't matter.
I have created a GH issues to track this (https://github.com/spray/spray-json/issues/4).

Cheers and thanks for the report,
Mathias

---
mat...@spray.cc
http://www.spray.cc

Mathias

unread,
Sep 27, 2011, 3:40:01 PM9/27/11
to spray-user
Steffen,

I've looked into this issue and realized, that currently the
JsonFormat returned by the "jsonFormat" helper already is member field
order agnostic.
Would you be able to provide more details on the error you were
seeing, maybe even a test case?

Cheers,
Mathias

On Sep 7, 7:23 pm, Steffen Fritzsche <fritzs...@scriptroom.de> wrote:
> Hi,
>
> I've got a custom sprayjsonformat. On the client side the ordering of some attributes changed and now my spray backend wasn't able to deserialize thejsonmessage anymore. It became clear that the order of the attributes is important but shouldn't spray-jsondecode the messages if all fields are included regardless of the field ordering?
>
> Steffen

Steffen Fritzsche

unread,
Sep 28, 2011, 2:58:02 AM9/28/11
to spray...@googlegroups.com
Hi Mathias,

no test case at hand, at least at the moment. Try to provide one later. But maybe my code helps

This is the read method:

def read(value: JsValue) = value match {
case JsObject(
JsField("name", JsString(name)) ::
JsField("description", JsString(description)) ::
JsField("scm", JsNumber(scm)) ::
JsField("scmUrl", JsString(scmUrl)) ::
JsField("scmBranch", JsString(scmBranch)) :: Nil) =>
new Project(0, name, description, AnalysisSteps.Created, SCM(scm.toInt), scmUrl, scmBranch, null)

case JsObject(
JsField("scm", JsNumber(scm)) ::
JsField("scmBranch", JsString(scmBranch)) ::
JsField("name", JsString(name)) ::
JsField("description", JsString(description)) ::
JsField("scmUrl", JsString(scmUrl)) :: Nil) =>
new Project(0, name, description, AnalysisSteps.Created, SCM(scm.toInt), scmUrl, scmBranch, null)

case _ => throw new DeserializationException("Project expected")
}

I had to pull in the second case because the matching did not work for the first on. I did not get an error, it just returned my default case.

It's not that big of a deal to put in additional matches but I would have expected the first JsObject case also matching the the second on, what for my app is not true.

Thanks for looking into this!
Steffen

Mathias

unread,
Sep 28, 2011, 4:27:13 AM9/28/11
to spray...@googlegroups.com
Steffen,

I see, where you are coming from.
You expect the JsObject extractor to unapply its field in an order-agnostic fashion.
Unfortunately this is not something that's possible with Scala extractors.

The unapply method of JsObject has this signature:

def unapply(o: JsObject): Option[List[JsField]

During the pattern matching the process is as follows:
1. The JsObject unapply method is called
2. If its result is `None`, the next case is tried right away
3. If its result is `Some(listOfFields)` the pattern matching process recurses into each field to determine a match.

It's impossible for the JsObject unapply method to "see" the order of the field matches that you would like to perform. Only the pattern matching code provided by the scala compiler sees this order and either matches it or doesn't match it.
This means that, if you decide to deserialize from JSON "manually" (as you do), you have to take care of ordering yourself.

The easier option would definitely be to simply rely on spray-jsons case class support, which already is member order agnostic (and would also reduce your code size significantly).
If your "Project" cannot be a case class, can't you move out its data into a separate case class?
We have a complete case class model of everything we need to serialize to and from JSON, this works very nicely and is incredibly compact…

Cheers,
Mathias

---
mat...@spray.cc
http://www.spray.cc

Steffen Fritzsche

unread,
Sep 28, 2011, 4:49:57 AM9/28/11
to spray...@googlegroups.com
Mathias,

thanks for the explanation.

> The easier option would definitely be to simply rely on spray-jsons case class support, which already is member order agnostic (and would also reduce your code size significantly).
> If your "Project" cannot be a case class, can't you move out its data into a separate case class?

I'm doing this for all other classes. Those are either case classes or I have a corresponding case class for the json serialization. The project is a bit special, since the write method adds a couple of "read-only" fields, so serialization and de-serialization for my project classes is different. That why I initially wrote it as custom protocol.

I'll look into your code for the case classes to modify my custom protocol accordingly. Meanwhile, since only those two cases are generated by my client, the current workaround is a fast and simple solution for me.

Steffen

Reply all
Reply to author
Forward
0 new messages