Moonwlker: JSON without annotation

Bertil Muth - Jun 15 '20 - - Dev Community

Jackson is a powerful library for processing JSON. It’s the default library used by the Spring Framework. It's very flexible and configurable. The standard way of configuring it is using annotations in the classes to be processed.

In this post, I will explain how to use the Moonwlker library that I created. It’s a facade to Jackson, and provides a builder API to configure Jackson. That way, you can get rid of anotations in your classes. I will also show you how to integrate Moonwlker into Spring Boot applications.

Before we dive into code examples, why would you want to get rid of annotations in your classes? There may be several reasons, including:

  • The annotations are JSON specific, and you don't want to bind yourself to using JSON as seralization mechanism.
  • You don't have access to some classes where you'd need to put annotations, e.g. super classes of a framework.
  • You're following Domain Driven Design (DDD) principles to publish/subscribe to Domain Events, and don't want your Domain Event classes to be cluttered with technology specific annotations.

Let's have a look at use cases, and how Moonwlker can help with it.

Inheritance

Say you have a superclass Person, with subclass Employee. The standard way to tell Jackson about the inheritance relationships is to use annotations in the superclass:

@JsonTypeInfo(property = "@type", include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonSubTypes({ @JsonSubTypes.Type(value = Employee.class) })
public class Person {

  private String firstName;
  private String lastName;

  public Person() {
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}
Enter fullscreen mode Exit fullscreen mode

What's the meaning of property = "@type" in the annotation? It declares that the JSON contains a property called @type. Its value is the name of the subclass so that Jackson knows which concrete class instance to create when deserializing.
As you can see, the superclass needs to mention all the subclasses, in this case the Employee class. Here's the code:

public class Employee extends Person{
  private String employeeNumber;

  public Employee() {
  }

  public String getEmployeeNumber() {
    return employeeNumber;
  }

  public void setEmployeeNumber(String employeeNumber) {
    this.employeeNumber = employeeNumber;
  }
}
Enter fullscreen mode Exit fullscreen mode

Here's how you deserialize JSON to an Employee object, given that the Employee class is in the same package as the Person class.

ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{ \"@type\" : \".Employee\", \"firstName\" : \"Jane\", \"lastName\" : \"Doe\" , \"employeeNumber\" : \"EMP-2020\"}";
Employee employee = (Employee) objectMapper.readValue(jsonString, Person.class);
Enter fullscreen mode Exit fullscreen mode

How Moonwlker handles inheritance

When you use Moonwlker, you can rid of all the annotations in the superclass. Instead, you build and register a module that contains the mapping between the JSON property, e.g. @type, and the superclass, e.g. Person:

ObjectMapper objectMapper = new ObjectMapper();

MoonwlkerModule module =
  MoonwlkerModule.builder()
    .fromProperty("@type").toSubclassesOf(Person.class)
    .build();

objectMapper.registerModule(module);
Enter fullscreen mode Exit fullscreen mode

The JSON looks almost identical, with the exception that there's no leading dot before the class name in the @type property:

String jsonString = "{ \"@type\" : \"Employee\", \"firstName\" : \"Jane\", \"lastName\" : \"Doe\" , \"employeeNumber\" : \"EMP-2020\"}";
Employee employee = (Employee) objectMapper.readValue(jsonString, Person.class);
Enter fullscreen mode Exit fullscreen mode

And that's it.

Integrating Moonwlker into Spring

To integrate Moonwlker into a Spring Boot project, all you need to do is register a Bean for the ObjectMapper:

@SpringBootApplication
public class GreeterApplication {
  public static void main(String[] args) {
    SpringApplication.run(GreeterApplication.class, args);
  }

  @Bean
  ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();

    MoonwlkerModule module =
      MoonwlkerModule.builder()
        .fromProperty("@type").toSubclassesOf(Person.class)
        .build();

    objectMapper.registerModule(module);
    return objectMapper;
  } 
}
Enter fullscreen mode Exit fullscreen mode

Immutable object with all argument constructors

The standard way in which Jackson supports all arguments constructors is to use the @JsonCreator and @JsonProperties annotations. Moonwlker changes that: it enables you to deserialize objects that have a single, all arguments default constructor.

An example class that can be (de)serialized with Moonwlker could look like this:

public class Dog extends Animal {
  private final String name;
  private final String command;

  public Dog(BigDecimal price, String name, String command) {
    super(price);
    this.name = name;
    this.command = command;
  }

  public String name() {
    return name;
  }

  public String command() {
    return command;
  }
}
Enter fullscreen mode Exit fullscreen mode

See the Moonwlker website on details how to enable this feature.

More to come

The Moonwlker project is work in progress.
Any feedback is welcome. Drop a note in the comments.
Or become a contributor. Thank you!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .