Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshalling nested objects to a "flat" XML structure

Tags:

java

xml

jaxb

How might I marshall an object hierarchy such that, instead of having the component objects becoming nested XML elements, their properties become direct children of the root element with their names prefixed by their type.

For example, given:

                                                    (A)
public class Customer {

    protected String firstName;
    protected String lastName;
    protected Address address;
}

public class Address {

    protected String street;
    protected String city;
}

Using the usual JAXB annotations would result in

                                                    (B)
<customer>
    <firstName>Juan</firstName>
    <lastName>dela Cruz</lastName>
    <address>
        <street>123 Rizal Avenue</street>
        <city>Manila</city>
    </address>
</customer>

But, instead, I need to marshall the same as

                                                    (C)
<customer>
    <firstName>Juan</firstName>
    <lastName>dela Cruz</lastName>
    <address_street>123 Rizal Avenue</address_street>
    <address_city>Manila</address_city>
</customer>

It would be great if there were some JAXB incantation to solve my needs since I'm already using JAXB for most of the things around this problem. In fact, these present some constraints to my specific situation:

  1. The Java classes in (A) are generated by JAXB from an existing schema that corresponds to the XML structure in (B). I would prefer not to maintain modified versions of the generated classes.
  2. I do not own or maintain the said schema. The actual schema is quite large and is often subject to minor modifications. It would be tedious to come up with and maintain an equivalent schema. Also, to keep up with schema modifications, I rely on the automated class generation by JAXB.
  3. If it makes things easier, nesting is only up to one level deep. In the example, Address would not contain any other complex type.

I'm looking at the @XmlPath annotation from MOXy, but I can't figure out how to get the node names prefixed as in (C).

I dream of a solution where some xjc customizations providing the right JAXB annotations get me going, but that's looking unlikely from my searches so far. Any non-JAXB solution would suffice.

like image 239
Edward Samson Avatar asked Nov 24 '22 21:11

Edward Samson


1 Answers

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

You can use the external mapping file in EclipseLink JAXB (MOXy) to apply a second mapping to your object model, and the @XmlPath extension to flatten the object model.

External Mapping File (oxm.xml)

The following is what the external mapping file might look like if your model classes where in a package called forum11007814.

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum11007814"
    xml-accessor-type="FIELD">
    <java-types>
        <java-type name="Customer">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="address" xml-path="."/>
            </java-attributes>
        </java-type>
        <java-type name="Address">
            <java-attributes>
                <xml-element java-attribute="street" name="address_street"/>
                <xml-element java-attribute="city" name="address_city"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

Below is code that demonstrates how to leverage MOXy's external mapping file when creating the JAXBContext.

package forum11007814;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String,Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum11007814/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        File xml = new File("src/forum11007814/c.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

jaxb.properties

To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry.

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

c.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Juan</firstName>
   <lastName>dela Cruz</lastName>
   <address_street>123 Rizal Avenue</address_street>
   <address_city>Manila</address_city>
</customer>
like image 170
bdoughan Avatar answered Dec 14 '22 22:12

bdoughan