Search code examples
javajsonjsonserializer

Is there any Java support for JSPON serialization with references?


I'm looking for a Java JSPON serializer that can handle references according to the JSPON specification.

Are there any available that can do this currently? Or is there any way to modify an existing serializer to handle object refs with the $ref notation?


Solution

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

    If you are interested in an object-JSON binding approach, below is how it could be done using MOXy. The example below is based on the example one from the JSPON Core Spec:

    Parent

    The Parent class is the domain object that corresponds to the root of the JSON message. It has two fields that are of type Child.

    package forum9862100;
    
    import javax.xml.bind.annotation.*;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Parent {
    
        protected Child field1;
        protected Child field2;
    
    }
    

    Child

    The Child class may be referenced by its key. We will handle this use case with an XmlAdapter. We link to an XmlAdapter via the @XmlJavaTypeAdapter annotation.

    package forum9862100;
    
    import javax.xml.bind.annotation.*;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    
    @XmlJavaTypeAdapter(ChildAdapter.class)
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Child {
    
        protected String id;
        protected String foo;
        protected Integer bar;
    
    }
    

    ChildAdapter

    Below is the implementation of the XmlAdapter. This XmlAdapter is stateful which means we will need to set an instance on the Marshaller and Unmarshaller.

    package forum9862100;
    
    import java.util.*;
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    
    public class ChildAdapter extends XmlAdapter<ChildAdapter.AdaptedChild, Child>{
    
        private List<Child> childList = new ArrayList<Child>();
        private Map<String, Child> childMap = new HashMap<String, Child>();
    
        public static class AdaptedChild extends Child {
            @XmlElement(name="$ref")
            public String reference;
        }
    
        @Override
        public AdaptedChild marshal(Child child) throws Exception {
            AdaptedChild adaptedChild = new AdaptedChild();
            if(childList.contains(child)) {
                adaptedChild.reference = child.id;
            } else {
                adaptedChild.id = child.id;
                adaptedChild.foo = child.foo;
                adaptedChild.bar = child.bar;
                childList.add(child);
            }
            return adaptedChild;
        }
    
        @Override
        public Child unmarshal(AdaptedChild adaptedChild) throws Exception {
            Child child = childMap.get(adaptedChild.reference);
            if(null == child) {
                child = new Child();
                child.id = adaptedChild.id;
                child.foo = adaptedChild.foo;
                child.bar = adaptedChild.bar;
                childMap.put(child.id, child);
            }
            return child;
        }
    
    }
    

    Demo

    The code below demonstrates how to specify a stateful XmlAdapter on the Marshaller and Unmarshaller:

    package forum9862100;
    
    import java.io.File;
    import javax.xml.bind.*;
    import javax.xml.transform.stream.StreamSource;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Parent.class);
    
            StreamSource json = new StreamSource(new File("src/forum9862100/input.json"));
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            unmarshaller.setProperty("eclipselink.media-type", "application/json");
            unmarshaller.setProperty("eclipselink.json.include-root", false);
            unmarshaller.setAdapter(new ChildAdapter());
            Parent parent = (Parent) unmarshaller.unmarshal(json, Parent.class).getValue();
    
            System.out.println(parent.field1 == parent.field2);
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.setProperty("eclipselink.media-type", "application/json");
            marshaller.setProperty("eclipselink.json.include-root", false);
            marshaller.setAdapter(new ChildAdapter());
            marshaller.marshal(parent, System.out);
        }
    
    }
    

    Output

    Below is the output from running the demo code. Note how the two instances of Child pass the identity test.

    true
    {
       "field1" : {
          "id" : "2",
          "foo" : "val",
          "bar" : 4
       },
       "field2" : {
          "$ref" : "2"
       }}
    

    For More Information