Search code examples
javajsonspringjacksonjaxbelement

How to JSON-marshal JAXBElement-wrapped responses without the JAXBElement wrapper?


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?


Solution

  • 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"
    }