Implicit defaults when deserializing JSON using Jackson

When deserializing multiple JSON messages, I want to specify a default value for attributes of a particular type. It is usually suggested that you simply specify the value in the class, but this is error prone if you need to do this in many classes. You can forget it and eventually use null instead of the default value. My intention is to set each property from Optional<T> to Optional.absent . Since null is exactly what Optional trying to eliminate, using them with Jackson has been frustrating.

Most of the Jackson features that allow you to customize the deserialization process focus on JSON, which is the input, rather than the process of creating the object you are serializing into. The closest thing is that I seem to get to a general solution by creating my own ValueInstantiator , but there are two remaining problems that I have:

  • How can I do this only for an Optional instance as absent , but not interfere with the rest of the instantiation process?
  • How to link the final result with my ObjectMapper ?

UPDATE: I want to clarify that I am looking for a solution that does not include modifying each class containing Optional . I am opposed to violating the principle of DRY. Me or my colleagues should not think about the need to do something extra every time we add Optional to a new or existing class. I want to be able to say: "To make each field" Optional "in each class I deserialized, pre-filled absent " only once and do with it.

This means the following:

  • abstract parent class (must be declared)
  • custom Builder / Creator / JsonDeserializer (annotation required for each applicable class)
  • mix in s? I tried this, combined with reflection, but I don’t know how to access the class I mixed into ...
+6
source share
3 answers

In particular, for java.lang.Optional, Jackson guys themselves have a module: https://github.com/FasterXML/jackson-datatype-jdk8

Guava Optional extends https://github.com/FasterXML/jackson-datatype-guava

It will create an Optional.absent parameter for null, but not for missing JSON values: - (.

See https://github.com/FasterXML/jackson-databind/issues/618 and https://github.com/FasterXML/jackson-datatype-jdk8/issues/2 .

So, you are stuck in initializing your options the same way you should initialize collections. This is good practice, so you should be able to enforce it.

 private Optional<Xxx> xxx = Optional.absent(); private List<Yyy> yyys = Lists.newArrayList(); 
+2
source

You can write your own deserializer to handle the default value. Effectively, you extend the appropriate deserializer for the type of object to be deserialized, get a deserialized value, and if it is null just returns the corresponding default value.

Here is a quick way to do this 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 ObjectMapper , you can register the module in an instance:

 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new DefaultStringModule()); 

To handle the default values ​​for fields not present in JSON, I usually saw this using the builder class, which will build the class using the provided values, and add the default values ​​for the missing fields. Then, in a deserialized class (e.g. MyClass ) add the @JsonDeserialize(builder = MyClass.Builder.class) annotation @JsonDeserialize(builder = MyClass.Builder.class) to tell Jackson to deserialize MyClass using the builder class.

+1
source

The value object should initialize these values ​​as missing. To ensure that default values ​​do not have zeros. The Guava module Optional handler really only needs to deserialize them as “missing” (even with explicit JSON values) and never be zeros with later versions. But since Jackson only works with JSON properties that exist (and not on things that may exist but don't have), POJO still needs a default assignment that is missing.

+1
source

Source: https://habr.com/ru/post/986181/


All Articles