Skip to content
This repository has been archived by the owner on Jan 22, 2019. It is now read-only.

Problem with XmlAdapter downcasting #36

Closed
somiranroy opened this issue Nov 21, 2014 · 12 comments
Closed

Problem with XmlAdapter downcasting #36

somiranroy opened this issue Nov 21, 2014 · 12 comments

Comments

@somiranroy
Copy link

I like Jackson and it is awesome but this issue is killing me! - http://jira.codehaus.org/browse/JACKSON-723. This is blocking me from adopting Jackson as a JSON generator. Can you please help.

The only difference between my problem and the Jira item is that I am using JaxbAnnotationModule instead of JacksonAnnotationIntrospector.

@cowtowncoder
Copy link
Member

Ok. As per comments on original issue:

To work on this, I think I need a unit test to try to resolve this: code snippet helps, but I'd also need adapter implementation.
With a unit test I hope it'd be easy to fix the problem.

@somiranroy
Copy link
Author

I shall create and send now. Just curious- what is your release process. Once you fix can I get it immediately?

@somiranroy
Copy link
Author

Here is the code-

Any.java

package com.somiran.exp.jackson.jaxb;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class)
public interface Any {
    public boolean isKnown();
}

Known.java

package com.somiran.exp.jackson.jaxb;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class)
public abstract class Known implements Any {
    @Override
    public boolean isKnown() {
        return false;
    }
    public Known(String type) {
        super();
        this.type = type;
    }
    String type;
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public abstract void validate();
}

UnKnown.java

package com.somiran.exp.jackson.jaxb;
public abstract class UnKnown implements Any {
    @Override
    public boolean isKnown() {
        return false;
    }
}

Resource.java

package com.somiran.exp.jackson.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class)
@XmlRootElement
public class Resource extends Known {
    public Resource(Integer resourceId, String resourceName) {
        super("Resource");
        this.resourceId = resourceId;
        this.resourceName = resourceName;
    }
    public Integer getResourceId() {
        return resourceId;
    }
    public void setResourceId(Integer resourceId) {
        this.resourceId = resourceId;
    }
    public String getResourceName() {
        return resourceName;
    }
    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }
    Integer resourceId;
    String  resourceName;
    @Override
    public void validate() {
    }
}

ResourceList.java

package com.somiran.exp.jackson.jaxb;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class)
@XmlRootElement
public class ResourceList extends Known{
    public ResourceList(String name) {
        super("ResourceList");
        this.name = name;
    }
    List<Resource> resourceList;
    String name;

    @XmlElement(name="resource")
    public List<Resource> getResourceList() {
        return resourceList;
    }
    public void setResourceList(List<Resource> resourceList) {
        this.resourceList = resourceList;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void validate() {
    }
}

AnyAdapter.java

package com.somiran.exp.jackson.jaxb;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AnyAdapter extends XmlAdapter<Object, Any> {
    @Override
    public Any unmarshal(Object source) 
        throws Exception {
        throw new RuntimeException("Don't Unmarshal using JAXB");
    }
    @Override
    public Object marshal(Any any) 
        throws Exception {
        if (any == null) {
            return null;
        }
        if (any.isKnown()) {
            return any;
        } else {
            return null;//UnknownAdapter.getInstance().marshal((Unknown)any);
        }
    }
}

JacksonJaxbAdapterImpl.java

package com.somiran.exp.jackson.jaxb;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;

public class JacksonJaxbAdapterImpl {

    public ObjectMapper initializeJackson() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setSerializationInclusion(Include.NON_NULL);

        // Need to use JaxB Annotation.
        JaxbAnnotationModule module = new JaxbAnnotationModule();
        mapper.registerModule(module);

        SimpleModule mySimpleModule = new SimpleModule();
        mySimpleModule.addSerializer(Long.class, new ToStringSerializer());
        mySimpleModule.addSerializer(Double.class, new ToStringSerializer());
        mapper.registerModule(mySimpleModule);

        return mapper;
    }
    public void writeObject(Object target, Writer writer, boolean isPretty) throws Exception {
        ObjectMapper mapper = initializeJackson();
        try {
            if(isPretty) {
                mapper.writerWithDefaultPrettyPrinter().writeValue(writer,target);
            } else {
                mapper.writeValue(writer,target);
            }
        } catch (JsonGenerationException e) {
            throw new RuntimeException(e);
         } catch (JsonMappingException e) {
            throw new RuntimeException(e);
         } catch (IOException e) {
            throw new RuntimeException(e);
         }
    }
}

