Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB for lists to be returned naturally for JSON or XML

Tags:

java

json

xml

moxy

I'm using MOXy with Jersey to implement a RESTful API and want to return lists naturally for JSON and XML, by which I mean that the XML contains an element tag for the overall collection as well as the collection items, whereas the JSON contains a tag for the collection only.

For example, I want to return an "organisation" resource with nested lists of facilities and locations. As XML:

<organisation>
  <id>1</id>
  <name>XYZ</name>
  <facilities>
    <facility>
      <id>1</id>
      <text>Telephone</text>
    </facility>
    <facility>
      <id>3</id>
      <text>Whiteboard</text>
    </facility>
  </facilities>
  <locations>
    <location>
      <id>1</id>
      <kind>REGION</kind>
      <name>London</name>
    </location>
    <location>
      <id>2</id>
      <kind>REGION</kind>
      <name>Manchester</name>
    </location>
  </locations>
</organisation>

And as JSON:

{
  "id": 1,
  "name": "XYZ",
  "facilities": [
    {
      "id": 1,
      "text": "Telephone"
    },
    {
      "id": 3,
      "text": "Whiteboard"
    }
  ],
  "locations": [
    {
      "id": 1,
      "kind": "REGION",
      "name": "London"
    },
    {
      "id": 2,
      "kind": "REGION",
      "name": "Manchester"
    }
  ]
}

Unfortunately, I don't seem to be able to get a code base that allows me to return the output like this for both XML and JSON. If I use a class to wrap the nested list, then the XML appears correct but not the JSON (see "ExternalFacilities" below). If I define the nested list as an ArrayList sub-class then the JSON appears correct but not the XML (see "ExternalLocations" below).

XML sample showing "facilities" list correct but not "locations"

See that there is no XML element that wraps the "locations" list (as there is for "facilities"), and also that each location has a pluralised element name.

<organisation>
  <id>1</id>
  <name>XYZ</name>
  <facilities>
    <facility>
      <id>1</id>
      <text>Telephone</text>
    </facility>
    <facility>
        <id>3</id>
        <text>Whiteboard</text>
    </facility>
  </facilities>
  <locations>
    <id>1</id>
    <kind>REGION</kind>
    <name>London</name>
  </locations>
  <locations>
    <id>2</id>
    <kind>REGION</kind>
    <name>Manchester</name>
  </locations>
</organisation>

JSON sample showing "locations" list correct but not "facilities"

See that the "facilities" list is a JSON object containing a JSON array, whereas I just want the JSON array (with the pluralised element name).

{
  "id": 1,
  "name": "XYZ",
  "facilities": {
    "facility": [
        {
            "id": 1,
            "text": "Telephone"
        },
        {
            "id": 3,
            "text": "Whiteboard"
        }
    ]
  },
  "locations": [
    {
      "id": 1,
      "kind": "REGION",
      "name": "London"
    },
    {
      "id": 2,
      "kind": "REGION",
      "name": "Manchester"
    }
  ]
}

This samples above are produced using the same code, just changing the Accept HTTP header to have Jersey return JSON instead of XML. Here is an excerpt of the classes:

ExternalOrganisation.java

@XmlRootElement(name="organisation")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExternalOrganisation {

   private String             name;
   private ExternalFacilities facilities;
   private ExternalLocations  locations;

   ...

}

ExternalFacilities.java

@XmlRootElement(name="facilities")
public class ExternalFacilities {
  @XmlElementRef
  private List<ExternalFacility> list;
  ...
}

ExternalLocations.java

@XmlRootElement(name="locations")
public class ExternalLocations extends ArrayList<ExternalLocation> {
  ...
}

ExternalFacility.java

@XmlRootElement(name="facility")
@XmlType(propOrder={"id", "uri", "kind", "text"})
public class ExternalFacility extends ExternalBase {
    // id inherited
    private String text;
    ....
}

ExternalLocation.java

@XmlRootElement(name="location")
@XmlType(propOrder={"id", "kind", "name"})
public class ExternalLocation extends ExternalBase {
    // id inherited
    @XmlElement private LocationKind kind;
    @XmlElement private String       name;
    ...
}

Seems at first similar to this question but I'm not trying to mix the types of object in my lists.

like image 381
Scott Avatar asked Jan 02 '14 19:01

Scott


1 Answers

You could do the following to get the desired XML and JSON representations:

Step #1 - Leverage @XMLElementWrapper

Instead of:

@XmlRootElement(name="organisation")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExternalOrganisation {

   private String             name;
   private ExternalFacilities facilities;
   private ExternalLocations  locations;

   ...

}

You could do the following with @XmlElementWrapper (see: http://blog.bdoughan.com/2010/09/jaxb-collection-properties.html):

@XmlRootElement(name="organisation")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExternalOrganisation {

   private String             name;

   @XmlElementWrapper
   @XmlElementRef
   private List<ExternalFacility> facilities;

   @XmlElementWrapper
   @XmlElementRef
   private List<ExternalLocation>  locations;

   ...

}

Step #2 - Leverage MOXy's Wrapper as Array Name Property

By specifying the wrapper as array name property, MOXy will use the value from @XmlElementWrapper as the JSON array name.

import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;

public class YourApplication  extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>(1);
        set.add(YourService.class);
        return set;
    }

    @Override
    public Set<Object> getSingletons() {
        MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
        moxyJsonProvider.setWrapperAsArrayName(true);

        HashSet<Object> set = new HashSet<Object>(1);
        set.add(moxyJsonProvider);
        return set;
    }

} 

For More Information

  • http://blog.bdoughan.com/2013/03/binding-to-json-xml-handling-collections.html
  • http://blog.bdoughan.com/2013/06/moxy-is-new-default-json-binding.html
like image 180
bdoughan Avatar answered Oct 20 '22 23:10

bdoughan