Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating more json like json from JAXB and Jersey

I work with a datamodel created using JAXB, from that I can generate XML directly

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#" xmlns:ext="http://musicbrainz.org/ns/ext#-2.0">
<artist-list offset="0" count="1">
   <artist ext:score="100" type="Group" id="4302e264-1cf0-4d1f-aca7-2a6f89e34b36">       
       <name>Farming Incident</name>
       <ipi-list>
          <ipi>1001</ipi>
       </ipi-list>
   </artist>
</artist-list>
</metadata>

and with the help of Jersey also generate JSon using Natural notation

"artist-list":
    {"offset":0,
     "count":1,
     "artist":[
         {"score":"100",
          "type":"Group",
          "id":"4302e264-1cf0-4d1faca7-2a6f89e34b36",
          "name":"Farming Incident",
          "ipi-list":
              {
                  "ipi":[
                       "1001"
                    ]
             }
          }]
     }

The Xml is fine, the json is nearly fine except that because Json directly supports arrays having elements like ipi-list and artist-list doesnt seem very json, is it possible to generate more json like json from my model ?

Additional Information as Requested The json is generated from this MMD schema http://svn.musicbrainz.org/mmd-schema/trunk/brainz-mmd2-jaxb/src/main/resources/musicbrainz_mmd-2.0.xsd using JAXB and Jersey , see http://svn.musicbrainz.org/search_server/trunk/servlet/src/main/java/org/musicbrainz/search/servlet/mmd2/ResultsWriter.java and http://svn.musicbrainz.org/search_server/trunk/servlet/src/main/java/org/musicbrainz/search/servlet/mmd2/ArtistWriter.java

The point is that I want to be able to generate Json and XML from one schema with the minimum of fuss, but apparently the Json doesn't look right so Im looking for a way to improve it (I don't really have any experience of json myself)

like image 567
Paul Taylor Avatar asked May 22 '12 09:05

Paul Taylor


3 Answers

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

You could leverage the JSON-Binding and external mapping document in EclipseLink JAXB (MOXy) to support your use case.

External Mapping File (oxml.xml)

You can use the @XmlPath(".") extension in MOXy to flatten parts of your object model. Specify a path of "." tells MOXy to include the referenced object in the parent node.

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum10699038">
    <java-types>
        <java-type name="Metadata">
            <java-attributes>
                <xml-element java-attribute="artistList" xml-path="."/>
            </java-attributes>
        </java-type>
        <java-type name="Artist">
            <java-attributes>
                <xml-element java-attribute="ipiList" xml-path="."/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

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

Demo

The code below populates the object model from your XML document, and then marshalled to JSON. It demonstrates how to leverage the external mapping file and put MOXy in JSON mode.

package forum10699038;

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 {
        // READ FROM XML
        JAXBContext jcXML = JAXBContext.newInstance(Metadata.class);

        File xml = new File("src/forum10699038/input.xml");
        Unmarshaller unmarshaller = jcXML.createUnmarshaller();
        Metadata metadata = (Metadata) unmarshaller.unmarshal(xml);

        // WRITE TO JSON
        Map<String, Object> properties = new HashMap<String, Object>(3);
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum10699038/oxm.xml");
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        JAXBContext jcJSON = JAXBContext.newInstance(new Class[] {Metadata.class}, properties);

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

}

Output

{
   "artist" : [ {
      "id" : "4302e264-1cf0-4d1f-aca7-2a6f89e34b36",
      "type" : "Group",
      "score" : "100",
      "name" : "Farming Incident",
      "ipi" : [ "1001" ]
   } ]
}

MOXy and Jersey

You can easily use MOXy as your JSON provider in a JAXB-RS environment such as Jersey:

  • http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html

OTHER FILES

Below are versions of your files I created to make sure everything worked properly.

input.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#" xmlns:ext="http://musicbrainz.org/ns/ext#-2.0">
    <artist-list offset="0" count="1">
        <artist ext:score="100" type="Group"
            id="4302e264-1cf0-4d1f-aca7-2a6f89e34b36">
            <name>Farming Incident</name>
            <ipi-list>
                <ipi>1001</ipi>
            </ipi-list>
        </artist>
    </artist-list>
</metadata>

Metadata

package forum10699038;

import javax.xml.bind.annotation.*;

@XmlRootElement
public class Metadata {

    @XmlElement(name="artist-list")
    ArtistList artistList;

}

ArtistList

package forum10699038;

import java.util.List;

public class ArtistList {

    private List<Artist> artist;

}

Artist

package forum10699038;

import javax.xml.bind.annotation.*;

@XmlType(propOrder={"name", "ipiList"})
public class Artist {

    @XmlAttribute
    private String id;

    @XmlAttribute
    private String type;

    @XmlAttribute(namespace="http://musicbrainz.org/ns/ext#-2.0")
    private String score;

    @XmlElement(name="ipi-list")
    private IPIList ipiList;

    private String name;

}

IPList

package forum10699038;

import java.util.List;

public class IPIList {

    private List<String> ipi;

}

package-info

@XmlSchema( 
    namespace = "http://musicbrainz.org/ns/mmd-2.0#", 
    elementFormDefault = XmlNsForm.QUALIFIED,
    xmlns={
        @XmlNs(prefix="", namespaceURI = "http://musicbrainz.org/ns/mmd-2.0#")
    }
) 
@XmlAccessorType(XmlAccessType.FIELD)
package forum10699038;

import javax.xml.bind.annotation.*;
like image 167
bdoughan Avatar answered Oct 22 '22 14:10

bdoughan


The JSON being created by Jersey is an exact JSOON representation of that model provided on the site. The problem you are facing here is that the site is providing an awkward data model, not that the framework is not doing the right thing.

Why does this service return an object of type artist-list instead of returning a list of artists? Why does the service have an ipi-list object also? The real question you should be asking should be, how should this be modeled to work better with all technologies.

like image 36
stevedbrown Avatar answered Oct 22 '22 15:10

stevedbrown


I personally don't like annotations too much. Have a habit of generating JSON/XML in plain code. :)

For example with Jackson (Gson also similar):

mapper = new ObjectMapper();
JsonNode root = mapper.createObjectNode();

JsonNode artist = mapper.createObjectNode();
artist.put("score", "100");
root.put("artist-list", artist);

ArrayNode ipiList = mapper.createArrayNode();
ipi.add("1001");
artist.put("ipi-list", ipiList);

It may looks a lot of work on surface. But for me this is very clear way of mapping JSON to objects. Basically having toJson() method in entity classes is my usual practice. Here is an example: https://github.com/richardzcode/metrics/blob/master/src/main/java/com/rz/metrics/core/entities

It is just me though.

For JAXB I believe you need to annotate your entity like below:

@XmlElement(name = "ipi-list")
private List<Ipi> ipi;
like image 24
Richard Avatar answered Oct 22 '22 13:10

Richard