Question

Is it possible to achieve un-/marshalling of a class structure that utilizes nesting of several abstract classes?
Given a class structure like this:

@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Animal {}
public abstract class Mammal extends Animal {}
public class Tiger extends Mammal {}
public class Elephant extends Mammal {}

The @XmlRootElemented Zoo class has a list of animals:

@XmlElementWrapper(name = "animals")
@XmlElements({
    @XmlElement(name = "elephant", type = Elephant.class),
    @XmlElement(name = "tiger", type = Tiger.class)
})
private List<Animal> animals;

I think you get the idea ... the XML for this:

<?xml version="1.0" encoding="UTF-8"?>
<zoo>
   <animals>
      <tiger>
         <name>Richard</name>
         <furry>true</furry>
      </tiger>
      <elephant>
         <name>Otis</name>
         <furry>false</furry>
      </elephant>
      <tiger>
         <name>Kirk</name>
         <furry>true</furry>
      </tiger>
   </animals>
</zoo>

This looks fine, cool.
Now the JSON ...

 {
    "animals" : {
       "tiger" : [ {
          "name" : "Richard",
          "furry" : true
       }, {
          "name" : "Kirk",
          "furry" : true
       } ],
       "elephant" : [ {
          "name" : "Otis",
          "furry" : false
       } ]
    }
 }

Why does it subgroup the Mammal class objects in JSON?

Im using EclipseLink MOXy 2.6 for marshalling.

Was it helpful?

Solution

ORIGINAL ANSWER

MOXy groups the keys tiger and elephant to avoid repeating them.


UPDATE #1

So it not possible to get a JSON like { 'animals': [ {'@type': 'tiger'}, {'@type': elephant'}, ... ] }?

Yes it's possible, you just need to map it that way:

Zoo

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Zoo {

    private List<Animal> animals;

}

Animal

import javax.xml.bind.annotation.*;

@XmlSeeAlso({Elephant.class, Tiger.class})
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Animal {

}

Demo

import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "@");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Zoo.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/forum19384491/input.json");
        Zoo zoo = unmarshaller.unmarshal(json, Zoo.class).getValue();

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

}

input.json/Output

{
   "animals" : [ {
      "@type" : "tiger"
   }, {
      "@type" : "elephant"
   }, {
      "@type" : "tiger"
   } ]
}

UPDATE #2

If you want to keep your current XML representation and just change the JSON representation you could use MOXy's external mapping document extension (see: http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html)

Mapping Document (oxm.xml)

We will use MOXy's external mapping document to change the mapping for the animals field on the Zoo class.

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum19384491">
    <java-types>
        <java-type name="Zoo">
            <java-attributes>
                <xml-element java-attribute="animals"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

In the demo code below we create 2 instances of JAXBContext on the same domain model. The one for JSON leverages an external mapping document to customize the mapping. input.xml is the XML document from your question.

import java.io.File;
import java.util.*;
import javax.xml.bind.*;

import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext xmlJC = JAXBContext.newInstance(Zoo.class);

        Unmarshaller unmarshaller = xmlJC.createUnmarshaller();
        File xml = new File("src/forum19384491/input.xml");
        Zoo zoo = (Zoo) unmarshaller.unmarshal(xml);

        Map<String, Object> properties = new HashMap<String, Object>(4);
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum19384491/oxm.xml");
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "@");
        JAXBContext jsonJC = JAXBContext.newInstance(new Class[] {Zoo.class}, properties);

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

}

Output

Below is the output from running the demo code.

{
   "animals" : [ {
      "@type" : "tiger",
      "name" : "Richard",
      "furry" : true
   }, {
      "@type" : "elephant",
      "name" : "Otis",
      "furry" : false
   }, {
      "@type" : "tiger",
      "name" : "Kirk",
      "furry" : true
   } ]
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top