This manual is a work in progress and is currently incomplete.
If you'd like to help improve it, and we hope you do, please see the README.

14 Jackson

Integration with the Jackson JSON marshalling library provides the ability to work with JSON.

The ratpack-jackson JAR is released as part of Ratpack’s core distribution and is versioned with it. As of Ratpack 0.9.12 is built against (and depends on) Jackson Core 2.4.4.

The ratpack.jackson.Jackson class provides most of the Jackson related functions.

14.1 Initialisation

The Jackson can be used with the Guice integration. The JacksonModule is a Guice module that enables the integration.

If not using Guice, you can use the Jackson.Init.register() method to add the necessary objects to the context registry.

14.2 Writing JSON responses

The Jackson integration adds a Renderer for rendering objects as JSON.

The Jackson.json() method can be used to wrap any object (serializable by Jackson) for use with the Context.render() method.

import ratpack.guice.Guice;
import ratpack.test.embed.EmbeddedApp;
import ratpack.jackson.JacksonModule;
import ratpack.http.client.ReceivedResponse;

import static ratpack.jackson.Jackson.json;
import static org.junit.Assert.*;

public class Example {

  public static class Person {
    private final String name;
    public Person(String name) {
      this.name = name;
    }
    public String getName() {
      return name;
    }
  }

  public static void main(String... args) {
    EmbeddedApp.fromHandlerFactory(launchConfig ->
      Guice.builder(launchConfig)
        .bindings(b ->
          b.add(JacksonModule.class, c -> c.prettyPrint(false))
        )
        .build(chain ->
          chain.get(ctx -> ctx.render(json(new Person("John"))))
        )
    ).test(httpClient -> {
      ReceivedResponse response = httpClient.get();
      assertEquals("{\"name\":\"John\"}", response.getBody().getText());
      assertEquals("application/json", response.getBody().getContentType().getType());
    });
  }
}

See the Jackson class documention for more examples, including streaming and JSON events.

14.3 Reading JSON requests

The Jackson integration adds a Parser for converting JSON request bodies into objects.

The Jackson.jsonNode() and Jackson.fromJson() methods can be used to create objects to be used with the Context.parse() method.

import ratpack.guice.Guice;
import ratpack.test.embed.EmbeddedApp;
import ratpack.jackson.JacksonModule;
import ratpack.http.client.ReceivedResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.reflect.TypeToken;

import java.util.List;

import static ratpack.util.Types.listOf;
import static ratpack.jackson.Jackson.jsonNode;
import static ratpack.jackson.Jackson.fromJson;
import static org.junit.Assert.*;

public class Example {

  public static class Person {
    private final String name;
    public Person(@JsonProperty("name") String name) {
      this.name = name;
    }
    public String getName() {
      return name;
    }
  }

  public static void main(String... args) {
    EmbeddedApp.fromHandlerFactory(launchConfig ->
      Guice.builder(launchConfig)
        .bindings(b ->
          b.add(JacksonModule.class, c -> c.prettyPrint(false))
        )
        .build(chain -> chain
          .post("asNode", ctx -> {
            JsonNode node = ctx.parse(jsonNode());
            ctx.render(node.get("name").asText());
          })
          .post("asPerson", ctx -> {
            Person person = ctx.parse(fromJson(Person.class));
            ctx.render(person.getName());
          })
          .post("asPersonList", ctx -> {
            List<Person> person = ctx.parse(fromJson(listOf(Person.class)));
            ctx.render(person.get(0).getName());
          })
        )
    ).test(httpClient -> {
      ReceivedResponse response = httpClient.requestSpec(s ->
        s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
      ).post("asNode");
      assertEquals("John", response.getBody().getText());

      response = httpClient.requestSpec(s ->
        s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
      ).post("asPerson");
      assertEquals("John", response.getBody().getText());

      response = httpClient.requestSpec(s ->
        s.body(b -> b.type("application/json").text("[{\"name\":\"John\"}]"))
      ).post("asPersonList");
      assertEquals("John", response.getBody().getText());
    });
  }
}

The integration adds a no opts parser, which makes it possible to use the Context.parse(Class) and Context.parse(TypeToken) methods.

import ratpack.guice.Guice;
import ratpack.test.embed.EmbeddedApp;
import ratpack.jackson.JacksonModule;
import ratpack.http.client.ReceivedResponse;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.reflect.TypeToken;

import java.util.List;

import static ratpack.util.Types.listOf;
import static org.junit.Assert.*;

public class Example {

  public static class Person {
    private final String name;
    public Person(@JsonProperty("name") String name) {
      this.name = name;
    }
    public String getName() {
      return name;
    }
  }

  public static void main(String... args) {
    EmbeddedApp.fromHandlerFactory(launchConfig ->
      Guice.builder(launchConfig)
        .bindings(b ->
          b.add(JacksonModule.class, c -> c.prettyPrint(false))
        )
        .build(chain -> chain
          .post("asPerson", ctx -> {
            Person person = ctx.parse(Person.class);
            ctx.render(person.getName());
          })
          .post("asPersonList", ctx -> {
            List<Person> person = ctx.parse(listOf(Person.class));
            ctx.render(person.get(0).getName());
          })
        )
    ).test(httpClient -> {
      ReceivedResponse response = httpClient.requestSpec(s ->
        s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
      ).post("asPerson");
      assertEquals("John", response.getBody().getText());

      response = httpClient.requestSpec(s ->
        s.body(b -> b.type("application/json").text("[{\"name\":\"John\"}]"))
      ).post("asPersonList");
      assertEquals("John", response.getBody().getText());
    });
  }
}

14.4 Using Jackson feature modules

Jackson feature modules allow Jackson to be extended to support extra data types and capabilities. For example the JDK8 module adds support for JDK8 types like Optional.

If using Guice, such modules can easily be registered via the module’s config.

import ratpack.guice.Guice;
import ratpack.test.embed.EmbeddedApp;
import ratpack.jackson.JacksonModule;
import ratpack.http.client.ReceivedResponse;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;

import java.util.Optional;

import static ratpack.jackson.Jackson.json;
import static org.junit.Assert.*;

public class Example {

  public static class Person {
    private final String name;
    public Person(String name) {
      this.name = name;
    }
    public String getName() {
      return name;
    }
  }

  public static void main(String... args) {
    EmbeddedApp.fromHandlerFactory(launchConfig ->
      Guice.builder(launchConfig)
        .bindings(b ->
          b.add(JacksonModule.class, c -> c
            .modules(new Jdk8Module()) // register the Jackson module
            .prettyPrint(false)
          )
        )
        .build(chain ->
          chain.get(ctx -> {
            Optional<Person> personOptional = Optional.of(new Person("John"));
            ctx.render(json(personOptional));
          })
        )
    ).test(httpClient -> {
      ReceivedResponse response = httpClient.get();
      assertEquals("{\"name\":\"John\"}", response.getBody().getText());
      assertEquals("application/json", response.getBody().getContentType().getType());
    });
  }
}