JacksonJaxbAdapterTest.java

package com.somiran.exp.jackson.jaxb;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

public class JacksonJaxbAdapterTest {
    public static void main(String[] args) throws Exception {
        JacksonJaxbAdapterTest test = new JacksonJaxbAdapterTest();
        ResourceList report = test.makeObject();
        StringWriter jacksonWriter = new StringWriter();
        JacksonJaxbAdapterImpl jacksonMainClass = new JacksonJaxbAdapterImpl();
        jacksonMainClass.initializeJackson();
        jacksonMainClass.writeObject(report, jacksonWriter, true);
        System.out.println(jacksonWriter);
    }

    ResourceList makeObject() {
        Resource primary = new Resource(12,"primary");
        Resource secondary = new Resource(13, "secondary");
        List<Resource> listofRes = new ArrayList<Resource>();
        listofRes.add(primary);
        listofRes.add(secondary);
        ResourceList report = new ResourceList("Daily");
        report.setResourceList(listofRes);
        return report;
    }

Test Method:

  1. Run JacksonJaxbAdapterTest, the JSON output is
    "null".
  2. Remove @XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class) from ResourceList.java. Response is:
    {
      "known" : false,
      "name" : "Daily",
      "resource" : [ null, null ],
      "type" : "ResourceList"
    }
  1. Remove @XmlJavaTypeAdapter(value=AnyAdapter.class, type=Any.class) from Resource.java.
    {
      "known" : false,
      "name" : "Daily",
      "resource" : [ {
        "known" : false,
        "resourceId" : 12,
        "resourceName" : "primary",
        "type" : "Resource"
      }, {
        "known" : false,
        "resourceId" : 13,
        "resourceName" : "secondary",
        "type" : "Resource"
      } ],
      "type" : "ResourceList"
    }
The problem is because AnyAdapter--XmlAdapter<Object, Any>::marshall() returning Object- it picks up UnknownSerializer

@cowtowncoder
Copy link
Member

Thanks! If I figured out how to fix it, I'll commit it, and code is available from git, so you can build it.
But it will take some time until next release is out, since we do release all Jackson components together, so there are no per-module releases.

@saxenapavankumar
Copy link

Hi- Need this fix too... any date?

@cowtowncoder
Copy link
Member

Nope. I haven't had time to work on this, nor will likely have any in near future.
Pull Requests are always welcome however.

@saxenapavankumar
Copy link

I am going to look at Jackson code for the first time. Any suggestion? Pointer? idea?

@cowtowncoder
Copy link
Member

@saxenapavankumar Excellent, if you could give it a go. I would try with 2.5.0 sources (i.e. master branch), but you may need to compile multiple components locally (at least jackson-databind; others may be fine from Maven central snapshots).

Unit test from above is quite big, and one thing that could help is trimming it down a bit.
Either way, creating a (failing) unit test as part of this module would help.
Problem is likely to be within JAXB module's handling, which is quite small. If not, it'd be something in jackson-databind, which is much bigger codebase.

@somiranroy
Copy link
Author

I looked at the code some back. The issue is with the code BeanSerializerFactory::createSerializer() and something that I didnt check. The output of the XmlAdapter::marshal() is "Object"- it makes an instance of StdDelegatingSerializer instead of BeanSerializer and that messes things up. I tried changing the adapter definition to-
public class AnyAdapter extends XmlAdapter [Known, Any]

It gives me fields from Known class only and not the sub class (Resource)- see the response below

{
  "known" : false,
  "name" : "Daily",
  "resource" : [ {
    "known" : false
  }, {
    "known" : false
  } ],
  "type" : "ResourceList"
}

@cowtowncoder
Copy link
Member

@somiranroy Right: if nominal type of Object is given, it is NOT assumed to be a POJO -- type is assumed to be used as is, to locate actual serializer to delegate to. And if type is specified as base type, that, too, will just handle base type and not try to figure out polymorphic type. I think JAXB also requires static typing like this, although given complexity of the spec I could be wrong here.

@cowtowncoder
Copy link
Member

I suspect this fix:

FasterXML/jackson-databind#731

which will be in 2.5.3 (and 2.6.0) might help here.

@cowtowncoder
Copy link
Member

I do not have a way to reproduce the problem at this point, so closing. May be reopened with reproducible unit test / code.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants