user3509599
user3509599

Reputation: 41

jackson custom serialization with filtering

I need to customize serialization of a POJO in Jackson so that I can apply filter on the properties based on user input

I applied the following annotations on the POJO.

@JsonFilter("userFilter")
@JsonSerialize(using = UserSerializer.class)

The custom serializer class is as below.

public class UserSerializer  extends JsonSerializer<User> {


    @Override
    public void serialize(User value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {

        ObjectMapper mapper = new ObjectMapper();
        SimpleFilterProvider sfp = new SimpleFilterProvider();

        // create a  set that holds name of User properties that must be serialized
        Set userFilterSet = new HashSet<String>();
        userFilterSet.add("firstName");
        userFilterSet.add("corporateOrgs");
        userFilterSet.add("rights");
        userFilterSet.add("requirements");


        sfp.addFilter("userFilter",SimpleBeanPropertyFilter.filterOutAllExcept(userFilterSet));

        // create an objectwriter which will apply the filters 
        ObjectWriter writer = mapper.writer(sfp);

        String json = writer.writeValueAsString(value);


    }

}

I can see that Jackson is trying to serialize the POJO using the custom serializer defined. However it ends up in infinite recursion/stackoverflow as writer.writeValueAsString(value) ends up calling the custom serializer again.

Obviously I have not got some basic stuff right here. If the filtering is done outside the serialize method (for example in a method called from main() ), filtering works as expected.

can anyone please provide insight/link to documentation on how to make use of custom serialization to leverage filtering.

Upvotes: 4

Views: 4915

Answers (1)

Stephan Schlecht
Stephan Schlecht

Reputation: 27096

Fields can be filtered out with JsonFilter, or you can create a custom JsonSerialize serializer that writes out only certain fields.

Independent of the use of a JsonFilter, the attempt to recursively reserialize the same object to be serialized (first parameter of the overwritten serialize method) in a user-defined serializer with an object mapper will result in an infinite loop. Instead, in a custom serializer you would rather use the JsonGenerator methods (second parameter of the overridden serialize method) to write out field name/values.

In the following answer both variants (@JsonFilter and @JsonSerialize) are demonstrated, where only a part of the available fields are serialized to JSON.

@JsonFilter

To apply filters to properties based on user input, you do not need to extend JsonSerializer. Instead, you annotate the POJO with JsonFilter and just apply the filtering.

A self-contained example based on your code would look like this:

package com.software7.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

import java.util.HashSet;
import java.util.Set;

public class Main {

    public static void main(String[] args) {
        Main m = new Main();
        try {
            m.serialize();
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    void serialize() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleFilterProvider sfp = new SimpleFilterProvider();

        Set<String> userFilterSet = new HashSet<>();
        userFilterSet.add("firstName");
        userFilterSet.add("corporateOrgs");
        userFilterSet.add("rights");
        userFilterSet.add("requirements");

        sfp.addFilter("UserFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept(userFilterSet));

        mapper.setFilterProvider(sfp);

        User user = new User("Brownrigg", "Don", "none", "+rwx", "n/a",
                "some", "superfluous", "properties");
        System.out.println(user);
        System.out.println(">>>> serializing >>>>");
        String s = mapper.writeValueAsString(user);
        System.out.println(s);
    }
}

User POJO

package com.software7.test;

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("UserFilter")
public class User {
    public String lastName;
    public String firstName;
    public String corporateOrgs;
    public String rights;
    public String requirements;
    public String a, b, c;

    public User(String lastName, String firstName, String corporateOrgs, String rights, String requirements,
                String a, String b, String c) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.corporateOrgs = corporateOrgs;
        this.rights = rights;
        this.requirements = requirements;
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return "User{" +
                "lastName='" + lastName + '\'' +
                ", firstName='" + firstName + '\'' +
                ", corporateOrgs='" + corporateOrgs + '\'' +
                ", rights='" + rights + '\'' +
                ", requirements='" + requirements + '\'' +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
}

Test

The debug output of the above program would look like this:

User{lastName='Brownrigg', firstName='Don', corporateOrgs='none', rights='+rwx', requirements='n/a', a='some', b='superfluous', c='properties'}
>>>> serializing >>>>
{"firstName":"Don","corporateOrgs":"none","rights":"+rwx","requirements":"n/a"}

The test is successful! As you can see, the properties lastName, a, b and c are removed.

@JsonSerialize Alternative

If you want to use a customer serializer instead you can do like so:

Replace the annotation:

@JsonFilter("UserFilter")

with

@JsonSerialize(using = UserSerializer.class)

but do not use both.

The UserSerializer class could look like this:

package com.software7.test;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class UserSerializer extends JsonSerializer<User> {
    @Override
    public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException, JsonProcessingException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeObjectField("firstName", user.firstName);
        jsonGenerator.writeObjectField("corporateOrgs", user.corporateOrgs);
        jsonGenerator.writeObjectField("rights", user.rights);
        jsonGenerator.writeObjectField("requirements", user.requirements);
        jsonGenerator.writeEndObject();
    }
}

Finally, the serialization method would look like this:

void serialize() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    User user = new User("Brownrigg", "Don", "none", "+rwx", "n/a",
            "some", "superfluous", "properties");
    System.out.println(user);
    System.out.println(">>>> serializing >>>>");
    String s = mapper.writeValueAsString(user);
    System.out.println(s);
}

The result would be the same in this example. Which variant is better suited depends on the specific use case or personal preferences.

Upvotes: 1

Related Questions