Ignore/Rename fields from third class using jackson mixin

When you consume a third party library or an internal object from another business unit you may not have the ability to modify source code to add @JsonIgnore annotation. Let's find out how to use jacksons mixins to exclude and rename properties during the serialization and deserialization process.

Detailed Video Notes

While building a REST web services you might have asked "Should we create a object to represent the resource or should we just expose the domain itself"? If you go with "just expose the domain" it doesn't feel natural to embed jackson annotations within the code or create a dependency on the jackson json library. What if you decided to change json parser to Gson, would you go through your entire set of business objects to refactor and test? If you partition service developers and domain developers would it add confusion or degrade code readability? Luckily, there is a way to use jackson to include or exclude and rename properties outside the lower level object with the use of jackson mix ins.

Getting started

[0:37]

To get started lets create a maven project in eclipse, add jackson, jsonpath and guava dependencies. Next we will create a Person class that contains id, social security number, and name.

public class Person {

    private String id;
    private String ssn;
    private String name;

    //...
}
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.5.0</version>
</dependency>

Creating a mixin

[0:48]

You can think of a mixin as a proxy, meaning before jackson converts the java arraylist to a jsonarray it checks if there is an associated mix-in for a target class. If there is, it overlays the corresponding fields and annotations. We will create a PersonMixIn to house our rules. Keep in mind that we have a factitious rule that we can't edit the existing Person class and we don't want to add a dependency on jackson as we may change to a future json implementation.

public interface PersonMixIn {

    @JsonProperty("fullname")
    abstract String getName();

    @JsonIgnore
    abstract String getSsn();
}

Outlining the unit test

[1:13]

For each one of our snippets we will write a unit test to validate the behavior of the mix in. In the setup method lets initialize an ObjectMapper specifying the mixin and setting the pretty print in jackson property returning an ObjectWriter.

public class AppTest {

    private ObjectMapper mapper = null;
    private ObjectWriter writer = null;

    @Before
    public void setUp () {
        mapper = new ObjectMapper();

        mapper.setMixIns(ImmutableMap.<Class<?>, Class<?>> of(Person.class,
                PersonMixIn.class));

        writer = mapper.writer().with(SerializationFeature.INDENT_OUTPUT);
    }
    //..
}

Renaming a property

[1:27]

Probably the most common is renaming of fields or properties. Our first rule is when we marshal the Person object to json we want to rename the name field to fullname. We do this by adding @JsonProperty and specifying the annotation value with the name we want to convert it to. Using json path we will parse and validate the full name making sure it equals "Jack Johnson".

@Test
public void rename_field_jackson() throws JsonProcessingException {

    Person person = new Person("1", "333445555", "Jack Johnson");

    String json = writer.writeValueAsString(person);

    System.out.println(json);

    String fullName = JsonPath.read(json, "$.fullname");

    assertEquals("Jack Johnson", fullName);
}

Output

{
  "id" : "1",
  "fullname" : "Jack Johnson"
}

Filtering, excluding or ignoring a field

[1:49]

The SSN number can be categorized as sensitive in the sense that you wouldn't want to expose it outside your internal systems. We have been asked to filter out the SSN when exposing the Person object as json. We can do this adding the @JsonIgnore in our PersonMixIn interface. Again we will validate with jsonpath and since the ssn field doesn't exists a PathNotFoundException will be thrown.

@Test(expected=PathNotFoundException.class)
public void ignore_field_jackson() throws JsonProcessingException {

    Person person = new Person("2", "222556789", "Joe Webb");

    String json = writer.writeValueAsString(person);

    JsonPath.read(json, "$.ssn");
}

As you can see using jackson mixins provides a powerful way to decouple lower level code from consumers providing a json view representation of objects.

Thanks for joining in today's level up, have a great day!