jackson-dataformat-xml icon indicating copy to clipboard operation
jackson-dataformat-xml copied to clipboard

Each list element is wrapped into an extra tag with the name of the list property (RFE: combine type id, property name)

Open repolevedavaj opened this issue 8 years ago • 15 comments

I am implementing a Java class for calling an external API with uses XML as data format. The API provider provides for each Endpoint the corresponding XML schema. I used the XJC compiler to create schematically compliant Java classes. Now I have the problem, that the object mapper does wrap list elements into extra tags and therefore, the API call is rejected.

XML schema:

<xs:element name="body" minOccurs="0">
  <xs:complexType>
    <xs:sequence minOccurs="1" maxOccurs="unbounded">
      <xs:element name="transaction" minOccurs="0">...</xs:element>
      <xs:element name="error" type="error" minOccurs="0"/>
      <xs:element type="xs:string" name="errorEmail" minOccurs="0">...</xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:element>

Java class:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "transactionAndErrorAndErrorEmail"
})
public static class Body {
    @XmlElements({
        @XmlElement(name = "transaction", type = Transaction.class),
        @XmlElement(name = "error", type = Error.class),
        @XmlElement(name = "errorEmail", type = String.class)
    })
    protected List<Object> transactionAndErrorAndErrorEmail;
    ...
}

Object Mapper Configuration:

 final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
                .xml()
                .modules(new JaxbAnnotationModule())
                .defaultUseWrapper(false)
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .build();

Generated XML:

<body >
  <transactionAndErrorAndErrorEmail>
    <transaction>
      ...
    </transaction>
  </transactionAndErrorAndErrorEmail>
  <transactionAndErrorAndErrorEmail>
    <error>
      ...
    </error>
  </transactionAndErrorAndErrorEmail>
</body>

What I expected:

<body >
    <transaction>
      ...
    </transaction>
    <error>
      ...
    </error>
</body>

I am working with Jackson version 2.8.8, but I tested it also with version 2.9.0.pr2, but without success.

repolevedavaj avatar Apr 24 '17 10:04 repolevedavaj

I have the same issue using Jackson annotations :

public class XmlMapperTest {

    private XmlMapper mapper = create();

    private XmlMapper create() {
        XmlMapper xmlMapper = new XmlMapper();
        return xmlMapper;
    }


    @Test
    public void shouldNotWrap() throws JsonProcessingException {
        Channel test = new Channel();
        test.items.add(new Pen());
        test.items.add(new Pen());
        test.items.add(new Pencil());
        System.out.println(mapper.writeValueAsString(test));
    }

    public class Channel {
        @JacksonXmlElementWrapper(localName = "items")
        public final List<Item> items = new ArrayList<Item>();
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Pen.class, name = "pen"),
            @JsonSubTypes.Type(value = Pencil.class, name = "pencil")}
    )
    public abstract class Item {    }
    public class Pen extends Item {    }
    public class Pencil extends Item {    }

}

produces

<Channel>
    <items>
        <items>
            <pen/>
        </items>
        <items>
            <pen/>
        </items>
        <items>
            <pencil/>
        </items>
    </items>
</Channel>

but I am expecting

<Channel>
    <items>
            <pen/>
            <pen/>
            <pencil/>
    </items>
</Channel>

It seems to be regression as I have found fixed bugs related to that : https://github.com/FasterXML/jackson-module-jaxb-annotations/issues/51 https://github.com/FasterXML/jackson-dataformat-xml/issues/178 https://github.com/FasterXML/jackson-dataformat-xml/issues/159 https://github.com/FasterXML/jackson-dataformat-xml/issues/197

If there is an error in my code - please give me an advice where.

Regards

grzesuav avatar Jul 02 '18 21:07 grzesuav

I have tested on Jackson version 2.9.6 for the record

grzesuav avatar Jul 02 '18 21:07 grzesuav

This is specifically occurring with polymorphic lists -- the abstract class's "localName" is written even if a more specific subtype is provided via @XmlElements or @JsonSubTypes. Still happening in 2.9.8.

nortonwong avatar Mar 18 '19 16:03 nortonwong

The only workaround at the moment seems to be writing a custom serializer or deserializer for the owner of the list, e.g. Body or Channel.

nortonwong avatar Mar 18 '19 16:03 nortonwong

In case anybody is here looking for a work around, I ended up using something like this:

public class CustomListSerializer extends StdSerializer<List<MyClass>> {

    public CustomListSerializer() {
        this(null);
    }

    public CustomListSerializer(Class<List<MyClass>> t) {
        super(t);
    }
    @Override
    public void serialize(
            List<MyClass> value, JsonGenerator jgen, SerializerProvider provider) throws
            IOException, JsonProcessingException {
        jgen.writeStartObject();
        for (MyClass item: value) {
            jgen.writeNullField(item.getValue());
        }
        jgen.writeEndObject();
    }
}

and annotated the attribute this way:

   @JsonSerialize(using = CustomListSerializer.class)
   @JacksonXmlElementWrapper(useWrapping = false)
   @JsonProperty("Items")
   private List<MyClass> myList = new ArrayList<>();

hopefully it helps someone.

vortexkd avatar Jan 29 '20 06:01 vortexkd

Had same issue. Fixed it with the annotations. Key was putting both wrapper AND the property annotation on the list. No custom serializers needed:

@JacksonXmlElementWrapper(localName = "FooList")
@JacksonXmlProperty(localName = "Foo")
List<Foo> fooList;

cherlo avatar Apr 28 '20 23:04 cherlo

For non-polymorphic types, annotations mentioned above should work to either include or not property name -- this is a feature, not bug.

