Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
repolevedavaj opened this issue Apr 24, 2017 · 14 comments

Comments

@repolevedavaj
Copy link

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.

@grzesuav
Copy link

grzesuav commented Jul 2, 2018

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 :
FasterXML/jackson-module-jaxb-annotations#51
#178
#159
#197

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

Regards

@grzesuav
Copy link

grzesuav commented Jul 2, 2018

I have tested on Jackson version 2.9.6 for the record

@nortonwong
Copy link

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

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.

@vortexkd
Copy link

vortexkd commented Jan 29, 2020

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.

@cherlo
Copy link

cherlo commented Apr 28, 2020

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;

@cowtowncoder
Copy link
Member

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
Copy link
Member

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.

@fkrauthan
Copy link

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.

@cowtowncoder
Copy link
Member

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

@goatfryed
Copy link

goatfryed commented Sep 21, 2020

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.

@cowtowncoder
Copy link
Member

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

@goatfryed
Copy link

goatfryed commented Sep 22, 2020 via email

@cowtowncoder
Copy link
Member

@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 cowtowncoder changed the title Each list element is wrapped into an extra tag with the name of the list property Each list element is wrapped into an extra tag with the name of the list property (RFE: combine type id, property name) Nov 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants