Question

I like to build a rest service with xml and json input and output. The xml output should valid and defined in a xsd schema using namespace. Using xml the service works well. For json i havn't found a solution to achieve a consistent input and output. To illustrate my problem i have done a small example. I created two classes with jaxb annotations Parent and Child. The parent has a @XmlAttribute and a list of child objects. The rest service can generate two example parent objects. One example including one child and the other example with two children. Because there are some differnce between the notations having a list with one or more members. Additionally the service has a @POST method which receives a parent object and returns it directly. My assumption is to put the generated json text into the post method and get the same notation as before. But this doesn't work for the most cases.

My rest service:

@Path("parent")
public class ParentResource {

    @GET
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Parent generateExample(){
        Parent father = new Parent();
        father.setName("peter");

        Child john = new Child();
        john.setName("john");

        father.getChildren().add(john);

        return father;
    }

    @GET
    @Path("list")
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Parent generateExample2(){
    Parent father = new Parent();
    father.setName("peter");

    Child john = new Child();
    john.setName("john");
    Child sue = new Child();
    sue.setName("sue");

    father.getChildren().add(john);
    father.getChildren().add(sue);

    return father;
    }


    @POST
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Consumes ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Parent echoEntity(Parent input){
        return input;
    }
}

Parent

@XmlAccessorType (XmlAccessType.NONE)
@XmlRootElement (name="parent")
public class Parent  implements Serializable{

   @XmlAttribute
  private String name;

  @XmlElement 
  private List<Child> children = null;

  public String getName() {
     return name;
  }

  public void setName(String name) {
     this.name = name;
  }

  public List<Child> getChildren() {
     if(children == null){
        children = new LinkedList<Child>();
     }
     return children;
  }

  public void setChildren(List<Child> children) {
     this.children = children;
  }

}

Child

@XmlAccessorType (XmlAccessType.NONE)
@XmlRootElement (name="child")
public class Child  implements Serializable {

    @XmlElement
    private String name;

    public String getName() {
       return name;
    }

    public void setName(String name) {
       this.name = name;
    }
}

package-info.java in the Parent+Child package to define namespace.

@javax.xml.bind.annotation.XmlSchema(namespace="http://myexample.com",
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED, 
    attributeFormDefault = javax.xml.bind.annotation.XmlNsForm.UNQUALIFIED)

Custom context resolver

@Provider
@Produces (MediaType.APPLICATION_JSON)
@Consumes (MediaType.APPLICATION_JSON)
public class CustomResolver implements ContextResolver<JAXBContext>{
    private final JAXBContext context;
    private final Set<Class<?>> types;
    private final Class<?>[] cTypes = {
       Parent.class, Child.class
    };

    public CustomResolver() throws Exception {
        this.types = new HashSet<Class<?>>(Arrays.asList(cTypes));


        Map<String, String> nsMap = new HashMap<String, String>();
        nsMap.put("http://myexample.com", "ex");

        JSONConfiguration config = JSONConfiguration.natural().rootUnwrapping(false).build();
//      JSONConfiguration config = JSONConfiguration.mapped().xml2JsonNs(nsMap).attributeAsElement("name").rootUnwrapping(false).build();
//      JSONConfiguration config = JSONConfiguration.mapped().attributeAsElement("name").rootUnwrapping(false).build();
//      JSONConfiguration config = JSONConfiguration.mappedJettison().build();
//      JSONConfiguration config = JSONConfiguration.badgerFish().build();

        context = new JSONJAXBContext(config, cTypes);
    }

    public JAXBContext getContext(Class<?> objectType) {
        return (types.contains(objectType)) ? context : null;
    }
}

Rest application including custom context resolver

public class RestApplication extends Application {
    private Set<Object> singletons = new HashSet<Object>();
    private Set<Class<?>> classes = new HashSet<Class<?>>();

    public RestApplication(){
        singletons.add(new ParentResource());

        classes.add(CustomResolver.class);
    }

    @Override
    public Set<Class<?>> getClasses(){
        return classes;
    }

    @Override
    public Set<Object> getSingletons(){
        return singletons;
    }
}

Test results with no custom context resolver (comment in application):

generated 1 :
{"@name":"peter","children":{"name":"john"}}
output of post method :
{"@name":"peter"}

generated 2 :
{"@name":"peter","children":[{"name":"john"},{"name":"sue"}]}
output of post method :
{"@name":"peter"}

Test results with natural notation (this notation i like to use)

generated 1 :
{"parent":{"name":"peter","children":[{"name":"john"}]}}
output of post method :
Exception: The request sent by the client was syntactically incorrect ()

generated 2 :
{"parent":{"name":"peter","children":[{"name":"john"},{"name":"sue"}]}}
output of post method :
Exception: The request sent by the client was syntactically incorrect ()

Test result with mapped notation and no namespace map

generated 1 :
{"parent":{"name":"peter","children":{"name":"john"}}}
output of post method :
The request sent by the client was syntactically incorrect ()

generated 2 :
{"parent":{"name":"peter","children":[{"name":"john"},{"name":"sue"}]}}
output of post method :
The request sent by the client was syntactically incorrect ()

Test result with mapped notation

generated 1 :
{"ex.parent":{"name":"peter","ex.children":{"ex.name":"john"}}}
output of post method :
{"ex.parent":{"name":"peter","ex.children":{"ex.name":"john"}}}

generated 2 :
{"ex.parent":{"name":"peter","ex.children":[{"ex.name":"john"},{"ex.name":"sue"}]}}
output of post method :
{"ex.parent":{"name":"peter","ex.children":[{"ex.name":"john"},{"ex.name":"sue"}]}}

wow it works. But this notation i don't like to use in javascript. The namespace prefixes are merged into the attribute names. This is not very usable to hardcode the prefixes in the javascript client when accessing the nested elements. The prefixes names could change! With jettison notations it looks worse than the examples before.

Long story but a simple question. Has somebody a solution to receive a json natural notation and be able to send the same notation to a jersey rest service?

Was it helpful?

Solution

I managed it by updating to a newer jersey version. Now i use 1.13 before it was quite old 1.1.5.1. I also have to do same changes. In the Custom context resolver i removed the annotation @Consumes (MediaType.APPLICATION_JSON) and add a new class and add it to the classes in the application class

@Provider
@Consumes (MediaType.APPLICATION_JSON)
public class NaturalJsonReader implements MessageBodyReader{

    private static final JSONConfiguration jConfig = JSONConfiguration.natural().rootUnwrapping(false).build();

    @Override
    public boolean isReadable(Class arg0, Type type, Annotation[] arg2, MediaType mt) {
        return true;
    }

    @Override
    public Object readFrom(Class arg0, Type arg1, Annotation[] arg2, MediaType arg3, MultivaluedMap arg4, InputStream arg5) throws IOException, WebApplicationException {

        try {
             JSONUnmarshaller m = new JSONJAXBContext(jConfig, arg0).createJSONUnmarshaller();
             Object o = m.unmarshalJAXBElementFromJSON(arg5, arg0).getValue(); 
             return o;
        } catch (JAXBException e) {
        }

        return null;
    }
}

After this the natural notation can be used for input and output operation of the rest services.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top