JACKSON support for Java Generics?

僤鯓⒐⒋嵵緔 提交于 2019-12-12 12:18:27

问题


Currently, I am working on a restFul project, which is schema based. So, we are using JAXB to do a XSD-->JAVA conversion. I have a class as follows:

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "", propOrder = {
     "systemResponse"
    })
    @XmlRootElement(name = "RestResponse")

public class RestResponse implements Serializable {

     private final static long serialVersionUID = 1L;
     @XmlElementRef(name = "SystemResponse", namespace = "http://www.intuit.com/psd/cdm/v1", type = JAXBElement.class)
   protected JAXBElement<? extends CdmComplexBase> systemResponse;

...
}

Here is how it is serialized:

{"systemResponse":{"name":"{http://www.intuit.com/psd/cdm/v1}Transactions","declaredType":"com.intuit.psd.cdm.v1.Transactions","scope":"javax.xml.bind.JAXBElement$GlobalScope","value":{"requestId":null,"requestName":null,"isEncrypted":null,"totalCount":null,"pageSize":null,"genDuration":null,"genDateTime":null,"transaction":[{"id":null,"externalKey":[],"metaData":null,"accountNumber":"12345678798","transactionNumber":null,"transactionReference":null,"batchID":null,"batchCycleDate":null,"paymentType":null,"paymentMethod":null,"transactionType":null,"cardType":null,"amount":null,"transactionDate":null,"authCode":null,"customerTransactionID":null,"ccnumberFirstSix":null,"ccnumberLastFour":null,"etctype":null,"posentryType":null}]},"nil":false,"globalScope":true,"typeSubstituted":false}}

When trying to deserialize, I am getting the following exception:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class javax.xml.bind.JAXBElement<com.intuit.psd.cdm.v1.CdmComplexBase>]: can not instantiate from JSON object (need to add/enable type information?)
 at [Source: java.io.StringReader@725d9aa7; line: 1, column: 20] (through reference chain: com.intuit.psd.cdm.v1.RestResponse["systemResponse"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObjectUsingNonDefault(BeanDeserializer.java:400)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:289)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2796)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1942)
    at com.bp.samples.json.HelperJackson.testMarshalUnmarshal(HelperJackson.java:122)
    at com.bp.samples.json.JSONToJavaTest.main(JSONToJavaTest.java:42)

Googling for solutions, suggests that you have to either add more meta-data to the serialized form or register a deserializer. Jettison is using JAXB annotations to successfully serialize and de-serialize. Plus, I can remove the namespace "http://www.intuit.com/psd/cdm/v1" from serialized too. Is there a way to do the same with JACKSON?

This is the code that I use to configure JACKSON:

    ObjectMapper mapper = new ObjectMapper();
    AnnotationIntrospector introspectorPrimary = new JacksonAnnotationIntrospector();
    AnnotationIntrospector introspectorSecondary = new JaxbAnnotationIntrospector();
    AnnotationIntrospector pair = new AnnotationIntrospector.Pair(introspectorPrimary, introspectorSecondary);
    mapper.getSerializationConfig().with(pair);
    mapper.getDeserializationConfig().with(pair);

回答1:


Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

Jackson is not a JAXB (JSR-222) compliant implementation, it only supports a subset of JAXB annotations in its JSON-binding implementation. For JAXB generated models you may be interested in EclipseLink JAXB (MOXy) which natively supports JSON binding.

JAVA MODEL

Below is a partial Java model that I inferred from your question.

RestResponse

package forum13591952;

import java.io.Serializable;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "systemResponse" })
@XmlRootElement(name = "RestResponse")
public class RestResponse implements Serializable {

    private final static long serialVersionUID = 1L;

    @XmlElementRef(name = "SystemResponse", namespace = "http://www.intuit.com/psd/cdm/v1", type = JAXBElement.class)
    protected JAXBElement<? extends CdmComplexBase> systemResponse;

}

CdmComplexBase

Below is a simplified version of your CdmComplexBase class.

package forum13591952;

import javax.xml.bind.annotation.XmlSeeAlso;

@XmlSeeAlso({Transactions.class})
public class CdmComplexBase {

}

Transactions

Below is a simplified version of your Transactions class.

package forum13591952;

public class Transactions extends CdmComplexBase {

    private long accountNumber;

    public long getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(long accountNumber) {
        this.accountNumber = accountNumber;
    }

}

ObjectFactory

Below is a simplified version of your ObjectFactory class. It specifies the @XmlElementDecl annotations that are used with your @XmlElementRef usage.

