The web service I am writing using Jersey / JAXB plays perfectly with
@Consuming and @Producing XML from JAXB-annotated POJOs. However when
working with JSON, JAXB (or Jersey?) seems to require a brittle ordering of
JSON elements. I am using Jersey 1.1.5 and JaxB 2.2 ri-20091104 (latest).
I've created a simple Web application project to demonstrate:
POJO
---------------
@XmlRootElement(name = "Test")
@XmlAccessorType(XmlAccessType.FIELD)
public class Test
{
@XmlAttribute
private int int1;
@XmlElement
private String string1;
@XmlElement
private List<String> strings;
@XmlAttribute
private int int2;
@XmlAttribute
private long long1;
public Test()
{
super();
}
public Test(int int1, String string1, List<String> strings, int int2,
long long1)
{
super();
this.int1 = int1;
this.int2 = int2;
this.long1 = long1;
this.string1 = string1;
this.strings = strings;
}
... hashcode & equals omitted.
}
Interface
---------------
@Path("test")
public interface IView
{
@GET
@Path("get/")
@Produces(MediaType.APPLICATION_JSON)
public abstract Test getTest();
@POST
@Path("post/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public abstract Test takeTest(final Test test);
}
Implementation
---------------
@Provider
public class View implements IView
{
public Test getTest()
{
return new Test(1, "string", Arrays.asList(new String[] { "s1", "s2"
}), 2, 3L);
}
public Test takeTest(final Test test)
{
return test;
}
}
When I request /test/get, JAXB returns me the following JSON every time,
even though I have no @XmlType(propOrder =) class annotation set. This is
fine - I don't mind an arbitrary ordering for output.
(Note that I am using a custom ContextResolver<JAXBContext> to read &
return JSONConfiguration.natural() notation for my Test class)
{"int1":1,"int2":2,"long1":3,"string1":"string","strings":["s1","s2"]}
However, when I try and consume JSON that does not match this order, strange
things happen and my int / long fields are not set. This is shown in the
following example:
Post:
{"int1":1,"string1":"string","strings":["s1","s2"],"int2":2,"long1":3}
Response:
{"int1":1,"int2":0,"long1":0,"string1":"string","strings":["s1","s2"]}
equals(): false
[...fields int2 and long1 are not being set.]
----------
I've created a test script which constructs various JSON strings and posts
them off to my takeTest() method, which returns a JSON representation of the
parsed Test object.
I then use Jackson within my test script to build this into a new Test
object (via a JaxbAnnotationIntrospector) and test equality on the two
objects.
Post:
{"string1":"string","strings":["s1","s2"],"int1":1,"int2":2,"long1":3}
Response:
{"int1":0,"int2":0,"long1":0,"string1":"string","strings":["s1","s2"]}
equals(): false
[... none of the int or long fields are being set]
----------
Post:
{"int1":1,"int2":2,"string1":"string","strings":["s1","s2"],"long1":3}
Response:
{"int1":1,"int2":2,"long1":0,"string1":"string","strings":["s1","s2"]}
equals(): false
[... int1 and int2 are correctly set, but long1 is not set]
----------
Post:
{"int1":1,"int2":2,"long1":3,"string1":"string","strings":["s1","s2"]}
Response:
{"int1":1,"int2":2,"long1":3,"string1":"string","strings":["s1","s2"]}
equals(): true
----------
If I attach an @XmlType(propOrder = { "int1", "int2", "long1", "string1",
"strings" }) annotation to my Test class, Jackson will happily follow this
and generate JSON that matches JAXB's expected ordering. I don't however
think the onus should be on clients to guarantee the 'primitives first'
ordering that Jersey / JAXB seems to expect.
My issue here is that if I'm using a particular JSON library (in whatever
language, divorced from my server) to post JSON at /test/post/ I cannot
guarantee element ordering. Is this a bug with Jersey's use of JAXB? Or
potentially JAXB by itself?