Further, just because xjc generates some definition does not mean Jackson necessarily can use classes: XML module's contract is quite simple:

"jackson-dataformat-xml should be able to READ what it WRITEs -- it does not necessarily map all arbitrary XML structures into Java types; or support all possible Java structures (for example, Lists of Lists are NOT supported)".

So just because Jackson produces different XML structure than original poster wanted is not in itself considered a flaw (although is incompatibility with JAXB/xjc -- Jackson is not a JAXB implementation). Criteria would be whether Jackson can successfully write specific Java value and read it back.

Second: if specific output structure is desired, this may be a valid request for enhancement. For that I would need full test class and code (code included is almost there).

cowtowncoder avatar Apr 29 '20 00:04 cowtowncoder

One other note: Jackson does not "compress" polymorphic types, and as such values as serialized are considered working as expected. Whether improvement could be implemented to omit certain levels is uncertain.

cowtowncoder avatar Apr 29 '20 00:04 cowtowncoder

Any news on this? I am currently trying to call an API that uses a XML based query language where you have something like:

    <filter>
        <and>
            <equalto>
               <field>WHENMODIFIED</field>
               <value>05/14/2020 17:09:33</value>
             </equalto>
        </and>
    </filter>

so I have a And and EqualTo class. (and a base class FilterField). But EqualTo class has

@JacksonXmlElementWrapper(useWrapping = false)
List<FilterField> field;

but for some reason the actual output is (similar to other people here):

    <filter>
        <and>
           <filter>
               <equalto>
                  <field>WHENMODIFIED</field>
                  <value>05/14/2020 17:09:33</value>
                </equalto>
             </filter>
        </and>
    </filter>

Instead of just picking the type name as defined as part of my JsonSubTypes it wraps it in the propertyName of my And class.

fkrauthan avatar May 15 '20 00:05 fkrauthan

@fkrauthan This is not enough to reproduce the issue you have, or know what is going on, without full class definitions: for example is FilterField polymorphic (has @JsonTypeInfo) or not. As such this may be same, related or different issue.

It would make sense to file a separate issue with your details.

cowtowncoder avatar May 15 '20 00:05 cowtowncoder

Hey hey @cowtowncoder , I came here today following a trail of issues starting in 2018. I've created a test that describes the expectation me and seemingly other users have and the actual outcome.

Note that the test and my following comments are to explain the issue and the feature/change request, even though I understand that the configuration provided may not be the one to actually yield the expected output, going forward.

The current issue here stems from the processing and interpretation of JsonTypeInfo.As.WRAPPER_OBJECT

If we compare to the json format, this means that a list such as

[{"some": "property", "type": "typeName"}]

gets transformed to

[{"typeName": {"some": "property"}}]

and in json landspace, this makes totally sense, as there is no other way to name this object.

Now, in xml landspace, every element inside an array is a actually a named element and and it would be nice to be able to rename this element instead of introducing a new wrapper element inside of it.

The current implementation generates

<Person>
	<hobbies>
		<hobbies>
			<reading>
				<favoriteBook>moby dick</favoriteBook>
			</reading>
		</hobbies>
		<hobbies>
			<biking>
				<bikeType>mountain bike</bikeType>
			</biking>
		</hobbies>
	</hobbies>
</Person>

where the inner hobbies is the equivalent to our unnamed json object, instead of the more desired

<Person>
	<hobbies>
		<reading>
			<favoriteBook>moby dick</favoriteBook>
		</reading>
		<biking>
			<bikeType>mountain bike</bikeType>
		</biking>
	</hobbies>
</Person>

This issue currently seems to mix two topics. People trying to achive the more natural xml representation i described, try to use @JsonSubTypes that don't yield the desired output. People coming from JAXB expect the @XMLElements annotation to work as described, but it seems like it simply isn't supported and should be evaluated, only when the above concludes.

goatfryed avatar Sep 21 '20 17:09 goatfryed

@goatfryed while I appreciate your adding more information, the problem is not I do not understand what users ideally would want to have -- it is that this conflation is not possible at this point: technically concept of property name and type id MUST be separate by Jackson handlers; there is no way around that.

I have left this issue open since at some point someone might be able to figure out how to support such combination; but at this point there is no plan.

cowtowncoder avatar Sep 21 '20 21:09 cowtowncoder

I thought so already, but it wasn't obvious to me what's the state of this issue. Maybe it will help other users to understand and make a decision whether they it fits their needs.

But just to clarify a bit more: Is the difficulty you're describing more within deserialization of such structures or both parts?

goatfryed avatar Sep 22 '20 05:09 goatfryed

@goatfryed I am not sure what you mean by "both parts"? The problem is specifically with separation of type id deserialization (with TypeDeserializer) and value deserialization (main JsonDeserializer): the two interact so that conceptually you can think of JsonDeserializer being wrapped within TypeDeserializer for polymorphic use cases. Actually it gets bit more complicated than this: for POJOs (BeanDeserializer, a JsonDeserializer implementation) maps property names to value deserializers, which are potentially wrapped by TypeDeserializer. While there are multiple inclusion styles they are designed to map to JSON-style structure; and then there is no style that would flatten property name and type id into one.

It would be easy enough to add a new type id inclusion mechanism, say JsonTypeInfo.As.FLATTENED_PROPERTY (or whatever), but that would require essentially using "type id" value as "property name" by BeanDeserializer (and TypeDeserializer using it too, but that'd probably not be a big problem).

So it is the bundling of conceptually separate (but in case of XML/JAXB, combined/bundled) identitifiers (one for property, one for subtype id) that has no handling in jackson-databind.

cowtowncoder avatar Sep 22 '20 17:09 cowtowncoder