【问题标题】:How to JSON-marshal JAXBElement-wrapped responses without the JAXBElement wrapper?如何在没有 JAXBElement 包装器的情况下对 JAXBElement 包装的响应进行 JSON 编组?
【发布时间】:2014-07-30 05:19:28
【问题描述】:

我有一个使用 Spring (v4.0.5) 的 http 服务。它的 http 端点是使用 Spring Web MVC 配置的。响应是从模式生成的带有 JAXB2 注释的类。响应包含在 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-object:

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

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

<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 来实现。我想或者推出我自己的序列化程序可能会起作用。想法?有什么建议吗?

【问题讨论】:

  • 被编组的响应对象是由xjc(JAXB2 绑定编译器)从模式中生成的。您是否建议以某种方式注释这些内容?即使这以某种方式完成,这些仍然被包裹在JAXBElement-s 中,它们本身不能被@JsonValue-annotated...
  • 好的。请看我的回答。

标签: java json spring jackson jaxbelement


【解决方案1】:

对于一个完全配置的项目的示例:

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

前往here

基本部分是:

  • Jackson mixin,它允许 Jackson 在编组之前从 JAXBElement-wrapper 解包响应对象。
  • Spring 上下文配置,配置 JSON 对象映射器以使用 Jackson,并配置所述映射器以利用 mixin 并使用 AnnotationIntrospectorPair introspector(注意页面有点过时)将 JAXB 注释内省器配置为主要内省器(确保响应符合 XSD 规定的内容)和杰克逊一个作为辅助(确保 JAXBElement unwrapping 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>

【讨论】:

  • 同样的方法也可以扩展到setter。
  • 下面是一个元素的部分输出,它是另一个元素的一部分,无法正确解组,使用您的方法,父级正确转换为 JSON,但此处的子级没有。你有什么建议可以帮助我吗? ` "sale" : { "actualSalesUnitPriceOrAssociateOrDescription" : [ { "name" : "{url.url/namespace}ItemID", "declaredType" : "java.lang.String", "scope" : "javax.xml.bind.JAXBElement$全球范围”,...`
【解决方案2】:

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

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

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

【讨论】:

  • 这在大多数情况下都有效。谢谢!问题在于,混合注释似乎与 jaxb-annotation-introspector 不能很好地混合(没有双关语)。事实上,我发现这两者是一个非此即彼的命题。这意味着如果我使用 mixin 方法从 JAXBElement 信封中解开实际响应对象以进行编组,我必须放弃 jaxb-annotation-introspector 提供的所有功能 - 例如属性名称、枚举值等的编组,与它们在 XML 案例中编组的方式相同。也许有人知道如何让两者一起工作。
  • 哦,mixin-s 和 JaxbAnnotationIntrospector 可以实际上是 paired。对我来说,将 JaxbAnnotationIntrospector 设置为这对中的主要对象效果最好。
猜你喜欢
  • 1970-01-01
  • 2020-04-24
  • 2015-11-03
  • 1970-01-01
  • 2012-07-07
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多