I am developing some classes that are going to be serialized in an XML 
format (which does not have an XSD available to auto-generate code from), 
and have been trying to work with Jackson to handle the XML part.  There 
are a few different output formats, which have some boilerplate attributes 
shared among them at the root level.  I would like to avoid duplicating 
code in all my classes to represent those common properties.  Moving the 
common properties to a superclass and using inheritance would be one way to 
get this to work, but that seems wrong from a OO perspective.  What I would 
really like to do is define some additional class that contains the shared 
properties, and then have an instance of that class as a field in each of 
the "real" classes, and then use some kind of XML unwrapping on that field, 
so the shared items appear on the enclosing object.

A co-worker far more familiar with Jackson than I am suggested that I tag 
that field with @JsonUnwrapped, but I have been having some unexpected 
results. Before diving down into the example below, are there are any 
known/expected limitations on how XML would get unwrapped using this 
annotation?


Examples:

If I need something like the target output:

<Root xmlns="urn:parentNS" xmlns:childNS="urn:childNS" 
ParentAttribute="parentAttrValue" 
childNS:ChildAttribute="childAttrValue">
   <ParentElement>parentElemValue</ParentElement>
</Root>

Where Root is one of the output document types I need, with ChildAttribute 
being 
an item that also needs to appear in other output document types, I create 
a class Child.java as 

=======================
Child.java
=======================
package test;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class Child {
    @JacksonXmlProperty(namespace = "urn:childNS", localName = 
"ChildAttribute", isAttribute = true)
    private String childAttribute = null;

    public String getChildAttribute() {
        return childAttribute;
    }

    public void setChildAttribute(String childAttribute) {
        this.childAttribute = childAttribute;
    }
}
=======================

And then create Root.java as 

=======================
Root.java
=======================
package test;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

@JacksonXmlRootElement(namespace = "urn:parentNS", localName = "Root")
public class Root {

    @JacksonXmlProperty(namespace = "urn:parentNS", localName = 
"ParentAttribute", isAttribute = true)
    private String parentAttribute = null;

    @JacksonXmlProperty(namespace = "urn:parentNS", localName = 
"ParentElement")
    private String parentElement = null;

    @JsonUnwrapped
    private Child child = null;
    
    public String getParentAttribute() {
        return parentAttribute;
    }

    public void setParentAttribute(String parentAttribute) {
        this.parentAttribute = parentAttribute;
    }

    public String getParentElement() {
        return parentElement;
    }

    public void setParentElement(String parentElement) {
        this.parentElement = parentElement;
    }
    
    public Child getChild() {
        return child;
    }

    public void setChild(Child child) {
        this.child = child;
    }
}
=======================

Using a simple main to test it out:

=======================
Main.java
=======================
package test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

public class Main {
    
    public static void main(String[] args ) {
        Root rc = new Root();
        rc.setParentAttribute("parentAttrValue");
        rc.setParentElement("parentElemValue");
        
        Child cc = new Child();
        cc.setChildAttribute("childAttrValue");
        
        XmlMapper mapper = new XmlMapper();
        try {
            String xml = mapper.writeValueAsString(rc);
            System.out.println(xml);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}
=======================

results in XML output for which the ParentElement element is serialized as 
an *attribute *on the Root element, even though it's not marked with 
isAttribute 
= true.  Why is ParentElement getting handled like this?

<Root xmlns="urn:parentNS" xmlns:childNS="urn:childNS" 
ParentAttribute="parentAttrValue" 
childNS:ChildAttribute="childAttrValue" ParentElement="parentElemValue />


My coworker later suggested also adding @JacksonXmlProperty(isAttribute = 
true) to the shared property, which *seems *to fix the problem.  That is, 
updating Root.java with 

=======================
Root.java
=======================
...
    @JsonUnwrapped
    @JacksonXmlProperty(isAttribute = true)
    private Child child = null;
...
=======================
results in

<Root xmlns="urn:parentNS" xmlns:childNS="urn:childNS" 
ParentAttribute="parentAttrValue" 
childNS:ChildAttribute="childAttrValue">
   <ParentElement>parentElemValue</ParentElement>
</Root>

Except something else is now happening under the hood, because if I were 
to, say, add an element to the Child class, as 

=======================
Child.java
=======================
package test;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class Child {
...
    @JacksonXmlProperty(namespace = "urn:childNS", localName = 
"ChildElement")
    private String childElement = null;
...
    public String getChildElement() {
        return childElement;
    }

    public void setChildElement(String childElement) {
        this.childElement = childElement;
    }
}
=======================

and modify Main.java

=======================
Main.java
=======================
...
        cc.setChildElement("childElemValue");
...
=======================

now ChildElement gets serialized as an attribute, even as ParentElement is 
still handled correctly.

<Root xmlns="urn:parentNS" xmlns:childNS="urn:childNS" 
ParentAttribute="parentAttrValue" 
childNS:ChildAttribute="childAttrValue" 
childNS:ChildElement="childElemValue">
   <ParentElement>parentElemValue</ParentElement>
</Root>

I won't add more sample code here, but if I change the ChildAttribute 
property to also be an element, things work as expected again, with the two 
properties of Child appearing as two subelements to Root.  So it seems like 
using an additional class this way only works if the shared properties are 
all attributes (and the JacksonXmlProperty annotation is used in 
conjunction with JsonUnwrapped), or the shared properties are all elements 
(in which case JsonUnwrapped alone gets the job done).

Is all that behavior expected, or am I outside the realm of what Jackson is 
meant to do for XML?

-- 
You received this message because you are subscribed to the Google Groups 
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to jackson-user+unsubscr...@googlegroups.com.
To post to this group, send email to jackson-user@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to