Jackson Mixins Common Uses

4 minute read

Jackson mixins is a mechanism to add Jackson annotations to classes without modifying the actual class. It was created for those cases where we can’t modify a class such as when working with third-party classes.

We can use any Jackson annotation but we don’t add them directly to the class. We use them in a mixin class instead that can be either an abstract class or an interface. They can be used for both Jackson serialization and deserialization and they have to be added to the ObjectMapper configuration.

In the next sections, we’re gonna be quite practical and we’ll show some common cases where it’s useful to use Jackson mixins.

Making a Third-Party Class Jackson Serializable

The first case we’re showing is when we need to use third-party classes that can’t be serialized or deserialized by Jackson because they aren’t following the Jackson conventions. Since we can’t modify these classes we have to use mixins to add all the necessary pieces that Jackson needs for serialization purposes.

Let’s suppose that we want to serialize this third-party class:

public class Person {

  private final String firstName;
  private final String lastName;

  public Person(String firstName, String lastName) {
    this.firstName = firstName ;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
        .add("firstName='" + firstName + "'")
        .add("lastName='" + lastName + "'")
        .toString();
  }
}

We can’t serialize this class with Jackson because the properties are private and there aren’t getters and setters. Hence, Jackson won’t recognize any property and will throw an exception :

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.grabanotherbyte.jackson.Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

Let’s create now a mixin to solve this problem! In our mixin we’ll add the properties that we want to serialize:

public abstract class PersonMixin {
  @JsonProperty private String firstName;
  @JsonProperty private String lastName;
}

In this case, we´ve created an abstract class because later we’ll need to add a constructor.

After that, we need to tell Jackson to use our Mixin. To do so, we need to set this up in our ObjectMapper:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Person.class, PersonMixin.class);

Now, Jackson will be able to serialize our Person and will serialize both the firstName and lastName.

On the other hand, if we also want to use our Person class for deserialization we need to add a jackson-friendly constructor to our mixin class:

public abstract class PersonMixin {
  // ... properties

  @JsonCreator
  public PersonMixin(@JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName) {}

}

Otherwise, Jackson would throw an exception.

Ignoring Properties

We’ll consider now a different scenario when serializing third-party classes. Let’s assume now that our Person class has all the getters, setters and constructors needed and all its fields will be serialized:

public class Person {

  private String firstName;
  private String lastName;

  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;
  }
}

However, we’d like to serialize only the firstName, and as before we can’t modify this class. Again, we can work around this by using a mixin class:

public abstract class PersonMixin {
  @JsonIgnore private String lastName;
}

This way, Jackson will ignore this property and will only serialize the firstName.

Changing Property Names

Following our previous example, we’d also like to change the name of some properties when they’re serialized.

Let’s modify our mixin class to rename the property lastName:

public abstract class PersonMixin {
  @JsonProperty("surname") private String lastName;
}

Now, our lastName property will be serialized as surname.

Overriding Custom Serializers and Deserializers

There are other scenarios where we find classes that are using custom serializers and deserializers but we want to override them. And of course, we can’t or we don’t want to modify these classes.

Let’s extend our Person class to include a custom serializer and deserializer:

@JsonSerialize(using = PersonSerializer.class)
@JsonDeserialize(using = PersonDeserializer.class)
public class Person {

  private final String firstName;
  private final String lastName;

  // getters, setters, constructors...

  public static class PersonSerializer extends JsonSerializer<Person> {

    static final String SEPARATOR = " ";

    @Override
    public void serialize(
        Person person, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
        throws IOException {
      jsonGenerator.writeString(person.getFirstName() + SEPARATOR + person.getLastName());
    }
  }

  public static class PersonDeserializer extends JsonDeserializer<Person> {

    @Override
    public Person deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
        throws IOException {
      String[] fields = jsonParser.getValueAsString().split(PersonSerializer.SEPARATOR);
      return new Person(fields[0], fields[1]);
    }
  }
}

As we can see, the Person class is now serialized by concatenating the firstName and lastName.

However, in some cases we’d like to serialize this class differently. Let’s create a different serializer and deserializer for our class:

public static class PersonReversedSerializer extends JsonSerializer<Person> {

  static final String SEPARATOR = ", ";

  @Override
  public void serialize(
      Person person, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
      throws IOException {
    jsonGenerator.writeString(person.getLastName() + SEPARATOR + person.getFirstName());
  }
}

public static class PersonReversedDeserializer extends JsonDeserializer<Person> {

  @Override
  public Person deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
      throws IOException {
    String[] fields = jsonParser.getValueAsString().split(PersonReversedSerializer.SEPARATOR);
    return new Person(fields[1], fields[0]);
  }
}

Now, our Person will be serialized as “lastName, firstName”.

As we did before, to use this new serializer and deserializer without modifying Person we need to specify it in our mixin class:

@JsonSerialize(using = PersonReversedSerializer.class)
@JsonDeserialize(using = PersonReversedDeserializer.class)
public abstract class PersonMixin {}

Now, Jackson will use these serializers and will ignore the ones specified in Person.

It would be the same if we wanted to do this just for a specific property.

Conclusion

In this short tutorial, we’ve briefly introduced the Jackson mixins and then focused on some cases where they may be useful. We’ve also shown some examples to illustrate these cases and show how these mixins work.

The source code of the examples can be found at Github.

Categories:

Updated:

Leave a comment