Hi,
We are currently adapting a library based on XML messages to JSON. To ease
ourselves in we are trying to piggy back off the XML annotations as much as
possible and minise the number of mixins that need to be created.
We are running into a problem with collections, specificly those that have
an attached xml adapter. The attached test demonstrates the problem. The
class being serialized has four lists. The generic list is there as a base
line, we haven't encountered it as a use case. We would expect the other
three lists to produce the same output, but they don't. The adapted list,
which is the main use case, gives the same output as the generic list. The
other adapted list ends up lossing all the details from the contained
elements. Only the list without an adpater outputs everything.
We are currently using version 2.8.6.
- Has this been fixed in a newer version?
- If not, what are the technical limitations behind this issue?
Cheers
David
--
You received this message because you are subscribed to the Google Groups
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
For more options, visit https://groups.google.com/d/optout.
package com.enactor.core.json;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import junit.framework.TestCase;
/**
*
* @author David Driver
*/
public class JsonCollectionTest
extends TestCase {
public void testSerialize() throws Exception {
LibraryClass main = new LibraryClass();
main.addChild(new LibraryBean("Child"));
main.addChild(new LibraryCount(1234));
System.out.println(createObjectMapper().writeValueAsString(main));
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
JaxbAnnotationIntrospector primary = new JaxbAnnotationIntrospector(mapper.getTypeFactory());
primary.setNameUsedForXmlValue(null);
mapper.setSerializationInclusion(Include.NON_EMPTY)
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
// JAXB annotations will be inspected before jackson annotations
.setAnnotationIntrospector(AnnotationIntrospectorPair.create(primary, new JacksonAnnotationIntrospector()))
.addMixIn(TheMarkerInterface.class, CustomTypeIdMixin.class);
return mapper;
}
@JsonPropertyOrder(value = { "@customType" }, alphabetic = true)
@JsonTypeInfo(use = Id.CUSTOM, property = "@customType")
@JsonTypeIdResolver(CustomTypeResolver.class)
@JsonInclude(Include.NON_EMPTY)
private interface CustomTypeIdMixin {
}
private static class CustomTypeResolver
implements TypeIdResolver {
private static final String RESOLVER_DESCRIPTION = "Example.CustomTypeResolver";
private JavaType baseType;
@Override
public void init(JavaType baseType) {
this.baseType = baseType;
System.out.println(baseType);
}
@Override
public String idFromBaseType() {
return idFromValueAndType(null, baseType.getRawClass());
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
if (value == null) {
return suggestedType.getSimpleName();
}
return idFromValue(value);
}
@Override
public String idFromValue(Object value) {
if (value instanceof TheMarkerInterface) {
String id = createJAXBName(value.getClass());
if (id != null) {
return id;
}
return value.getClass().getSimpleName();
}
return null;
}
private String createJAXBName(Class<?> clazz) {
XmlRootElement root = clazz.getAnnotation(XmlRootElement.class);
if (root != null) {
return "{" + root.namespace() + "}" + root.name();
}
XmlType type = clazz.getAnnotation(XmlType.class);
if (type != null) {
return "{" + type.namespace() + "}" + type.name();
}
return null;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
// Not being tested
return null;
}
@Override
public String getDescForKnownTypeIds() {
return RESOLVER_DESCRIPTION;
}
@Override
public Id getMechanism() {
return Id.CUSTOM;
}
}
private static interface TheMarkerInterface {
// Interface that is part of the library
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "LibraryClass", namespace = "test")
@XmlRootElement(name = "libraryClass", namespace = "test")
private static class LibraryClass
implements TheMarkerInterface {
@XmlElement(name = "generic")
private List<Object> genericList = new ArrayList<>();
@XmlElement(name = "marked")
private List<TheMarkerInterface> markedList = new ArrayList<>();
@XmlElement(name = "adapted")
@XmlJavaTypeAdapter(value = IdempotentAdapter.class)
private List<TheMarkerInterface> adaptedList = new ArrayList<>();
@XmlElement(name = "markedAdapted")
@XmlJavaTypeAdapter(value = MarkedIdempotentAdapter.class)
private List<TheMarkerInterface> markedAdaptedList = new ArrayList<>();
public void addChild(TheMarkerInterface child) {
genericList.add(child);
markedList.add(child);
adaptedList.add(child);
markedAdaptedList.add(child);
}
public List<Object> getGenericList() {
return genericList;
}
public List<TheMarkerInterface> getMarkedList() {
return markedList;
}
public List<TheMarkerInterface> getAdaptedList() {
return adaptedList;
}
public List<TheMarkerInterface> getMarkedAdaptedList() {
return markedAdaptedList;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "LibraryBean", namespace = "test")
@XmlRootElement(name = "libraryBean", namespace = "test")
private static class LibraryBean
implements TheMarkerInterface {
private String value;
public LibraryBean(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "LibraryCount", namespace = "test")
@XmlRootElement(name = "libraryCount", namespace = "test")
private static class LibraryCount
implements TheMarkerInterface {
private long value;
public LibraryCount(long value) {
this.value = value;
}
public long getValue() {
return value;
}
}
private static class IdempotentAdapter<E>
extends XmlAdapter<E, E> {
@Override
public E unmarshal(E v) throws Exception {
return v;
}
@Override
public E marshal(E v) throws Exception {
return v;
}
}
private static class MarkedIdempotentAdapter<E extends TheMarkerInterface>
extends IdempotentAdapter<E> {
}
}
[simple type, class com.enactor.core.json.JsonCollectionTest$TheMarkerInterface]
[simple type, class com.enactor.core.json.JsonCollectionTest$TheMarkerInterface]
[simple type, class com.enactor.core.json.JsonCollectionTest$TheMarkerInterface]
[simple type, class com.enactor.core.json.JsonCollectionTest$TheMarkerInterface]
[simple type, class com.enactor.core.json.JsonCollectionTest$LibraryClass]
{
"@customType" : "{test}libraryClass",
"adapted" : [ {
"value" : "Child"
}, {
"value" : 1234
} ],
"generic" : [ {
"value" : "Child"
}, {
"value" : 1234
} ],
"marked" : [ {
"@customType" : "{test}libraryBean",
"value" : "Child"
}, {
"@customType" : "{test}libraryCount",
"value" : 1234
} ],
"markedAdapted" : [ {
"@customType" : "{test}libraryBean"
}, {
"@customType" : "{test}libraryCount"
} ]
}