如何在没有JAXBElement包装的情况下JSON编组JAXBElement包装的响应?

时间:2014-06-09 16:03:49

标签: java json spring jackson jaxbelement

我有一个使用Spring(v4.0.5)的http服务。它的http端点是使用Spring Web MVC配置的。响应是由模式生成的JAXB2-anotated类。响应包含在JAXBElement中,因为生成的JAXB类没有运行@XmlRootElement注释(并且模式不能修改为医生)。我不得不fight a bit获得XML编组工作;无论如何,它正在发挥作用。

现在我正在设置JSON编组。我遇到的是获取具有JAXBElement“信封”的JSON文档。

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

我想要编组的只是value - 对象:

{
  "what": "what",
  "when": "2014-06-09T15:56:46Z"
}

这是我的JSON编组配置(弹簧上下文配置的一部分):

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

我希望通过配置ObjectMapper可以实现这一点。我想也可以推出我自己的序列化器。思考?建议?

2 个答案:

答案 0 :(得分:9)

您可以为JAXBElement类注册mixin annotation,它会将@JsonValue注释放在JAXBElement.getValue()方法上,使其返回值为JSON表示。这是一个例子:

提供给xjc的示例.xsd chema文件。

<?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>

Java主类:

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));
    }
}

输出:

XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<item>
    <number>123</number>
    <string>value</string>
</item>
JSON:
{
  "number" : 123,
  "string" : "value"
}

答案 1 :(得分:3)

有关完全配置项目的示例:

  • 仅使用Spring(v4.0.5)进行内容协商和编组。
  • 使用JAXB生成响应的对象表示。
  • 支持XML和JSON内容协商。
  • 在编组JSON响应时尊重JAXB注释。
  • 避免在响应对象周围泄漏JAXBElement - 包装。

前往here

基本要素是:

  • 杰克逊 mixin ,它允许杰克逊在编组之前从JAXBElement - 包装器中解开响应对象。
  • Spring上下文配置,它配置JSON对象映射器以使用Jackson,并配置所述映射器以利用 mixin 并使用AnnotationIntrospectorPair introspector(注意,页面有点过时了)它将JAXB注释introspector配置为主要的introspector(确保响应符合XSD规定的内容),将Jackson作为次要内容(确保JAXBElement展开 mixin 正在播放中。

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();
}

Spring上下文配置

<?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>