Serialize XML "choice" element with Jackson

Viewed 325

We have an XSD of this form:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0.0">
    <xs:element name="SomeEndpoint">
        <xs:complexType>
            <xs:choice>
                <xs:element name="RequestPayload">
                    <!-- snip -->
                </xs:element>
                <xs:element name="ResponsePayload">
                    <!-- snip -->
                </xs:element>
                <xs:element name="ErrorPayload">
                    <!-- snip -->
                </xs:element>
            <xs:choice>
        </xs:complexType>
    </xs:element>
</xs:schema>

Never mind if we like it; it's given. So for instance, for a request, we'd get XML of the form

<SomeEndpoint>
    <RequestPayload>
        <!-- snip -->
    </RequestPayload>
</SomeEndpoint>

For our own sanity, we want to have three POXOs, so to speak:

@JsonTypeName("RequestPayload")
class SomeEndpointRequestPayload {
}

@JsonTypeName("ResponsePayload")
class SomeEndpointResponsePayload {
}

@JsonTypeName("ErrorPayload")
class SomeEndpointErrorPayload {
}

So far, so good -- we can, using Jackon 2.11.0 with jackson-dataformat-xml:2.11.2, serialize the "real" objects just fine. FWIW, we use a mapper configured like so:

public XmlMapper payloadMapper() {
    var xmlMapper = new XmlMapper();
    xmlMapper.registerModule(new JavaTimeModule());
    xmlMapper.registerModule(new JaxbAnnotationModule());
    return xmlMapper;
}

Now we "only" need that wrapping tag, SomeEndpoint. Things we've tried:

  • Root Path: We thought that it might be possible to inject a custom "root path" for a POXO via @JsonRootName or @JacksonXmlRootElement. We tried passing names like SomeEndpoint.RequestPayload or SomeEndpoint/RequestPayload; no luck.

  • Wrapper Object: This seems the most natural solution, but we struggled not getting an additional wrapping tag named after the property. In particular, this variant is not accepted by Jackson:

    @JsonRootName(value = "SomeEndpoint")
    class SomeEndpointWrapper {
      @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
      @JsonUnwrapped
      Object payload;
    }
    

    This one still outputs a <payload> tag:

    @JsonRootName(value = "SomeEndpoint")
    class SomeEndpointWrapper {
       @XmlElements({
         @XmlElement(name="RequestPayload", type=SomeEndpointRequestPayload.class),
         @XmlElement(name="ResponsePayload", type=SomeEndpointResponsePayload.class),
         @XmlElement(name="ErrorPayload", type=SomeEndpointErrorPayload.class)
       })
       Object payload;
    }
    

    We've tried too many mixes of annotations to count, or reproduce here, but these two seemed like "should work".

    Note bene: We've been using a marker interface instead of Object to restrict values to the three types. Since it doesn't seem to make a difference to Jackson, I've left it out for the sake of simplicity.

  • Workaround: We can have one property per choice option:

    @Data
    @JsonRootName(value = "SomeEndpoint")
    class SomeEndpointWrapper {
      @JsonProperty(value = "Request")
      private SomeEndpointRequestPayload request;
    
      @JsonProperty(value = "Antwort")
      private SomeEndpointResponsePayload successResponse;
    
      @JsonProperty(value = "Fehlerinformation")
      private SomeEndpointErrorPayload errorResponse;
    }
    

    This works, but ... nah, really?

What's a good way to implement an XML choice in POXOs without creating spurious wrapping tags?

0 Answers
Related