11

I have got a sample class:

class Zoo {
    public Collection<? extends Animal> animals;
}

When serialized with MOXy, I am getting:

{
    "bird": [
        {
            "name": "bird-1",
            "wingSpan": "6 feets",
            "preferredFood": "food-1"
        }
    ],
    "cat": [
        {
            "name": "cat-1",
            "favoriteToy": "toy-1"
        }
    ],
    "dog": [
        {
            "name": "dog-1",
            "breed": "bread-1",
            "leashColor": "black"
        }
    ]
}

Why is it using array indicators "[]", while bird, cat, and dog are not arrays? Second, is there a way to get rid of "bird", "cat", and "dog"?

In other words, I am trying to get to:

{
        {
            "name": "bird-1",
            "wingSpan": "6 feets",
            "preferredFood": "food-1"
        }
    ,
        {
            "name": "cat-1",
            "favoriteToy": "toy-1"
        }
    ,
        {
            "name": "dog-1",
            "breed": "bread-1",
            "leashColor": "black"
        }
}

Thanks, Behzad

bdoughan
  • 147,609
  • 23
  • 300
  • 400
Behzad Pirvali
  • 764
  • 3
  • 10
  • 28

1 Answers1

10

QUESTION #1

Why is it using array indicators "[]", while bird, cat, and dog are not arrays?

To get this JSON representation you have mapped your model with the @XmlElementRef annotation which tells JAXB to use the value of the @XmlRootElement annotation as the inheritance indicator. With MOXy's JSON binding these become keys. We make the value of these keys JSON arrays since keys are not allowed to repeat.

Zoo

In your model you have the @XmlElementRef annotation on your animals field/property.

import java.util.Collection;
import javax.xml.bind.annotation.XmlElementRef;

class Zoo {
    @XmlElementRef
    public Collection<? extends Animal> animals;
}

Animal

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({Bird.class, Cat.class, Dog.class})
public abstract class Animal {

    private String name;

}

Bird

On each of your subclasses you have an @XmlRootElement annotation.

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Bird extends Animal {

    private String wingSpan;
    private String preferredFood;

}

input.json/Output

{
   "bird" : [ {
      "name" : "bird-1",
      "wingSpan" : "6 feets",
      "preferredFood" : "food-1"
   } ],
   "cat" : [ {
      "name" : "cat-1",
      "favoriteToy" : "toy-1"
   } ],
   "dog" : [ {
      "name" : "dog-1",
      "breed" : "bread-1",
      "leashColor" : "black"
   } ]
}

For More Information


QUESTION #2

Second, is there a way to get rid of "bird", "cat", and "dog"?

You are going to need some sort of inheritance indicator to represent the various subclasses.

OPTION #1 - @XmlDescriminatorNode/@XmlDescriminatorValue

Here I do this using MOXy's @XmlDescriminatorNode/@XmlDescriminatorValue annotations.

Zoo

import java.util.Collection;

class Zoo {
    public Collection<? extends Animal> animals;
}

Animal

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({Bird.class, Cat.class, Dog.class})
@XmlDiscriminatorNode("@type")
public abstract class Animal {

    private String name;

}

Bird

import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;

@XmlDiscriminatorValue("bird")
public class Bird extends Animal {

    private String wingSpan;
    private String preferredFood;

}

input.json/Output

{
   "animals" : [ {
      "type" : "bird",
      "name" : "bird-1",
      "wingSpan" : "6 feets",
      "preferredFood" : "food-1"
   }, {
      "type" : "cat",
      "name" : "cat-1",
      "favoriteToy" : "toy-1"
   }, {
      "type" : "dog",
      "name" : "dog-1",
      "breed" : "bread-1",
      "leashColor" : "black"
   } ]
}

For More Information

OPTION #2 - @XmlClassExtractor

ClassExtractor (AnimalExtractor)

You can write some code that will determine the appropriate subclass based on the JSON content.

import org.eclipse.persistence.descriptors.ClassExtractor;
import org.eclipse.persistence.sessions.*;

public class AnimalExtractor extends ClassExtractor {

    @Override
    public Class extractClassFromRow(Record record, Session session) {
        if(null != record.get("@wingSpan") || null != record.get("@preferredFood")) {
            return Bird.class;
        } else if(null != record.get("@favoriteToy")) {
            return Cat.class;
        } else {
            return Dog.class;
        }
    }

}

Animal

The @XmlClassExtractor annotation is used to specify the ClassExtractor.

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({Bird.class, Cat.class, Dog.class})
@XmlClassExtractor(AnimalExtractor.class)
public abstract class Animal {

    private String name;

}

Bird

Due to how MOXy processes the @XmlElement and @XmlAttribute annotations, any of the data you want to be made available to the ClassExtractor will need to be annotated with @XmlAttribute.

import javax.xml.bind.annotation.XmlAttribute;

public class Bird extends Animal {

    @XmlAttribute
    private String wingSpan;

    @XmlAttribute
    private String preferredFood;

}

input.json/Output

{
   "animals" : [ {
      "wingSpan" : "6 feets",
      "preferredFood" : "food-1",
      "name" : "bird-1"
   }, {
      "favoriteToy" : "toy-1",
      "name" : "cat-1"
   }, {
      "breed" : "bread-1",
      "leashColor" : "black",
      "name" : "dog-1"
   } ]
}

For More Information


DEMO CODE

The following demo code can be used with both of the mappings described above.

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);
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Zoo.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/forum14210676/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);
    }

}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Thank You so much for your reply. I am getting an exception during de-serialization. I was not able to update the post. I have added this information as an answer. Thanks for your help! – Behzad Pirvali Jan 09 '13 at 20:12
  • @BehzadPirvali - Could you post the issue you are seeing as a new question? – bdoughan Jan 09 '13 at 20:22
  • OK, Sure I will post it as new question – Behzad Pirvali Jan 09 '13 at 20:28
  • 1
    I have just posted it as a new question, Thanks – Behzad Pirvali Jan 09 '13 at 20:41
  • @BehzadPirvali - I have posted an answer: http://stackoverflow.com/a/14246467/383861 – bdoughan Jan 09 '13 at 21:08
  • @BlaiseDoughan - Thanks for this clear and good example . But I would like to know how can I access the value in the parent element (say a field in `Zoo` inside `ClassExtractor` ? I have posted this question at http://stackoverflow.com/questions/26253383/access-parent-json-element-in-eclipselink-moxy-s-classextractor too. Thanks – Ken Chan Oct 08 '14 at 09:27