4

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);
Behzad Pirvali
  • 764
  • 3
  • 10
  • 28

2 Answers2

5

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


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.

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.

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.


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.

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.

Pyves
  • 6,333
  • 7
  • 41
  • 59
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Thank u SO MUCH for this elaborate & accurate response! Off course, reading your previous posts, I kind of knew, that Jettison or Moxy would be a better approach for me. But, I got into little problems with those too. – Behzad Pirvali Nov 28 '12 at 23:56
  • 1
    Here is the output from the above demo class with real classes: ` 12345678798 { "Transactions" : { "Transaction" : [ { "AccountNumber" : "12345678798" } ] } }` – Behzad Pirvali Nov 28 '12 at 23:56
  • So, here are my questions: 1) Is there a way to camal case the JSON variables: so AccountNumber --> accountNumber for JSON only? 2) Jettison has got the namespace mapping, does Moxy have it too? 3) What would be the advantage of Moxy over Jettison, I guess performance? Thanks, Behzad – Behzad Pirvali Nov 28 '12 at 23:58
  • @BehzadPirvali - I have updated my post with answers to your follow up questions. – bdoughan Nov 29 '12 at 11:24
  • Thank U so much for your reply and sorry for the late response. I did not get a notification and somehow missed your answers. Great answers, especially the last one. So, I really need to know more about MOXy internal. How do you fix Jettison's list problem? So, are you going from JAXB classes directly to JSON? Is there a paper or link that goes through question no 3 in more details. The link you provided is not very related as far a I can tell! Thank u so much again. At this point I would like to recommend MOXy for Intuit when doing JAXB+JSON. Thanks again – Behzad Pirvali Dec 06 '12 at 01:16
  • @BehzadPirvali - I have updated my post with answers to your second set of follow up questions. Thank you for taking an interest in MOXy. – bdoughan Dec 06 '12 at 10:51
1

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.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • 1
    All good points, except that this code is generated by JAXB compiler. – Behzad Pirvali Nov 28 '12 at 19:34
  • I understand that. But it still suggests that schema used is not very good for data binding as it forces use of JAXBElement as fallback -- something that does not map cleanly to Java types. So it might be good idea to see what is causing that as it might be possible to modify schema to produce better Java classes. – StaxMan Nov 29 '12 at 01:08
  • That is a good point and absolutely agree. But then again xsd, is more powerful than Java and many other languages. In this particular case, one of the contained elements is using xsd-choice to contain different types. This brings lots of flexibility into the contract, but there is no Java construct, which could mutate to different types. In C-language "Union" would kind of do it. – Behzad Pirvali Nov 30 '12 at 18:22
  • Agreed there -- and this is actually my main issue wrt using XML (or, certain types of JSON constructs) -- for passing Object data. It is (too) easy to create data structures with high impedance. But if you need to support that, yes, XML with JAXB is probably the way to go. – StaxMan Nov 30 '12 at 19:33
  • do you guys Behzad and @StaxMan have any solutions or options on how to avoid the JAXBElement when I Unmarshal by Declared Type especially if I'm trying to use XSD to create POJOs so I can convert to JSON? – dweeb Jan 31 '19 at 15:35