问题
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