Server

Creating Your Own Marshaller

Updated: November 15, 2024

The marshalling service allows you to create JSON-to-Java and Java-to-JSON marshallers. To create a marshaller, you have to write a Java class that manages the marshalling and registers it using a contribution.

Let's suppose we'd like to create marshallers for the following Java object:

 public class Product {

    private String reference;
    public int getReference() { return reference; }
    public void setReference(int reference) { this.reference = reference; }

    private String description;
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }

}

Creating a Java-to-JSON Marshaller

A Java-to-JSON marshaller must implement the org.nuxeo.ecm.core.io.registry.Writer<EntityType> class. It must have a @Setup annotation to register it as a marshaller and a @Supports annotation to specify the supported mimetype (JSON in our example). The Product class is a POJO, we can either use Jackson POJO marshalling capabilities or create a custom JSON marshalling.

Jackson POJO

 @Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonWriter extends AbstractJsonWriter<Product> {
    public void write(Product product, JsonGenerator jg) throws IOException {
        jg.writeObject(product);
    }
}

Custom JSON

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonWriter extends AbstractJsonWriter<Product> {
    public void write(Product product, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeNumberField("ref", product.getReference());
        jg.writeStringField("desc", product.getDescription());
        jg.writeEndObject();
    }
}

  • The @Setup annotation is here to define our object as a marshaller.
  • The mode = Instanciations.SINGLETON parameter defines that the marshaller must be instanciated once.
  • The priority = Priorities.REFERENCE parameter defines it as the default one for the Product class.
  • The AbstractJsonWriter super-class is the Java-to-JSON Marshaller's base class (it has the @Supports("application/json") annotation). It helps to write the marshaller and to reuse the existing JSON marshalling.

Creating a JSON-to-Java Marshaller

A JSON-to-Java marshaller must implement the org.nuxeo.ecm.core.io.registry.Reader<EntityType> class. It must have a @Setup annotation to register it as a marshaller and a @Supports annotation to specify the supported mimetype (JSON in our example). The Product class is a POJO, we can either use Jackson POJO marshalling capabilities or create a custom JSON marshalling.

Jackson POJO

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonReader extends AbstractJsonReader<Product> {
    public Product read(JsonNode jn) throws IOException {
        return new ObjectMapper().readValue(jn, Product.class);
    }
}

Custom JSON

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductJsonReader extends AbstractJsonReader<Product> {
    public Product read(JsonNode jn) throws IOException {
        Product product = new Product();
        product.setReference(jn.get("reference").getIntValue());
        product.setDescription(jn.get("description").getTextValue());
        return product;
    }
}

 

  • The @Setup annotation is here to define our object as a marshaller.
  • The mode = Instanciations.SINGLETON parameter defines that the marshaller must be instanciated once.
  • The priority = Priorities.REFERENCE parameter defines it as the default one for the Product class.
  • The AbstractJsonReader super-class is the JSON-to-Java Marshaller's base class (it has the @Supports("application/json") annotation). It helps to write the marshaller and to reuse the existing JSON marshalling.

Each marshaller supports injection of the RenderingContext or any Nuxeo service using the @java.inject.Inject annotation. The injected objects are inherited while extending an existing class. The AbstractJsonWriter and the AbstractJsonReader provide the RenderingContext ("ctx" attribute) and the MarshallingRegistry ("registry" attribute).

Managing Lists

The Nuxeo Platform provides built-in abstract classes to manage lists.

Java-to-JSON

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductListJsonWriter extends DefaultListJsonWriter<Product> {
    public ProductListJsonWriter() {
        super("products", Product.class);
    }
}

JSON-to-Java

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class ProductListJsonReader extends DefaultListJsonReader<Product> {
    public ProductListJsonReader() {
        super("products", Product.class);
    }
}

Registering Your Marshallers

The marshalling service provides an extension point : org.nuxeo.ecm.core.io.MarshallerRegistry/marshallers . It allows to add new marshallers, override existing ones or unregister some.

marshallers-contrib.xml

<?xml version="1.0"?>
<component name="org.nuxeo.example.marshallers">
  <extension target="org.nuxeo.ecm.core.io.MarshallerRegistry" point="marshallers">
    <register class="org.nuxeo.example.ProductJsonWriter" enable="true" />
    <register class="org.nuxeo.example.ProductJsonReader" enable="true" />
    <register class="org.nuxeo.example.ProductListJsonWriter" enable="true" />
    <register class="org.nuxeo.example.ProductListJsonReader" enable="true" />
  </extension>
</component>

Using Your Marshallers

  • From a JAX-RS endpoint

    1. Make sure your WebEngine application provides the nuxeo-core-io marshalling.

      If it extends automation server or REST API v1, it's ok.

      Otherwise, you have to register a single JAX-RS object in your application.

      public class ProductApplication extends WebEngineModule {
          public Set<Object> getSingletons() {
              return Sets.newHashSet(new JsonCoreIODelegate());
          }
      }
      
    2. From your REST endpoints, just consume or return Product as usual.

      Using marshallers

      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public Product doGet() {
          Product product;
          // get the product
          return product;
      }
      
      @POST
      @Consumes(MediaType.APPLICATION_JSON)
      public Response doPost(Product product) {
          // do something with the product
          return Response.ok().build();
      }
      

  • Outside JAX-RS

    You can use the Nuxeo Platform marshalling service outside the web context. The simplest way is to use org.nuxeo.ecm.core.io.registry.MarshallerHelper.

    MarshallerHelper - Java-to-JSON

    Product product = ...;
    String json = MarshallerHelper.objectToJson(product, CtxBuilder.get());
    

    The MarshallerHelper class is a helper to use the MarshallingRegistry service. This service provides multiple ways to get marshallers depending on your needs.

    MarshallingRegistry - Java-to-JSON

    Product product = ...;
    OutputStream target = ...;
    
    MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class);
    
    // get the most priorized JSON Writer for Product
    Writer<Product> writer = registry.getWriter(CtxBuilder.get(), Product.class, MediaType.APPLICATION_JSON_TYPE);
    writer.write(object, Product.class, Product.class, APPLICATION_JSON_TYPE, target);
    
    // get a specific JSON Writer instance
    ProductJsonWriter writer = registry.getInstance(CtxBuilder.get(), ProductJsonWriter.class);
    writer.write(object, Product.class, Product.class, APPLICATION_JSON_TYPE, target);
    
    // get the most priorized JSON Writer for Product
    // associated with the given context and available for use in multiple threads (usefull for use in listeners or schedulers)
    Writer<Product> writer = registry.getUniqueWriter(CtxBuilder.get(), Product.class, Product.class, MediaType.APPLICATION_JSON_TYPE);
    writer.write(object, Product.class, Product.class, APPLICATION_JSON_TYPE, target);
    
    // get a specific JSON Writer instance
    // associated with the given context and available for use in multiple threads (usefull for use in listeners or schedulers)
    ProductJsonWriter writer = registry.getUniqueInstance(CtxBuilder.get(), ProductJsonWriter.class);
    writer.write(object, Product.class, Product.class, APPLICATION_JSON_TYPE, target);
    
    // advanced use for Java class based on generic type
    List<Product> products = ...;
    // use TypeUtils from commons-lang3 to get the generic type
    Type productListType = TypeUtils.parametrize(List.class, Product.class)
    Writer<List<Product>> writer = registry.getWriter(CtxBuilder.get(), List.class, productListType, MediaType.APPLICATION_JSON_TYPE);
    writer.write(object, List.class, productListType, APPLICATION_JSON_TYPE, target);
    

  • From another marshaller: The easiest way to do that is to extends AbstractJsonWriter or AbstractJsonReader.

    Java-to-JSON Aggregation

    @Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
    public class AnObjectJsonWriter extends AbstractJsonWriter<AnObject> {
        public void write(AnObject anObject, JsonGenerator jg) throws IOException {
            jg.writeStartObject();
            jg.writeStringField("someField", anObject.getSomeField());
    
            Product product = anObject.getProduct();
    
            // this will generates the product json using the provided Java-to-JSON marshaller
            writeEntityField("product", product, jg);
            // same things with 2 calls
            jg.writeFieldName("product");
            writeEntity(product, jg);
    
            // or even, use the inherited "registry" attribute to get your marshaller and do what you want
    
            jg.writeEndObject();
        }
    }