FasterXML / jackson-dataformat-xml

Extension for Jackson JSON processor that adds support for serializing POJOs as XML (and deserializing from XML) as an alternative to JSON
Apache License 2.0
568 stars 222 forks source link

@JacksonXmlElementWrapper as a @JsonCreator parameter #149

Open dmikurube opened 9 years ago

dmikurube commented 9 years ago

Do you have a plan to make @JacksonXmlElementWrapper specifiable as a parameter?

I'm wondering to use Jackson-dataformat-XML to deserialize to a immutable class instance, but @JacksonXmlElementWrapper is not available as a @JsonCreator parameter. We do a similar approach for JSON by specifying @JsonProperty on @JsonCreator.

For example:

{
  "type": "TYPE",
  "labels": [ "foo", "bar" ]
}

<body>
  <type>TYPE</type>
  <labels><label>foo</label><label>bar</label></labels>
</body>

@JacksonXmlRootElement(localName="body")
public class Body {
    @JsonCreator
    public Body(@JsonProperty("type")
                @JacksonXmlProperty("type")
                String type,
                @JsonProperty("labels")
                @JacksonXmlElementWrapper(localName="labels")
                @JacksonXmlProperty(localName="label")
                Collection<String> labels) {
        // ...
    }

    @JsonProperty("type")
    @JacksonXmlProperty("type")
    public String getType() { return this.type; }

    @JsonProperty("labels")
    @JacksonXmlElementWrapper(localName="labels")
    @JacksonXmlProperty("label")
    public ImmutableList<String> getLabels() { return this.labels; }

    private final String type;
    private final ImmutableList<String> labels;
}
cowtowncoder commented 9 years ago

Usage should already work, as far as recognizing annotation goes.

However.... whether it works with respect to possible reordering needed for use with @JsonCreator I don't know -- there's a good chance it won't. If it does not, it is likely it will not be possible to make it work, since buffering mechanism would not work well with structural changes needed to support optional wrapping of elements.

So: if this usage does not work (which I assume is the case since you filed the issue :) ), I would suggest only passing non-collection properties via Creator, and using field or setter for collection properties. This will work, and while not optimal, it is unlikely that passing of collection via creator for XML will be supported.

dmikurube commented 9 years ago

Thank you for the reply. Hmm, sad news... Just adding ElementType.PARAMETER on JacksonXmlElementWrapper didn't work.

Since I don't want to make the instance mutable (non-final), I'm giving it a try to hack jackson-dataformat-xml and jackson-databind. Will send a PR when it succeeds. :)

cowtowncoder commented 9 years ago

Right, the problem is not regarding ability to add the annotation there, or even getting it observed by Jackson. Rather, it is because handling of Creator parameters is very different from regular simple properties: since object does not yet exist, parameters typically need to be buffered. And the way XML content is supported requires a few tweaks to handling of token stream, and these are not easy to handle through buffering process.

tboettch commented 9 years ago

Any updates here? I am running into a similar issue. Interestingly, the code below worked with Jackson 2.5.4, but it crashes in 2.6.2:

package com.example.test;

import java.util.Arrays;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class Main {

    static class Bean {
        private final List<String> values;

        @JsonCreator
        public Bean(@JacksonXmlProperty(localName="value") List<String> values) {
            this.values = values;
        }

        @JacksonXmlElementWrapper(useWrapping=false)
        @JacksonXmlProperty(localName="value")
        public List<String> getValues() {
            return values;
        }
    }

    public static void main(String[] args) throws Exception {
        XmlMapper mapper = new XmlMapper();
        JacksonXmlModule module = new JacksonXmlModule();
        module.setDefaultUseWrapper(false);
        mapper.registerModule(module);

        Bean original = new Bean(Arrays.asList("foo", "bar"));
        String serialized = mapper.writeValueAsString(original);
        System.out.println(serialized);
        Bean roundTrip = mapper.readValue(serialized, Bean.class);
        System.out.println(roundTrip.getValues());
    }
}
tboettch commented 9 years ago

Also, if I change the property to not use a custom localName, it works fine even in 2.6.2:

static class Bean {
        private final List<String> values;

        @JsonCreator
        public Bean(@JacksonXmlProperty(localName="values") List<String> values) {
            this.values = values;
        }

        @JacksonXmlElementWrapper(useWrapping=false)
        public List<String> getValues() {
            return values;
        }
    }
cowtowncoder commented 9 years ago

As to the original question: no, it is not possible to make @JsonCreator also contain element wrapper information. There are multiple reasons why this would not work, from conceptual separation (@JsonCreator is generic, dataformat-agnostic annotation; xml annotations are only defined for xml usage), to differing arity (@JsonCreator relates to a set of one or more properties, whereas wrapper information is always related to one specific property -- although @JsonCreator may also be used for "delegating" use case, in which case there is no actual property specified at all).

I would strongly recommend not trying to use combination of Lists and @JsonCreator with XML module, since I am not confident they can made to work together reliably. I realize that it is great to be able to use a constructor for creating immutable objects, but sometimes defining a private setter (or field) just works better (Jackson can access those without problems, but code can not directly call them).

cowtowncoder commented 3 years ago

One change wrt annotations in 2.12: @JacksonXmlElementWrapper is allowed on (constructor) parameters. Will see if I can check whether use itself would work or not.

cowtowncoder commented 3 years ago

Looks like this will not work with 2.12 either, despite now being able to specify wrapper annotation. Problem is with matching of property itself. Interestingly enough it is possible to make deserialization "work" by making both property and wrapper names as labels (since inner element name is not verified), but this will make serialization wrong (will, as expected, use labels for both, which is not intention here).

ghost commented 1 year ago

Hello, colleagues. Is there any news? Has anyone succeeded in making @JacksonXmlElementWrapper with @JsonCreator work? It's 2.14.1 now, and I don't see any improvements with this. Am I missing something?

cowtowncoder commented 1 year ago

@syroeshko I don't think there has been any work on this. It is quite likely that the problem has to do with buffering of XML tokens required by use of Constructor for properties, and there is no clear path on how this can be resolved (although it is now possible for XmlMapper to provide its token TokenBuffer implementation).