问题
I have an http service that is using Spring (v4.0.5). Its http endpoints are configured using Spring Web MVC. The responses are JAXB2-anotated classes that are generated off of a schema. The responses are wrapped in JAXBElement
as the generated JAXB classes do not sport @XmlRootElement
annotations (and the schema cannot be modified to doctor this). I had to fight a bit with getting XML marshalling ti work; in any case, it is working.
Now I am setting up JSON marshalling. What I am running into is getting JSON-documents that feature the JAXBElement
"envelope".
{
"declaredType": "io.github.gv0tch0.sotaro.SayWhat",
"globalScope": true,
"name": "{urn:io:github:gv0tch0:sotaro}say",
"nil": false,
"scope": "javax.xml.bind.JAXBElement$GlobalScope",
"typeSubstituted": false,
"value": {
"what": "what",
"when": "2014-06-09T15:56:46Z"
}
}
What I would like to get marshalled instead is just the value
-object:
{
"what": "what",
"when": "2014-06-09T15:56:46Z"
}
Here is my JSON marshalling config (part of the spring context configuration):
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonMapper" />
<property name="supportedMediaTypes" value="application/json" />
</bean>
<bean id="jacksonMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ss'Z'" />
<property name="timeZone">
<bean class="java.util.TimeZone" factory-method="getTimeZone">
<constructor-arg type="java.lang.String" value="UTC" />
</bean>
</property>
</bean>
</property>
</bean>
I am hoping that this can be accomplished by configuring the ObjectMapper
. I guess alternatively rolling out my own serializer may work. Thoughts? Suggestions?
回答1:
You can register a mixin annotation for the JAXBElement class which would put the @JsonValue annotation on the JAXBElement.getValue() method making its return value to be the JSON representation. Here is an example:
An example .xsd chema file that are given to xjc
.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="item" type="Thing"/>
<xs:complexType name="Thing">
<xs:sequence>
<xs:element name="number" type="xs:long"/>
<xs:element name="string" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
A Java main class:
public class JacksonJAXBElement {
// a mixin annotation that overrides the handling for the JAXBElement
public static interface JAXBElementMixin {
@JsonValue
Object getValue();
}
public static void main(String[] args) throws JAXBException, JsonProcessingException {
ObjectFactory factory = new ObjectFactory();
Thing thing = factory.createThing();
thing.setString("value");
thing.setNumber(123);
JAXBElement<Thing> orderJAXBElement = factory.createItem(thing);
System.out.println("XML:");
JAXBContext jc = JAXBContext.newInstance(Thing.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(orderJAXBElement, System.out);
System.out.println("JSON:");
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(JAXBElement.class, JAXBElementMixin.class);
System.out.println(mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(orderJAXBElement));
}
}
Output:
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<item>
<number>123</number>
<string>value</string>
</item>
JSON:
{
"number" : 123,
"string" : "value"
}
回答2:
For an example of a fully configured project that:
- Uses solely Spring (v4.0.5) for content negotiation and marshaling.
- Uses JAXB to generate the object representation of the responses.
- Supports both XML and JSON content negotiation.
- Honors the JAXB annotations when marshaling JSON responses.
- Avoids leaking the
JAXBElement
-wrapper around the response object.
head over here.
The essential pieces are:
- A Jackson mixin, which allows Jackson to unwrap the response object from the
JAXBElement
-wrapper prior to marshaling. - Spring context configuration which configures the JSON object mapper to use Jackson, and configures said mapper to take advantage of the mixin and use an
AnnotationIntrospectorPair
introspector (note that the page is a little out of date) which configures the JAXB annotation introspector as the primary introspector (makes sure the responses conform to what the XSD prescribes) and the Jackson one as the secondary (ensures theJAXBElement
unwrapping mixin is in play).
The Mixin:
/**
* Ensures, when the Jackson {@link ObjectMapper} is configured with it, that
* {@link JAXBElement}-wrapped response objects when serialized as JSON documents
* do not feature the JAXBElement properties; but instead the JSON-document that
* results in marshalling the member returned by the {@link JAXBElement#getValue()}
* call.
* <p>
* More on the usage and configuration options is available
* <a href="http://wiki.fasterxml.com/JacksonMixInAnnotations">here</a>.
*/
public interface JaxbElementMixin {
@JsonValue
Object getValue();
}
The Spring context config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.0.xsd">
<context:component-scan base-package="io.github.gv0tch0.sotaro"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="ignoreAcceptHeader" value="false" />
<property name="useJaf" value="false" />
<property name="defaultContentType" value="application/xml" />
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml" />
<entry key="json" value="application/json" />
</map>
</property>
</bean>
<bean id="typeFactory" class="com.fasterxml.jackson.databind.type.TypeFactory" factory-method="defaultInstance" />
<bean id="jaxbIntrospector" class="com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector">
<constructor-arg ref="typeFactory" />
</bean>
<bean id="jacksonIntrospector" class="com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector" />
<bean id="annotationIntrospector" class="com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair">
<!-- Note that in order to get the best of both the JAXB annotation instrospector and the Mixin configuration
the JAXB introspector needs to be the primary introspector, hence it needs to stay at position 0. -->
<constructor-arg index="0" ref="jaxbIntrospector" />
<constructor-arg index="1" ref="jacksonIntrospector" />
</bean>
<bean id="jacksonMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="annotationIntrospector" ref="annotationIntrospector" />
<!-- The mixin ensures that when JAXBElement wrapped responses are marshalled as JSON the
JAXBElement "envelope" gets discarded (which makes our JSON responses conform to spec). -->
<property name="mixInAnnotations">
<map key-type="java.lang.Class" value-type="java.lang.Class">
<entry key="javax.xml.bind.JAXBElement" value="io.github.gv0tch0.sotaro.JaxbElementMixin" />
</map>
</property>
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ss'Z'" />
<property name="timeZone">
<bean class="java.util.TimeZone" factory-method="getTimeZone">
<constructor-arg type="java.lang.String" value="UTC" />
</bean>
</property>
</bean>
</property>
</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonMapper" />
<property name="supportedMediaTypes" value="application/json" />
</bean>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="supportJaxbElementClass" value="true" />
<property name="contextPath" value="io.github.gv0tch0.sotaro" />
</bean>
<bean id="xmlConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml" />
</bean>
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters register-defaults="false">
<ref bean="xmlConverter" />
<ref bean="jsonConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
来源:https://stackoverflow.com/questions/24124149/how-to-json-marshal-jaxbelement-wrapped-responses-without-the-jaxbelement-wrappe