When deserializing a variety of JSON messages, I want to provide a default value for attributes of a certain type. It is generally suggested to simply specify the value in the Class, but this is error-prone if you have to do this across many Classes. You might forget one and end up with null
instead of a default value. My intention is to set every property that is an Optional<T>
to Optional.absent
. Since null
is exactly what Optional
is trying to eliminate, using them with Jackson has proven to be frustrating.
Most features of Jackson that allow you to customize the deserialization process focus on the JSON that is the input, not around the process of instantiating the Object that you are deserializing into. The closest I seem to be getting to a general solution is by building my own ValueInstantiator
, but there are two remaining issues I have:
Optional
as absent
but not interfere with the rest of the instantiation process?ObjectMapper
?UPDATE: I want to clarify that I am looking for a solution that does not involve modifying each Class that contains Optional
's. I'm opposed to violating the DRY principle. Me or my colleagues should not have to think about having to do something extra every time we add Optional
's to a new or existing Class. I want to be able to say, "make every Optional field in every Class I deserialize into, pre-filled with Absent
", only once, and be done with it.
That means the following are out:
You can ignore null fields at the class level by using @JsonInclude(Include. NON_NULL) to only include non-null fields, thus excluding any attribute whose value is null. You can also use the same annotation at the field level to instruct Jackson to ignore that field while converting Java object to json if it's null.
If there are fields in Java objects that do not wish to be serialized, we can use the @JsonIgnore annotation in the Jackson library. The @JsonIgnore can be used at the field level, for ignoring fields during the serialization and deserialization.
Serialization is the process of converting the state of an object to a byte stream in a way that the byte stream can be reverted into a copy of the original object. Deserialization, as you may already have inferred, is the process of converting the serialized form of an object back into a copy of the original object.
XML Serialization and Deserialization with Jackson This short tutorial shows how the Jackson library can be used to serialize Java object to XML and deserialize them back to objects.
Specifically for java.lang.Optional, there is a module by the Jackson guys themselves: https://github.com/FasterXML/jackson-datatype-jdk8
Guava Optional is covered by https://github.com/FasterXML/jackson-datatype-guava
It will create a Optional.absent for null's, but not for absent JSON values :-(.
See https://github.com/FasterXML/jackson-databind/issues/618 and https://github.com/FasterXML/jackson-datatype-jdk8/issues/2.
So you're stuck with initializing your Optionals just as you should initialize collections. It is a good practice, so you should be able to enforce it.
private Optional<Xxx> xxx = Optional.absent();
private List<Yyy> yyys = Lists.newArrayList();
You can write a custom deserializer to handle the default value. Effectively you will extend the appropriate deserializer for the type of object you are deserializing, get the deserialized value, and if it's null
just return the appropriate default value.
Here's a quick way to do it with Strings:
public class DefaultStringModule extends SimpleModule {
private static final String NAME = "DefaultStringModule";
private static final String DEFAULT_VALUE = "[DEFAULT]";
public DefaultStringModule() {
super(NAME, ModuleVersion.instance.version());
addDeserializer(String.class, new DefaultStringDeserializer());
}
private static class DefaultStringDeserializer extends StdScalarDeserializer<String> {
public DefaultStringDeserializer() {
super(String.class);
}
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
String deserialized = jsonParser.getValueAsString();
// Use a default value instead of null
return deserialized == null ? DEFAULT_VALUE : deserialized;
}
@Override
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return deserialize(jp, ctxt);
}
}
}
To use this with an ObjectMapper
you can register the module on the instance:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new DefaultStringModule());
To handle default values for fields not present in the JSON, I've typically seen this done through the use of a builder class that will construct the class using the values supplied and add any default values for the missing fields. Then, on the deserialized class (e.g. MyClass
), add a @JsonDeserialize(builder = MyClass.Builder.class)
annotation to indicate to Jackson to deserialize MyClass
by way of the builder class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With