Reputation: 134
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
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