jackson-dataformat-xml
jackson-dataformat-xml copied to clipboard
Unable to (de)serialize `null` in polymorphic Collection -- may need to use `xsi:nil` handling?
Hello,
I am using jackson-dataformat-xml-2.9.8 and I use the as-property-typing to handle polymorphic types in collections. Whereas it works in normal cases, there is a problem when the collection contains a null element: the deserializer complains on missing type-id, although in this case none is needed.
In particular, the Jackson XML deserializer is unable to deserialize the output of Jackson XML serializer.
Example code:
package test; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.util.ArrayList; import java.util.List; import org.junit.Test; public class CollectionTest { public static class Master { public Master() { super(); } public List<Detail> details = new ArrayList<>(); } public static class Detail { public Detail() { super(); } } @Test public void testNull() throws Exception { JacksonXmlModule jacksonXmlModule = new JacksonXmlModule(); ObjectMapper.DefaultTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(DefaultTyping.NON_FINAL) { @Override public boolean useForType(JavaType t) { if (t.isCollectionLikeType()) { return false; } return true; } }; XmlMapper mapper = new XmlMapper(jacksonXmlModule); mapper.setDefaultTyping( typeResolverBuilder .init(JsonTypeInfo.Id.CLASS, null) .inclusion(JsonTypeInfo.As.PROPERTY) .typeProperty("cl") ); Master master = new Master(); master.details.add(null); String xml = mapper.writeValueAsString(master); System.out.println(xml); Master deserializedMaster = mapper.readValue(xml, Master.class); System.out.println(deserializedMaster.details); } }
Now, the serializer creates XML:
<Master cl="test.CollectionTest$Master"><details><details/></details></Master>
while the deserializer complains on an empty element <details/>
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class test.CollectionTest$Detail]: missing type id property 'cl' (for POJO property 'details') at [Source: (StringReader); line: 1, column: 50] (through reference chain: test.CollectionTest$Master["details"]->java.util.ArrayList[0]) at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43) at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1645) at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1218) at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:300) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1178) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:288) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27) at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136) at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288) at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:189) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130) at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1178) at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004) at test.CollectionTest.testNull(CollectionTest.java:74) 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:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Now, I understand that there may be some complications to determine the context for the deserializer in the sense of JsonTokens. However, it seems that the problem is caused by not properly detecting JsonToken.NULL_VALUE and reading JsonToken.START_OBJECT instead. This occurs in com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.nextToken() in a code segment claiming by a comment to fix the issue #180 (note that there are 2 such fragments in the code, lines 548 and 590):
// 06-Jan-2015, tatu: as per [dataformat-xml#180], need to // expose as empty Object, not null (or, worse, as used to // be done, by swallowing the token) _nextToken = JsonToken.END_OBJECT; _parsingContext = _parsingContext.createChildObjectContext(-1, -1); return (_currToken = JsonToken.START_OBJECT);
Without this fix of issue #180, the parser would return JsonToken.NULL_VALUE and handle the case correctly.
Should JacksonXML be able to deserialize nulls in collections? Is there a better way, how to represent nulls in collections so that JacksonXML understands the concept?
我的好像也是这个问题?怎么解决呢
Quick note: should probably make use of new xsi:nil
handling features; will edit title.