amanmehara
amanmehara

Reputation: 134

Jackson custom serializers

Can a single custom serializer be used as both key serializer and normal object serializer?

i.e. sometime use the serializer as a key serializer for serializing map keys while at other time serializing the object normally.

I was facing issues with JsonGenerator object passed to the serializer method.

When used as a key serializer it expects a field name but when using normally, it expects the value.

Upvotes: 1

Views: 727

Answers (1)

Michał Ziober
Michał Ziober

Reputation: 38625

You can use the same custom serializer but you need to distinguish somehow whether you need to generate property or whole object. Map is serialized to JSON Object where Map keys are converted to JSON Object properties. To generate property with Jackson we need to use writeFieldName method. To distinguish how you would like to use this serialiser in constructor you can provide this information. Example:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.Collections;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        SimpleModule userModule = new SimpleModule();
        userModule.addSerializer(User.class, new UserJsonSerializer(false));
        userModule.addKeySerializer(User.class, new UserJsonSerializer(true));

        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.registerModule(userModule);

        User user = new User();
        System.out.println(mapper.writeValueAsString(Collections.singletonMap(user, user)));
    }
}

class User {

    private String firstName = "Tom";
    private String lastName = "Smith";

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

class UserJsonSerializer extends JsonSerializer<User> {

    private final boolean generateKey;

    UserJsonSerializer(boolean generateKey) {
        this.generateKey = generateKey;
    }

    @Override
    public void serialize(User value, JsonGenerator gen, SerializerProvider serializers)
        throws IOException {
        if (generateKey) {
            serializeAsMapKey(value, gen);
        } else {
            serializeAsObject(value, gen);
        }
    }

    private void serializeAsMapKey(User value, JsonGenerator gen) throws IOException {
        gen.writeFieldName(String.join(",", value.getFirstName(), value.getLastName()));
    }

    private void serializeAsObject(User value, JsonGenerator gen) throws IOException {
        gen.writeStartObject();
        gen.writeFieldName("first");
        gen.writeString(value.getFirstName());
        gen.writeFieldName("last");
        gen.writeString(value.getLastName());
        gen.writeEndObject();
    }
}

Above code prints:

{
  "Tom,Smith" : {
    "first" : "Tom",
    "last" : "Smith"
  }
}

If you do not have any common logic you can just create two separate classes: UserJsonSerializer and UserKeyJsonSerializer which is an object oriented and clear solution.

Upvotes: 1

Related Questions