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

@JsonUnwrapped does not work inside list #676

Open
SimonCockx opened this issue Oct 22, 2024 · 3 comments
Open

@JsonUnwrapped does not work inside list #676

SimonCockx opened this issue Oct 22, 2024 · 3 comments

Comments

@SimonCockx
Copy link
Contributor

SimonCockx commented Oct 22, 2024

@JsonUnwrapped does not unwrap items inside a list. Below is a minimal example of what I'm trying to achieve.

I'm trying to model the following simple XSD scheme in Java.

<xs:element name="document" type="Document" />
<xs:complexType name="Document">
  <xs:sequence maxOccurs="unbounded">
    <xs:element name="a" type="xs:string" />
    <xs:element name="b" type="xs:string" />
  </xs:sequence>
</xs:complexType>

There is one type Document which contains a sequence of alternating a and b elements. E.g., this is a valid XML instance:

<document>
  <a>A1</a>
  <b>B1</b>
  <a>A2</a>
  <b>B2</b>
  <a>A3</a>
  <b>B3</b>
</document>

I tried to represent this in Java using the following classes:

@JacksonXmlRootElement(localName = "document")
public class Document {
    private List<ABPair> abPairs = new ArrayList<>();

    @JsonUnwrapped
    @JacksonXmlElementWrapper(useWrapping = false)
    public List<ABPair> getABPairs() {
        return abPairs;
    }
    public Document addABPair(ABPair pair) {
        abPairs.add(pair);
        return this;
    }
}

public static class ABPair {
    private String a;
    private String b;
    
    public String getA() {
        return a;
    }
    public String getB() {
        return b;
    }

    public ABPair setA(String a) {
        this.a = a;
        return this;
    }
    public ABPair setB(String b) {
        this.b = b;
        return this;
    }
}

However, the @JsonUnwrapped does not seem to work in conjunction with useWrapping = false.
Unit test reproducing the problem:

@Test
public void test() throws JsonProcessingException {
    XmlMapper mapper = new XmlMapper();

    Document document =
            new Document()
                    .addABPair(new ABPair().setA("A1").setB("B1"))
                    .addABPair(new ABPair().setA("A2").setB("B2"))
                    .addABPair(new ABPair().setA("A3").setB("B3"));
    assertEquals("<document><a>A1</a><b>B1</b><a>A2</a><b>B2</b><a>A3</a><b>B3</b></document>", mapper.writeValueAsString(document));
}

The actual result is "<document><abpairs><a>A1</a><b>B1</b></abpairs><abpairs><a>A2</a><b>B2</b></abpairs><abpairs><a>A3</a><b>B3</b></abpairs></document>".

Is there a way to get this working in Jackson?

@cowtowncoder
Copy link
Member

I am not 100% sure if this is doable or not: but I don't thing @JsonUnwrapped will work to achieve that.

On figuring out working mapping: the usual way of figuring out compatible structure between Java types and XML output is to try to serialize POJOs in question to create compatible structure.

But... yeah. This is probably not possible currently: your attempt makes sense from user perspective, but I don't think it is possible with XML module handling as is.

@SimonCockx
Copy link
Contributor Author

SimonCockx commented Oct 25, 2024

On the serialization side, I did manage to get something working.

The problem is that IndexedListSerializer#unwrappingSerializer returns itself, which makes sense for JSON, but could be better for XML. I replaced this in the BeanSerializerFactory#buildIndexedListSerializer with a custom UnwrappableIndexedListSerializer which is exactly the same as IndexedListSerializer except that it returns something else as unwrappingSerializer. I then let it return my own UnwrappingIndexedListSerializer, which unwraps all of the items inside a list.

On the deserialization side however, things seem worse. If I look in the code, it seems Jackson does not support deserializing multiple fields with the same name based on order. In the simpler case where no lists are involved, it already seems to fail, e.g.,

    @Test
    public void test() throws JsonProcessingException {
        ObjectMapper mapper = new XmlMapper();

        Document deserialised = mapper.readValue("<document><a>A1</a><b>B1</b><a>A2</a><b>B2</b></document>", Document.class);
        // Both `myABPair1` and `myABPair2` are assigned to the second pair... `<a>A2</a><b>B2</b>`
    }

    public static class Document {
        private ABPair myABPair1 = null;
        private ABPair myABPair2 = null;

        @JsonUnwrapped
        public ABPair getMyABPair1() {
            return myABPair1;
        }
        public Document setMyABPair1(ABPair pair) {
            myABPair1 = pair;
            return this;
        }

        @JsonUnwrapped
        public ABPair getMyABPair2() {
            return myABPair2;
        }
        public Document setMyABPair2(ABPair pair) {
            myABPair2 = pair;
            return this;
        }
    }

I'm starting to wonder: is support for order-dependent deserialisation something I can customize (even though it might be hard -- I'm adventurous :)), or does this just go too deep into the Jackson stack? Any advice?

@cowtowncoder
Copy link
Member

So, yes, I can see why logically supporting @JsonUnwrapped for Lists on serialization side could make sense for XML. Unfortunately jackson-databind is supposed to be format-agnostic (although there are some minor deviations to support non-JSON formats, mostly XML, with some capability introspection; and in some cases overrides) so it's not super easy to go about that.

But as you correctly noted, the more difficult part really is deserialization, where JSON's unique names for Objects clashes with translation of XML element sequences (where repetition is common/expected).

As to order-dependant deserialization; ordering is not counted on for "Object" structured things (in JSON, from JSON Objects, in XML, translated element sequences not recognized as Arrays (... from Object model)). Or put another way, deserializers are directly responsible for reading tokens and using them, but there is no customizability exposed by annotations.

So I am not 100% sure how things could proceed here.

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

2 participants