Closed rpatrick00 closed 8 years ago
This is the case of differing defaults; Jackson defaults to using wrapper element, whereas JAXB does not. You can use Jackson annotation from module jackson-dataformat-xml
(I forget the name, something like @JacksonXmlWrapperElement
) on specified property, and XML module also has default configuration setting (for XmlMapper
) that may be changed to choose JAXB default setting.
Let me know if you can't locate the settings; I forget the exact methods.
@JacksonXmlElement(useWrapping=false) does exactly the opposite of what I want/need. It removes the outer list wrapper but the individual list elements are still being wrapped.
I am not seeing anything that allows me to change the defaults to JAXB, sorry.
Ok. So this is actually more related to polymorphic type handling, where type identifier is to be used as the element wrapper. Would it be possible to include definitions of value types (or at least one)?
I also noticed that type declaration (private List computers;
) is missing generic type (I assume it should be something like List<Computer> computers
?)
Sorry, I was bitten by the web rendering of the unescaped <Computer> tag because my code does have the List type information:
@XmlRootElement(name="company") @XmlAccessorType(XmlAccessType.FIELD) public class Company { //@XmlElementWrapper(name = "computers") @XmlElements({ @XmlElement(type = DesktopComputer.class, name = "desktop"), @XmlElement(type = LaptopComputer.class, name = "laptop") }) private List<Computer> computers; ...
I am not sure exactly what you mean by including the "definitions of value types" (isn't that what the @XmlElements annotation above is doing?). My Computer super class also has the @XmlSeeAlso annotation with the type information:
@XmlSeeAlso({ LaptopComputer.class, DesktopComputer.class }) @XmlAccessorType(XmlAccessType.FIELD) public class Computer { @XmlAttribute @XmlID private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } }
I have full control over the source so I can add additional annotations, if required, to get the proper XML shape.
Thanks, Robert
I guess it would be great to split this problem into two parts, due to 2 possible problem areas:
So, a reproduction that only did one of the two would be most helpful in figuring out the actual issue. I would probably start with JAXB + JSON use case; and if that does not fail, try Jackson annotations with XML output to try to reproduce the problem.
I will work on this and at a minimum, provide more details and a reproducer of the specific problem(s).
JAXB annotations with JSON fails in Jackson 2.7.0-rc2:
Using Jackson to read the file previously written by Jackson: [WARNING] java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl. java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces sorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293) at java.lang.Thread.run(Thread.java:745) Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (FIELD_NAME), expected START_OBJECT: need JSON Object to contain As.WRAPPER_OBJ ECT type information for class test.Computer at [Source: company-jackson.json; line: 3, column: 5](through reference chain: test.Company["computers"]->java.util.ArrayList[0]) at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingE xception.java:216) at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenExcep tion(DeserializationContext.java:962) at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserialize r._deserialize(AsWrapperTypeDeserializer.java:91) at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserialize r.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:49) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserialize WithType(BeanDeserializerBase.java:992) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deser ialize(CollectionDeserializer.java:279) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deser ialize(CollectionDeserializer.java:249) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deser ialize(CollectionDeserializer.java:26) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize (SettableBeanProperty.java:490) at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAn dSet(FieldProperty.java:101) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserial ize(BeanDeserializer.java:257) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(Bea nDeserializer.java:125) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMa pper.java:3773) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.ja va:2657) at test.RunThis.readJacksonFile(RunThis.java:61) at test.RunThis.main(RunThis.java:94) ... 6 more [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.236 s [INFO] Finished at: 2015-12-16T06:51:35-06:00 [INFO] Final Memory: 21M/378M [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.4.0:java (r un-this) on project jackson-list-test: An exception occured while executing the Java class. null: InvocationTargetException: Unexpected token (FIELD_NAME), expe cted START_OBJECT: need JSON Object to contain As.WRAPPER_OBJECT type informatio n for class test.Computer [ERROR] at [Source: company-jackson.json; line: 3, column: 5](through reference chain: test.Company["computers"]->java.util.ArrayList[0]) [ERROR] -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e swit ch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please rea d the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionE xception
However, the JSON generated does not have the extra layer of list element wrappers (i.e., it is what I would expect). Full reproducer is at https://github.com/rpatrick00/jackson-list-test
{
"computers" : [ {
"desktop" : {
"id" : "computer-1",
"location" : "Bangkok"
}
}, {
"desktop" : {
"id" : "computer-2",
"location" : "Pattaya"
}
}, {
"laptop" : {
"id" : "computer-3",
"vendor" : "Apple"
}
} ]
}
In switching to Jackson annotations (so that I could switch to the XML serialization part of the problem), I am able to generate the proper JSON but Jackson is still failing to read it back with Jackson 2.7.0-rc2. Maybe I omitted something in the conversion from JAXB annotations (since I am less familiar with them than JAXB annotations)? Here are the details (reproducer is the same as above, just changed the POJO annotations as shown below). The reproducer with Jackson annotations is available at https://github.com/rpatrick00/jackson-xml-list-test:.
Error:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (FIELD_NAME), expected START_OBJECT: need JSON Object to contain As.WRAPPER_OBJECT type information for class test.Computer at [Source: company-jackson.json; line: 3, column: 5] (through reference chain: test.Company["computers"]->java.util.ArrayList[0]) at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:216) at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:962) at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer._deserialize(AsWrapperTypeDeserializer.java:91) at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:49) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:992) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:279) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26) at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:490) at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:257) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3773) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2657) at test.RunThis.readJacksonFile(RunThis.java:61) at test.RunThis.main(RunThis.java:94) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Company.java
@JsonRootName(value = "company") public class Company { private List<Computer> computers; public Company() { computers = new ArrayList<Computer>(); } public List<Computer> getComputers() { return computers; } public void setComputers(List<Computer> computers) { this.computers = computers; } public Company addComputer(Computer computer) { if (computers == null) { computers = new ArrayList<Computer>(); } computers.add(computer); return this; } }
Computer.java
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "type" ) @JsonSubTypes({ @JsonSubTypes.Type(value = DesktopComputer.class, name = "desktop"), @JsonSubTypes.Type(value = LaptopComputer.class, name = "laptop") }) public class Computer { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } }
DesktopComputer.java
@JsonTypeName("desktop") public class DesktopComputer extends Computer { @JsonProperty("location") private String location; public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } }
LaptopComputer.java
@JsonTypeName("laptop") public class LaptopComputer extends Computer { @JsonProperty("vendor") private String vendor; public String getVendor() { return vendor; } public void setVendor(String vendor) { this.vendor = vendor; } }
JSON file generated
{ "computers" : [ { "desktop" : { "id" : "computer-1", "location" : "Bangkok" } }, { "desktop" : { "id" : "computer-2", "location" : "Pattaya" } }, { "laptop" : { "id" : "computer-3", "vendor" : "Apple" } } ] }
Switching the native Jackson annotations to serialize XML instead of JSON does two undesirable things:
1.) It throws the same error as above 2.) It generates the XML with the unnecessary and unwanted "list element" wrappers
<?xml version='1.1' encoding='UTF-8'?> <company> <computers> <computers> <desktop> <id>computer-1</id> <location>Bangkok</location> </desktop> </computers> <computers> <desktop> <id>computer-2</id> <location>Pattaya</location> </desktop> </computers> <computers> <laptop> <id>computer-3</id> <vendor>Apple</vendor> </laptop> </computers> </computers> </company>
The reproducer with Jackson annotations is available at https://github.com/rpatrick00/jackson-xml-list-test. If you run the RunThis main class without any args, it will serialize XML. Add the -useJson arg to get it to serialize Json instead.
In Jackson 2.4.3, we were able to strip out these unwanted wrapper objects using a custom serializer/deserializer (even though we would have preferred not to have to write custom code, it was our only option).
That custom code no longer works in Jackson 2.6.3 and newer (didn't test the intermediate versions to see where it broke) and, of course, requires the use of Jackson annotations to register them. If I remember correctly, the error when we tried to use the custom serializers/deserializers to strip out the wrappers in 2.6.3 was very similar to, if not exactly, the error above.
In my opinion, it would be very desirable to eliminate the need for the custom serializers/deserializers to make it possible to get Jackson's XML output with JAXB annotations to match JAXB exactly. While it would be nice for us if this were the default when using the Jaxb Annotations Module, I would be ok with having to set additional mapper/module properties, if necessary, but would like to not have to add Jackson annotations.
@rpatrick00 Thank you for the test and investigation! I will need to read this with thought, hoping to start resolving parts of the problem.
@rpatrick00 Interesting. I can reproduce the basic jackson-databind
problem with the example. Suspecting it is related to combination of Object and Type Id somehow, will dig deeper.
Ok, yes. And another piece of the puzzle is that handling that is needed to allow JSON Object wrapped Object Id (for JSOG) is somehow causing the issue, as an unintended side effect. Change probably went in 2.5 (guessing it might be issue https://github.com/FasterXML/jackson-databind/issues/669) -- and building from tag 2.5.0 does indeed pass, and latest 2.5 from branch fails. So that's what triggers the problem.
Ok, first things first: the underlying problem with databind is fixed (for 2.6.5 patch, and master
for 2.7.0).
This was an uncaught regression caused by an improvement to allow JSOG-style object ids (which are simple JSON Object wrappers... something not originally thought to be needed, or useful, plus tricky to deal in streaming approach). I am glad this was caught so big thank you for the report and detective work.
Now, as to XML/JAXB part, it appears like JAXB annotations are not to blame, as the results are the same with equivalent Jackson @JsonTypeInfo
annotations. Polymorphic type handling is tricky on XML side, and double so with Lists without wrapper element.
I will try to work on this for XML module next.
Will close this issue now, assuming problem exists within XML module, and is not due to translation of JAXB annotations itself.
So is there an existing issue on the XML side?
@rpatrick00 I assumed one of existing issues would cover it. But if not, a new one would be needed over there. If I understand issue correctly it is combination of polymorphic serialization as XML, using As.WRAPPER_OBJECT
producing output that has one more element than what you would want.
Description few comments up should cover it, although due to a fix to master
of jackson-databind
, only part of it fails.
I'll file a new issue just in case, linking back to this one -- if there is a duplicate, it can be closed, but it's better to have two than none.
Re-created remaining problem as: https://github.com/FasterXML/jackson-dataformat-xml/issues/178
I have a very simple example of a class with a List member that uses polymorphism. The Java code looks like this:
JAXB serializes the output correctly (or at least in the way that I want it) like this:
Jackson JAXB annotations wraps each resulting element in the list in a redundant <computers> tag like this:
How can I eliminate the extra wrapping Jackson is doing to generate the same shape as JAXB?