Jackson Deserialization and the Builder Pattern

5 minute read

The builder design pattern is one of those patterns that are widely used by developers. It’s very helpful when we have to create objects of classes that have many properties, especially when some of them are optional. It’s common that when we use builders the constructors of our classes are private and instances can only be created through the builder. Therefore, if we use these classes for deserialization purposes with Jackson we may face some issues since Jackson can’t instantiate our classes due to the lack of a public constructor.

In this tutorial, we’ll show how to use our builders with Jackson for JSON deserialization. We’ll also cover how to do this if we’re using Lombok to generate the builders.

Jackson Deserialization Using a Builder

Let’s first create a class to use it in our examples!

We’ll create a simple Address class:

public class Address {

  private final String street;
  private final String zipCode;
  private final String city;
  private final String province;
  private final String country;

  private Address(String street, String zipCode, String city, String province, String country) {
    this.street = street;
    this.zipCode = zipCode;
    this.city = city;
    this.province = province;
    this.country = country;
  }

  // ... getters, equals, hashCode and toString
  
  public static class Builder {

    private String street;
    private String zipCode;gvkh
    private String city;
    private String province;
    private String country;

    public Builder street(String street) {
      this.street = street;
      return this;
    }

    public Builder zipCode(String zipCode) {
      this.zipCode = zipCode;
      return this;
    }

    public Builder city(String city) {
      this.city = city;
      return this;
    }

    public Builder province(String province) {
      this.province = province;
      return this;
    }

    public Builder country(String country) {
      this.country = country;
      return this;
    }

    public Address build() {
      return new Address(street, zipCode, city, province, country);
    }
  }
}

As we can see, this class is immutable and we can only instantiate it through its builder.

If we try to deserialize a JSON into our Address class, Jackson will throw an exception saying that it can’t find a creator to construct an instance of our class:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.grabanotherbyte.jackson.builder.Address` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"street":"street","zipCode":"1234","city":"my city","province":"province","country":"country"}"; line: 1, column: 2]

This happens because the constructor of our class is private.

What we need to do is to tell Jackson to use our builder for the deserialization. We can do this by annotating our class with @JsonDeserialize and specify our Builder class there:

@JsonDeserialize(builder = Address.Builder.class)
public class Address {
  // ... rest of the class
}

However, we’ll still get an exception if we try to use the class as it is right now:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "street" (class com.grabanotherbyte.jackson.builder.Address$Builder), not marked as ignorable (0 known properties: ])
 at [Source: (String)"{"street":"street","zipCode":"1234","city":"my city","province":"province","country":"country"}"; line: 1, column: 12] (through reference chain: com.grabanotherbyte.jackson.builder.Address$Builder["street"])

Now Jackson is telling us that it can’t recognize the properties of our Builder class. This happens because Jackson expects that our builder uses the prefix with in the setter methods. In our case, we don’t use any prefix.

One option to fix this is to change our builder to use the with prefix. Another option is to instruct Jackson to use a different prefix by using the @JsonPOJOBuilder annotation in our Builder class:

@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
  // ... rest of the class
}

In our case, we leave the prefix as an empty string since we’re not using any prefix.

Also, keep in mind that Jackson expects that the name of the method that creates the instances in our builder is build. Luckily, we can also customize this in the same annotation:

@JsonPOJOBuilder(withPrefix = "", buildMethodName = "create")
public static class Builder {
  // ... rest of the class
}

As seen, we’ve just supposed that our builder build method is now called create.

Jackson Deserialization Using Lombok Builders

Now we’re gonna do the same as in the previous section but working with a class that uses Lombok to generate the builder.

First, let’s change our Address class to add the proper lombok annotations:

@Value
@Builder(setterPrefix = "with")
public class Address {

  String street;
  String zipCode;
  String city;
  String province;
  String country;

}

This class is also immutable and it has a private constructor. Hence, we can create instances only through its builder.

As we can see above, our builder now uses the prefix with in the setter methods. Therefore, we only need to tell Jackson to use this builder to create instances and we’re good to go:

@Value
@Builder(setterPrefix = "with")
@JsonDeserialize(builder = AddressLombok.AddressBuilder.class)
public class Address {
  // ... rest of the class
}

This is enough to use this class for deserialization with Jackson.

However, let’s suppose that we’re not using with as setter prefix, how can we let Jackson know? We have to declare the Builder class ourselves and use the same @JsonPOJOBuilder annotation as before:

@Value
@Builder
@JsonDeserialize(builder = Address.AddressBuilder.class)
public class Address {

  String street;
  String zipCode;
  String city;
  String province;
  String country;

  @JsonPOJOBuilder(withPrefix = "")
  public static class AddressBuilder {
    // Lombok will add the rest
  }
}

Also notice that Lombok uses build as the default name of the build method. If we want to use another one we have to specify it in the buildMethodName property of the @JsonPOJOBuilder annotation as we did in the previous section.

@Jacksonized

Lombok has introduced an experimental feature @Jacksonized in its 1.18.14 version. The goal of this feature is to simplify the integration between Lombok and Jackson.

Let’s modify our previous example to use this annotation! It’s as simple as just replacing all the Jackson annotations with @Jacksonized:

@Value
@Jacksonized
@Builder
public class Address {

  String street;
  String zipCode;
  String city;
  String province;
  String country;

}

That’s it! Our class is ready!

Notice that if we change the setter prefix or the build method name of our builder Lombok will take care of it and will change the generated Jackson annotations accordingly.

This annotation is great and helps us save code and time when working with builders and Jackson. But remember that at the time of writing this tutorial it’s still experimental.

Conclusion

In this tutorial, we’ve explained how to use Jackson to deserialize classes that can only be instantiated by using a builder. We’ve shown examples of how to do this and we’ve also shown how to customize the default behavior of the Jackson annotations. Additionally, we’ve also covered how to do this if we’re using the popular library Lombok.

The source code of the examples is available at Github.

Categories:

Updated:

Leave a comment