REST API

JSON Marshalling

Updated: July 17, 2023

The Nuxeo Platform provides an extensible and customizable marshalling service: MarshallerRegistry This service allows you to use built-ins but also to register your own marshallers. The marshalling service facilitates the configuration of the rendered JSON, the aggregation of a marshaller from another and the possibility to extend or override existing marshallers.

The marshalling service is set up for the REST API v1 and the Automation server. Therefore, it is possible to operate all rendering parameters for the corresponding provided services.

The JSON generation and parsing is based on Jackson.

Creating Your Own Marshaller

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.Injectannotation. The injected objects are inherited while extending an existing class. TheAbstractJsonWriterand theAbstractJsonReaderprovide the RenderingContext ("ctx" attribute) and theMarshallingRegistry` ("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" version="1.0.0">
  <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.

    MarshallingHelper - Java-to-JSON

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

    The MarshallingHelper 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();
        }
    }
    

Parameterizing and Reusing Marshallers

The Nuxeo Platform marshalling service provides a rendering context. This context allows you to parametrize your JSON marshallers. For example you can decide to add a parameter which tells your marshaller to fetch or not some parts of the rendered object.

Usages

This context is managed by the class org.nuxeo.ecm.core.io.registry.context.RenderingContext. A helper is provided to easily create a context: org.nuxeo.ecm.core.io.registry.context.RenderingContext.CtxBuilder.

When the marshalling service is used from JAX-RS, the context is filled with:

  • The Nuxeo URL
  • The client locale
  • The given HTTP headers
  • The GET parameters
  • The Java Request attributes

If you add parametrized behaviors to your marshaller, it is then customizable by the end-user application using the corresponding parameter in headers or in GET parameters.

Parametrize the description and add the product's URL

@Setup(mode = 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());
        if (ctx.getBooleanParameter("loadProductDescription")) {
            jg.writeStringField("desc", product.getDescription());
        }
        String url = ctx.getBaseUrl() + "/productApp/product/ref/" + product.getReference();
        jg.writeStringField("url", url);
        jg.writeEndObject();
    }
}

You can send the parameters using HTTP GET parameter or HTTP header (useful for POST/PUT/DELETE results).

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/productApp/product/ref/10001?loadProductDescription=true'
$ curl -X GET -H 'loadProductDescription:true' 'http://localhost:8080/nuxeo/site/productApp/product/ref/10001'

You can also use the rendering context from the server side using the CtxBuilder.

MarshallingRegistry - Java-to-JSON

Product product = ...;
RenderindContext ctx = CtxBuilder.param("loadProductDescription", Boolean.TRUE).get();
String json = MarshallingHelper.objectToJson(product, ctx);

Finally, you can force the value of a parameter on the server side by overriding the rendering context used to produce the response.

Use marshallers

@GET
@Produces(MediaType.APPLICATION_JSON)
public Product doGet(@Context HttpServletRequest request) {
    Product product;
    // get the product ... and configure output
    RenderindContext ctx = CtxBuilder.param("loadProductDescription", Boolean.TRUE).get();
    request.setAttribute(CTX_KEY, ctx);
    return product;
}
private static final String CTX_KEY = "_STORED_GENERATED_RENDERING_CONTEXT";

RenderingContext API

Setting/Getting the Nuxeo Base URL

Web context usage Retrieved from the HTTP call
Server side usage
ctx = CtxBuilder.base("http://server/nuxeo").get();
Use from marshallers
String url = ctx.getBaseUrl();

Setting/Getting the Current Locale

Web context usage Retrieved from the end-user's browser
Server side usage
ctx = CtxBuilder.locale(Locale.FRENCH).get();
Use from marshallers
Locale locale = ctx.getLocale();

Delivering/Using a CoreSession to/in Marshallers

Web context usage Retrieved from the WebContext, or from a document or created from a nxrepository parameter or created from a X-NXrepository header.
Server side usage
CoreSession session = ...;
ctx = CtxBuilder.session(session).get();
Use from marshallers The try-resource statement ensure the session would be closed if it's necessary
try (SessionWrapper wrapper = ctx.getSession(document)) {CoreSession session = wrapper.getSession();
    }
try (SessionWrapper wrapper = ctx.getSession(null)) {CoreSession session = wrapper.getSession();
    }

Setting/Getting Any Parameter

Web context usage URL:
  • .../some-url?paramName=paramValue
HTTP Header:
  • paramName:paramValue
Server side usage
ctx = CtxBuilder.param("paramName", "paramValue").get();
ctx.setParameterValues("paramName", "paramValue");
Use from marshallers
Object o = ctx.getParameter("paramName");
List list = ctx.getParameters("paramName");


boolean b = ctx.getBooleanParameter("paramName");

Loading Some Document Properties

Web context usage URL:
  • .../some-url?properties=*
  • .../some-url?properties=dublincore,file
  • .../some-url?properties=dublincore&properties=file
HTTP Header:
  • X-NXproperties:*
  • X-NXproperties:dublincore,file
Server side usage
ctx = CtxBuilder.properties("*").get();
ctx = CtxBuilder.properties("dublincore", "file").get();
ctx.setParameterValues("properties", "dublincore", "file");
Use from marshallers
Set properties = ctx.getProperties();

Loading Additional Parts of an Object Whose entity-type Is 'objectType'

The corresponding behavior depends on the marshaller.

Web context usage URL:
  • .../some-url?fetch.objectType=part1,part2
  • .../some-url?fetch.objectType=part1&fetch.objectType=part2
HTTP Header:
  • X-NXfetch.objectType:part1,part2
Server side usage Only for documents
ctx = CtxBuilder.fetchInDoc("part1", "part2").get();
ctx = CtxBuilder.fetch("objectType", "part1", "part2").get();
ctx.setParameterValues("fetch.objectType", "part1", "part2");
Use from marshallers
Set toLoad = ctx.getFetched("objectType");

Enabling Enricher for an objectType

The enricher will contribute to the object marshalling. The object marshaller must extend ExtensibleEntityJsonWriter. The given enricher name must match an existing enricher for this object type.

Web context usage URL:
  • .../some-url?enrichers.objectType=children,acl
  • .../some-url?enrichers.objectType=children&enrichers.objectType=acl
HTTP Header:
  • X-NXenrichers.objectType:children,acl
Server side usage Only for documents
ctx = CtxBuilder.enrichDoc("children", "acl").get();
ctx = CtxBuilder.enrich("objectType", "children", "acl").get();
ctx.setParameterValues("enrichers.objectType", "children", "acl");
Use from marshallers
Set enricherToActivate = ctx.getEnrichers("objectType");

Translating Some Part of an Object Which May Contains Some l10n Key

Web context usage URL:
  • .../some-url?translate.objectType=elementToTranslate
HTTP Header:
  • X-NXtranslate.objectType:elementToTranslate
Server side usage
ctx = CtxBuilder.translate("objectType", "elementToTranslate").get();
ctx.setParameterValues("translate.objectType", "elementToTranslate");
Use from marshallers
Set toTranslate = ctx.getTranslated("objectType");

Controlling the Aggregation Depth

Available values are: root for no aggregation, children for 1 level, max for 2 levels. Default value is 'children'. See next chapter for more details

Web context usage URL:
  • .../some-url?depth=children
HTTP Header:
  • depth:children
Server side usage
ctx = CtxBuilder.depth(DepthValues.children).get();
ctx.setParameterValues("depth", "children");
Use from marshallers
try (Closeable resource = ctx.wrap().controlDepth().open()) {
 // call another marshaller
} catch (MaxDepthReachedException e) {
 // do not load properties
}

Aggregating Marshallers and Avoiding Infinite Loops

Let's take an example. We created two classes, Product and Category. And we'd like to marshall them as JSON.

When loading a category, we'd like to be able to get the corresponding products.

Custom JSON

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class CategoryJsonWriter extends AbstractJsonWriter<Category> {
    public void write(Category category, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeNumberField("ref", category.getReference());
        jg.writeStringField("desc", category.getDescription());
        if (ctx.getFetched("category").contains("products")) {
            jg.writeArrayFieldStart("products");
            for (Product product: product.getProducts()) {
                // delegates the marshalling to Nuxeo
                writeEntity(product);
            }
            jg.writeEndArray();
        }
        jg.writeEndObject();
    }
}

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/productApp/category/ref/12?fetch.category=products'

When loading a product, we'd like to be able to get the corresponding categories.

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());
        if (ctx.getFetched("product").contains("categories")) {
            jg.writeArrayFieldStart("categories");
            for (Category category: product.getCategories()) {
                // delegates the marshalling to Nuxeo
                writeEntity(category);
            }
            jg.writeEndArray();
        }
        jg.writeEndObject();
    }
}

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/productApp/category/ref/12?fetch.product=categories'

This works fine. But if we want to get both product's categories and category's products, there's a real risk to get an infinite loop. Or at least to get a very huge amount of JSON (if the category-product graph has no cycle).

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/productApp/category/ref/12?fetch.product=categories&fetch.category=products'

To manage this case, we could use a counter put in the context but this will not work in most case because the counter will manage the first "line" of marshaller and disable the others: that's due to the processing which follows a "Depth First Search".

The Nuxeo Platform provides a specific object to wrap the depth control in each "crossing line". Each time you call another marshaller, you should use it.

Custom JSON

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class CategoryJsonWriter extends AbstractJsonWriter<Category> {
    public void write(Category category, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeNumberField("ref", category.getReference());
        jg.writeStringField("desc", category.getDescription());
        // control the graph depth
        try (Closeable resource = ctx.wrap().controlDepth().open()) {
            if (ctx.getFetched("category").contains("products")) {
                jg.writeArrayFieldStart("products");
                for (Product product: product.getProducts()) {
                    // delegates the marshalling to Nuxeo
                    writeEntity(product);
                }
                jg.writeEndArray();
            }
        } catch (MaxDepthReachedException e) {
            // do not load products
        }
        jg.writeEndObject();
    }
}

The max depth is controlled by the depth parameter in the rendering context. The default value for depth is children.

Depth values: If we get a product -> its categories -> their products ...

  • root: No category will be loaded, just the first product.
  • children: The product is loaded with its categories.
  • max: The product is loaded, its categories also, and their products.

The only way to load more data is to avoid the use of depth control (which is bad!).

Each existing Nuxeo writer/enricher, which may use another marshaller, uses the depth's control.

Enriching / Extending / Overriding the Existing Marshallers

The Nuxeo Platform provides a flexible JSON marshalling. But sometimes, you'd like to add information from your Information System to the existing JSON or even override it to match your end-user needs.

There may be different needs:

  • Add information related to the marshalled object: Try to use built-in parameters, or else create an enricher.

  • Add existing informations to the JSON: Try to use built-in parameters, or else extend the existing marshaller.

  • Change the existing JSON: Override the existing marshaller.

Enriching an Existing Marshaller

The enrichment mechanism is only enabled for the existing marshallers which extend ExtensibleEntityJsonWriter . That means, almost all of them support enrichment.

An enrichable JSON object provides a contextParameters field which contains all contributions. An enricher is activated if its name is specified in the parameters (for example: enrichers.document=breadcrumb,children). The enricher must write 0, 1 or many key-value in the current JsonGenerator and must let the JsonGenerator in the same state it got it.

Enrichment's destination

{
    ... // the existing generated object
    contextParameters: {  // only present if at least one enricher is activated
        "theFirstEnricherKey": "theFirstEnricherValue",
        "theSecondEnricherKey": { ... },
        "theThirdEnricherKey-Entry1": { ... },
        "theThirdEnricherKey-Entry2": { ... }
    }
}

Let's take the breadcrumb enricher as an example. It contributes to the generated JSON for a document and adds all of the parent documents.

ChildrenJsonEnricher

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.REFERENCE)
public class BreadcrumbJsonEnricher extends AbstractJsonEnricher<DocumentModel> { // <= enrich DocumentModel Java type
    public BreadcrumbJsonEnricher() {
        super("breadcrumb"); // <= is activated if "children" is in "enrichers.document" parameter's values
    }
    public void write(JsonGenerator jg, DocumentModel document) throws IOException {
        List<DocumentModel> parentDocuments = null;
        try (SessionWrapper wrapper = ctx.getSession(document)) {
            parentDocuments = wrapper.getSession().getParentDocuments(document.getRef());
        }
        jg.writeFieldName("breadcrumb"); // <= write "breadcrumb" as key
        writeEntity(documentList, jg); // delegates the parent documents marshalling to Nuxeo
    }
}

Of course, like any JSON marshaller, you have to register it.

marshallers-contrib.xml

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

That's all, you can now use it!

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/api/v1/path/to/my/document?enrichers.document=breadcrumb'

All existing endpoints which return a DocumentModel will handle the registered enricher.

Extending an Existing JSON Marshaller

If you want to add information at the root level of the existing JSON (not in a contextParameters sub object), you can extend an existing marshaller.

The extending mechanism is only enabled for the existing marshallers which extend ExtensibleEntityJsonWriter . That means, almost all of them support it.

That could be dangerous if you use some JSON attributes which name already exists is the inherited JSON. Double check it!

Let's take an example with the DocumentModel. The current marshaller class for DocumentModel is DocumentModelJsonWriter.

Extended marshaller

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.OVERRIDE_REFERENCE) // <= an higher priority is used
public class MyCustomDocumentModelJsonWriter extends DocumentModelJsonWriter { // <= override the class
    protected void extend(DocumentModel document, JsonGenerator jg) throws IOException { // <= just implement the inherited extend method
        super.extend(document, jg); // optionnal
        jg.writeStringField("additional", "information"); // feel free to write anything
    }
}

Don't forget to register the marshaller. The resulting object will contain the whole existing JSON with your additional data.

All existing endpoints which return a DocumentModel will return the extended JSON.

Overriding an Existing JSON Marshaller

In this case, you want to completely rewrite an existing JSON.

Be careful with this approach, the marshallers provided by Nuxeo are used in many provided tools and screens. If you change it, you may break some existing components.

Custom marshaller

@Setup(mode = Instantiations.SINGLETON, priority = Priorities.OVERRIDE_REFERENCE) // <= an higher priority is used
public class MyCustomDocumentModelJsonWriter extends AbstractJsonWriter<DocumentModel> {
    public void write(DocumentModel document, JsonGenerator jg) throws IOException {
        // generate what you want
    }
}

JSON Marshallers Provided by the Nuxeo Platform

Each existing marshaller class provides a well documented Javadoc. Please, read the corresponding Javadoc to understand the generated JSON format and the available parameters.

document

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON DocumentModelJsonWriter Writes a document as JSON. It is enrichable and extendable.
  • properties=*
    Loads all document's properties
  • properties=dublincore,file
    Loads only dublincore and file schemas
  • fetch.document=properties
    Loads every properties associated with a resolver
  • fetch.document=versionLabel
    Loads the versioning information
  • fetch.document=lock
    Loads the lock owner and the lock date
DocumentModel JSON-to-Java DocumentModelJsonReader Reads a document from JSON. Supports either reference or JSON object for extended fields value.
  • If an uid is specified, it will update the existing document with the given properties. The result is a DocumentModelImpl ready to save.
  • Otherwise, you have to specify the name and the type of the document.
  • The result is a SimpleDocumentModel.

document / acls

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher ACLJsonEnricher Enriches a document. Adds document ACLs. Activated with enrichers.document=acls.

document / permissions

< /tr>
Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher BasePermissionsJsonEnricher Enriches a document. Adds user's permission on the document. Activated with enrichers.document=permissions.

document / breadcrumb

Managed Java type Type Java class Behavior
DocumentModel Java-to-JSON Enricher BreadcrumbJsonEnricher Enriches a document. Add the parent's documents. Activated with enrichers.document=breadcrumb.

document / children

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher ChildrenJsonEnricher Enriches a document. Adds the children documents. Activated with enrichers.document=children.

document / contextualParameters

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher ContextualParametersJsonEnricher Enriches a document with free key/value pair. Only from the server side.
String name = "contextualParameters";
Map<String, String> keyValues = ...;
ctx.enrich(name).param(name, keyValues).get();

document / preview

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher PreviewJsonEnricher Enriches a document. Adds the URL of its preview. Activated with enrichers.document=preview.

document / thumbnail

Managed Java Type Type Java Class Behavior
DocumentModel Java-to-JSON Enricher ThumbnailJsonEnricher Enriches a document. Adds the URL of its thumbnail. Activated with enrichers.document=thumbnail.

documents

Managed Java Type Type Java Class Behavior
List Java-to-JSON DocumentModelListJsonWriter Writes a list of document as JSON. Supports paginated lists and provides pagination information. Delegates the document's writing to the Nuxeo Platform.
List JSON-to-Java DocumentModelListJsonReader Reads a list of document from JSON. Delegates the document's reading to the Nuxeo Platform.

acls

Managed Java Type Type Java Class Behavior
ACP Java-to-JSON ACPJsonWriter Writes a set of access right as JSON. It is enrichable and extendable

docType

Managed Java Type Type Java Class Behavior
DocumentType Java-to-JSON DocumentTypeJsonWriter Writes a document's type as JSON. It is enrichable and extendable Delegates the writing of the type's schemas to the Nuxeo Platform.

docTypes

< th colspan="1">Type
Managed Java Type Java Class Behavior
List Java-to-JSON DocumentTypeListJsonWriter Writes a list of document's types as JSON. Supports paginated lists and provides pagination information. Delegates the type's writing to the Nuxeo Platform.

facet

Managed Java Type Type Java Class Behavior
CompositeType Java-to-JSON FacetJsonWriter Writes a document's facet as JSON. It is enrichable and extendable. Delegates the writing of the facet's schemas to the Nuxeo Platform.

facets

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON FacetListJsonWriter Writes a list of facets as JSON. Supports paginated lists and provides pagination information. Delegates the facet's writing to the Nuxeo Platform.

schema

Managed Java TypeTypeJava ClassBehavior
Schema Java-to-JSON SchemaJsonWriter Writes a schema as JSON. It is enrichable and extendable.

schemas

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON SchemaListJsonWriter Writes a list of schemas as JSON. Supports paginated lists and provides pagination information. Delegates the schema's writing to the Nuxeo Platform.

validation_constraint

Managed Java TypeTypeJava ClassBehavior
Constraint Java-to-JSON ConstraintJsonWriter Writes a validation constraint as JSON. It is enrichable and extendable.

validation_constraints

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON ConstraintListJsonWriter Writes a list of constraints as JSON. Supports paginated lists and provides pagination information. Delegates the constraint's writing to the Nuxeo Platform.

validation_report

Managed Java TypeTypeJava ClassBehavior
DocumentValidationReport Java-to-JSON DocumentValidationReportJsonWriter Writes a validation report as JSON. It is enrichable and extendable. Delegates the constraint's writing to the Nuxeo Platform.

user

Managed Java TypeTypeJava ClassBehavior
NuxeoPrincipal Java-to-JSON NuxeoPrincipalJsonWriter Writes a user as JSON. It is enrichable and extendable.
NuxeoPrincipal JSON-to-Java NuxeoPrincipalJsonReader Reads a user from JSON

users

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON NuxeoPrincipalListJsonWriter Writes a list of users as JSON. Supports paginated lists and provides pagination information. Delegates the user's writing to the Nuxeo Platform.
List JSON-to-Java NuxeoPrincipalListJsonReader Reads a list of users from JSON. Delegates the user's reading to the Nuxeo Platform

group

Managed Java TypeTypeJava ClassBehavior
NuxeoGroup Java-to-JSON NuxeoGroupJsonWriter Writes a group as JSON. It is enrichable and extendable.
NuxeoGroup JSON-to-Java NuxeoGroupJsonReader Reads a group from JSON

groups

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON NuxeoGroupListJsonWriter Writes a list of groups as JSON. Supports paginated lists and provides pagination information. Delegates the group's writing to the Nuxeo Platform
List JSON-to-Java NuxeoGroupListJsonReader Reads a list of groups from JSON. Delegates the group's reading to the Nuxeo Platform.

directoryEntry

Managed Java Type Type Java Class Behavior
DirectoryEntry Java-to-JSON DirectoryEntryJsonWriter Writes a directory entry as JSON. It is enrichable and extendable.
  • fetch.directoryEntry=parent
    Loads the entry's parent field as the corresponding entry in the same directoryL
    Useful for dc:subjects or dc:coverage values.
  • translate.directoryEntry=label
    Translates the label if it matches an existing l10n key.
    Useful for dc:nature values.
DirectoryEntry JSON-to-Java DirectoryEntryJsonReader Reads a directory entry from JSON

directoryEntries

Managed Java TypeTypeJava ClassBehavior
List Java-to-JSON DirectoryEntryListJsonWriter Writes a list of directory entries as JSON. Supports paginated lists and provides pagination information. Delegates the directory entries writing to the Nuxeo Platform.
List JSON-to-Java DirectoryEntryListJsonReader Reads a list of directory entries from JSON. Delegates the directory entries reading to the Nuxeo Platform.

logEntry

Managed Java TypeTypeJava ClassBehavior
LogEntry Java-to-JSON LogEntryJsonWriter Writes an audit entry to JSON. It is enrichable and extendable.
List Java-to-JSON LogEntryListJsonWriter Writes a list of audit entries to JSON It is enrichable and extendable. Delegates the entries writing to the Nuxeo Platform.

Document JSON and Extended Fields

Nuxeo Platform 7.2 introduces the concept of Extended fields. See Field Constraints and Validation and How to Customize Document Validation.

This kind of document field are defined as reference to external object. For example, in the Dublin Core schema:

  • dc:creator and dc:lastContributors are references to Nuxeo users
  • dc:contributors contains references to Nuxeo users
  • dc:subjects contains references to the "l10nsubjects" directory entries
  • dc:coverage is a reference to the "l10ncoverage" directory entry
  • dc:nature is a reference to the "nature" directory entry

Usage

The document marshalling provides a nice way to get the data referenced by one of its fields in-place.

For example, a call to get the JSON of a document which creator is the user "johnd" will return:

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/api/v1/path/to/my/document?properties=dublincore'

Resulting JSON

{  
  "entity-type":"document",
  "properties":{  
    "dc:creator": "johnd",
    ...
  },
  ...
}

If we tell the document marshaller to load the dc:creator referenced value, it will return the user's JSON in-place:

Curl calls

$ curl -X GET 'http://localhost:8080/nuxeo/site/api/v1/path/to/my/document?properties=dublincore&fetch.document=dc:creator'

Resulting JSON

{  
  "entity-type":"document",
  "properties":{  
    "dc:creator":{  
      "entity-type":"user",
      "id":"johndoe",
      "properties":{  
        "firstName":"John",
        "lastName":"Doe",
        "password":"",
        "groups":[  
          "members"
        ],
        "company":"Nuxeo",
        "email":"[email protected]",
        "username":"johnd"
      },
      "extendedGroups":[  
        {  
          "name":"members",
          "label":"Members group",
          "url":"group/members"
        }
      ],
      "isAdministrator":false,
      "isAnonymous":false
    },
    ...
  },
  ...
}

To load every extended fields of a document, use fetch.document=properties.

The Nuxeo Platform provides resolver for document, directory entry, user and group. It also provides Java-to-JSON marshaller for those objects. Therefore, you can fetch any property based on built-in resolvers.

Updating Document Properties

If a field is defined as a reference and if you provide a JSON-to-Java marshaller for the referenced object, you can either update the document using the reference as usual or use the object's JSON to update the corresponding document property.

  1. The Nuxeo Platform uses the ObjectResolver.getManagedClasses() method to get the expected type.
  2. The Nuxeo Platform delegates the JSON-to-Java marshalling to the marshalling service.
  3. The marshalling service delegates the JSON parsing to the registered marshaller.
  4. The Nuxeo Platform uses the resolver to get the reference from the parsed object and update the property.

The platform also provides JSON-to-Java for document, directory entry, user and group. Therefore, you can update any field based on the corresponding resolvers using the referenced object JSON.

Custom Resolver

When you create a custom resolver, if you want to be able to load the JSON of a referenced object, you have to register a Java-to-JSON marshaller.

If you want to be able to update a field using the JSON rather than the reference, you have to register a JSON-to-Java marshaller and be sure the getManagedClasses() method of your resolver returns the expected Java type.

Testing JSON Data

The Nuxeo Platform offers a utility object to test JSON data: org.nuxeo.ecm.core.io.marshallers.json.JsonAssert. It is provided by nuxeo-core-io test-jar.

Maven dependency

<!-- JSON marshallers tests -->
<dependency>
  <groupId>org.nuxeo.ecm.core</groupId>
  <artifactId>nuxeo-core-io</artifactId>
  <type>test-jar</type>
  <scope>test</scope>
</dependency>

Testing a Simple Marshaller

Let's test the following marshaller:

Product Java-to-JSON marshaller

@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 test looks like:

ProductJsonWriter test

public class ProductJsonWriterTest extends AbstractJsonWriterTest.Local<ProductJsonWriter, Product> {

    public ProductJsonWriterTest() {
        super(ProductJsonWriter.class, Product.class);
    }

    @Test
    public void test() throws Exception {
        Product product = new Product(123, "a description");
        JsonAssert json = jsonAssert(product);
        json.isObject().properties(2);
        json.has("ref").isEquals(product.getReference());
        json.has("desc").isEquals(product.getDescription());
    }

}

Each line makes an assertion on the provided JSON. Each assertion returns either the current JsonAssert (json.isObject().properties(2)) or a new JsonAssert based on the referenced data (json.**has("reference")**.isEquals(product.getReference())).

If your marshaller uses other marshallers, don't forget to load the corresponding contribution using @Deploy or @LocalDeploy.

Testing an Enricher

If you wrote an enricher for DocumentModel, the tested object should be DocumentModel. In that case, don't forget to load the contribution which contains your enricher registering.

public class SomeEnricherTest extends AbstractJsonWriterTest.Local<DocumentModelJsonWriter, DocumentModel> {

    public ProductJsonWriterTest() {
        super(DocumentModelJsonWriter.class, DocumentModel.class);
    }

    @Test
    public void test() throws Exception {
        DocumentModel doc = ...;
        RenderingContext ctx = CtxBuilder.enrichDoc("someEnricher").get();
        JsonAssert json = jsonAssert(doc, ctx);
        json = json.has("contextParameters.someEnricher");
        // make assertion on the enricher here
    }

}

Testing Any JSON Data

You can also use JsonAssert to test the result of your services.

public class SomeJsonTest {

    @Test
    public void test() throws Exception {
        String jsonString = ...; // get JSON from somewhere
        JsonAssert json = JsonAssert.on(jsonString);
        // make assertion on the json here
    }

}

Tips and Tricks

Getting the JSON

JsonAssert provides the ability to get the json at any level.

Json to test

{
  "field1": { "field2": "hi!" },
  "field3": "hello"
}

JsonAssert json = ...;
System.out.println( json.toString() );
// will print the whole JSON
System.out.println( json.has("field1.field2").toString() );
// will print "hi!" - be careful, the quotes are keeped, that's the real JSON

Quickly Testing Deep Data

JsonAssert provides the ability to use JSON path.

Json to test

{
  "field1": {
    "field2": [ {
        "field3": "myDataShouldBeHere"
    } ]
  }
}

json.has("field1.field2[0].field3").isEquals("myDataShouldBeHere");

Quickly Testing Array Values

JsonAssert provides the ability to test array values.

Json to test

{
  "field1": [ "A should be here", "B should be here", "C should be here twice", "C should be here twice" ]
}

json.has("field1").contains("A should be here", "B should be here", "C should be here twice", "C should be here twice");

Quickly Testing Complex Structure

JsonAssert provides the ability to test the value of a field for multiple object in an array.

Json to test

{
  "field1": [
    { "field2": "A is here" },
    { "field2": "B is here twice" },
    { "field2": "B is here twice" }
  ]
}

json.childrenContains("field1.field2", "A is here", "B is here twice", "B is here twice");

JsonAssert.childrenContains can cross over multiple arrays and objects.