package forum13591952;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name="SystemResponse", namespace="http://www.intuit.com/psd/cdm/v1")
    public JAXBElement<CdmComplexBase> createCdmComplexBase(CdmComplexBase value) {
        return new JAXBElement<CdmComplexBase>(new QName("SystemResponse"), CdmComplexBase.class, value);
    }

    @XmlElementDecl(name="Transactions", namespace="http://www.intuit.com/psd/cdm/v1", substitutionHeadName="SystemResponse", substitutionHeadNamespace="http://www.intuit.com/psd/cdm/v1")
    public JAXBElement<Transactions> createTransactions(Transactions value) {
        return new JAXBElement<Transactions>(new QName("Transactions"), Transactions.class, value);
    }

}

jaxb.properties

To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

DEMO

The demo code below will marshal the objects to both XML and JSON

package forum13591952;

import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(RestResponse.class, ObjectFactory.class);

        ObjectFactory objectFactory = new ObjectFactory();

        RestResponse response = new RestResponse();
        Transactions transactions = new Transactions();
        transactions.setAccountNumber(12345678798L);
        response.systemResponse = objectFactory.createTransactions(transactions);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        // Marshal to XML
        marshaller.marshal(response, System.out);

        // Marshal to JSON
        marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
        marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
        marshaller.marshal(response, System.out);
    }

}

OUTPUT

Below is the output from running the demo code. Note how the JSON representation is very similar to the XML representation.

<?xml version="1.0" encoding="UTF-8"?>
<RestResponse xmlns:ns0="http://www.intuit.com/psd/cdm/v1">
   <ns0:Transactions>
      <accountNumber>12345678798</accountNumber>
   </ns0:Transactions>
</RestResponse>
{
   "Transactions" : {
      "accountNumber" : 12345678798
   }
}

FOR MORE INFORMATION

  • http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
  • http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
  • http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

UPDATE #1

Below are the answers to your first set of follow-up questions:

1) Is there a way to camal case the JSON variables: so AccountNumber --> accountNumber for JSON only?

You can use MOXy's external mapping document to customize the JSON representation. See linked answer below for a full code example.

  • Can I get MOXy to rename an element when generating json?

2) Jettison has got the namespace mapping, does Moxy have it too?

By default MOXy does not require that you emulate namespace qualification in your JSON message to match your XML structure. In our XML binding we do matching based on the qualified name and in our JSON binding we do the matching based on the local name based on the same metadata. We also support Jettison style namespaces. The linked answer below contains a full example of how this is done with MOXy.

  • How to provide JACKSON with the namespace-mapper in the code?

3) What would be the advantage of Moxy over Jettison, I guess performance?

Jettison is a library that converts JSON to/from StAX events so that it can be used with an XML binding library to produce/consume JSON (see: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html). Because of this Jettison has problems with the following items that MOXy doesn't.

  • Jettison has problems representing lists of size 1 as JSON arrays.
  • A special indicator is required for XML attributes
  • Difficult to specify whether you want the String "1" represented as "1" or 1 in JSON.
  • A special representation is required for XML namespaces (see: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison-namespace.html).

UPDATE #2

Below are the answers to your second set of follow-up questions:

1) How do you fix Jettison's list problem?

Jettison converts StAX events to/from JSON based solely on the events received. This means that in order for it to recognize a collection it needs to receive 2 startElement events with the same name, so lists of size 1 are not represented as JSON arrays. Since MOXy provides native JSON-binding it knows when data is coming from a list.

2) So, are you going from JAXB classes directly to JSON?

Yes, MOXy converts Java objects (with JAXB and MOXy annotations) directly to/from JSON.

3) Is there a paper or link that goes through question no 3 in more details?

We don't have a document comparing MOXy with Jettison. We did examine the pain points people experienced when using Jettison and made sure we eliminated those. And although MOXy does not need to emulate XML concepts like attributes and namespaces in JSON like Jettison does we provide settings to enable this behaviour to make it easier for people to transition from Jettison to MOXy.

  • How to provide JACKSON with the namespace-mapper in the code?

At this point I would like to recommend MOXy for Intuit when doing JAXB+JSON.

Thank you for your support. I'm pretty active on Stack Overflow, but if you post questions to the EclipseLink Forum you'll be able to get support from the entire team.

  • http://www.eclipse.org/forums/eclipse.rt.eclipselink



回答2:


JAXBElement is an XML-specific datatype (basically similar to a DOM tree), and Jackson does not know what to do with it. The reason Jettison may be able to handle this is because it builds on XML APIs, and only converts to JSON at the output end (or lowest part of input).

But why do you use JAXBElement there? It is a fallback used for cases where there is no way to really data-bind things; sort of like mapping data to a Java Map or so. Could you use real POJO instead? That would work just fine.



来源:https://stackoverflow.com/questions/13591952/jackson-support-for-java-generics

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!