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?
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"
}
For an example of a fully configured project that:
JAXBElement
-wrapper around the response object.head over here.
The essential pieces are:
JAXBElement
-wrapper prior to marshaling.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 the JAXBElement
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With