[java] Setting default values to null fields when mapping with Jackson

I am trying to map some JSON objects to Java objects with Jackson. Some of the fields in the JSON object are mandatory(which I can mark with @NotNull) and some are optional.

After the mapping with Jackson, all the fields that are not set in the JSON object will have a null value in Java. Is there a similar annotation to @NotNull that can tell Jackson to set a default value to a Java class member, in case it is null?

Edit: To make the question more clear here is some code example.

The Java object:

class JavaObject {
    @NotNull
    public String notNullMember;

    @DefaultValue("Value")
    public String optionalMember;
}

The JSON object can be either:

{
    "notNullMember" : "notNull"
}

or:

{
    "notNullMember" : "notNull",
    "optionalMember" : "optional"
}

The @DefaultValue annotations is just to show what I am asking. It's not a real annotation. If the JSON object is like in the first example I want the value of the optionalMember to be "Value" and not null. Is there an annotation that does such a thing?

This question is related to java json jackson

The answer is


There are already a lot of good suggestions, but here's one more. You can use @JsonDeserialize to perform an arbitrary "sanitizer" which Jackson will invoke post-deserialization:

@JsonDeserialize(converter=Message1._Sanitizer.class)  
public class Message1 extends MessageBase
{
    public String string1 = "";
    public int integer1;

    public static class _Sanitizer extends StdConverter<Message1,Message1> {
        @Override
        public Message1 convert(Message1 message) {
            if (message.string1 == null) message.string1 = "";
            return message;
        }
    }
}

Make the member private and add a setter/getter pair. In your setter, if null, then set default value instead. Additionally, I have shown the snippet with the getter also returning a default when internal value is null.

class JavaObject {
    private static final String DEFAULT="Default Value";

    public JavaObject() {
    }

    @NotNull
    private String notNullMember;
    public void setNotNullMember(String value){
            if (value==null) { notNullMember=DEFAULT; return; }
            notNullMember=value;
            return;
    }

    public String getNotNullMember(){
            if (notNullMember==null) { return DEFAULT;}
            return notNullMember;
    }

    public String optionalMember;
}

You can create your own JsonDeserializer and annotate that property with @JsonDeserialize(as = DefaultZero.class)

For example: To configure BigDecimal to default to ZERO:

public static class DefaultZero extends JsonDeserializer<BigDecimal> {
    private final JsonDeserializer<BigDecimal> delegate;

    public DefaultZero(JsonDeserializer<BigDecimal> delegate) {
        this.delegate = delegate;
    }

    @Override
    public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return jsonParser.getDecimalValue();
    }

    @Override
    public BigDecimal getNullValue(DeserializationContext ctxt) throws JsonMappingException {
        return BigDecimal.ZERO;
    }
}

And usage:

class Sth {

   @JsonDeserialize(as = DefaultZero.class)
   BigDecimal property;
 }

Another option is to use InjectableValues and @JacksonInject. It is very useful if you need to use not always the same value but one get from DB or somewhere else for the specific case. Here is an example of using JacksonInject:

protected static class Some {
    private final String field1;
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectables() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    assertEquals(actualValuePresent.getField2(), "field2value");
}

Keep in mind that if you are using constructor to create the entity (this usually happens when you use @Value or @AllArgsConstructor in lombok ) and you put @JacksonInject not to the constructor but to the property it will not work as expected - value of the injected field will always override value in json, no matter whether you put useInput = OptBoolean.TRUE in @JacksonInject. This is because jackson injects those properties after constructor is called (even if the property is final) - field is set to the correct value in constructor but then it is overrided (check: https://github.com/FasterXML/jackson-databind/issues/2678 and https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333 for more information), this test is unfortunately passing:

protected static class Some {
    private final String field1;

    @JacksonInject(value = "defaultValueForField2", useInput = OptBoolean.TRUE)
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectablesIncorrectBehavior() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    // unfortunately "field2value" is overrided because of putting "@JacksonInject" to the field
    assertEquals(actualValuePresent.getField2(), "somedefaultValue");
}

Hope this helps to someone with a similar problem.

P.S. I'm using jackson v. 2.9.6


Looks like the solution is to set the value of the properties inside the default constructor. So in this case the java class is:

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

After the mapping with Jackson, if the optionalMember is missing from the JSON its value in the Java class is "Value".

However, I am still interested to know if there is a solution with annotations and without the default constructor.


I had a similar problem, but in my case the default value was in database. Below is the solution for that:

 @Configuration
 public class AppConfiguration {
 @Autowired
 private AppConfigDao appConfigDao;

 @Bean
 public Jackson2ObjectMapperBuilder builder() {
   Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
       .deserializerByType(SomeDto.class, 
 new SomeDtoJsonDeserializer(appConfigDao.findDefaultValue()));
   return builder;
 }

Then in SomeDtoJsonDeserializer use ObjectMapper to deserialize the json and set default value if your field/object is null.


Only one proposed solution keeps the default-value when some-value:null was set explicitly (POJO readability is lost there and it's clumsy)

Here's how one can keep the default-value and never set it to null

@JsonProperty("some-value")
public String someValue = "default-value";

@JsonSetter("some-value")
public void setSomeValue(String s) {
    if (s != null) { 
        someValue = s; 
    }
}

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to json

Use NSInteger as array index Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) HTTP POST with Json on Body - Flutter/Dart Importing json file in TypeScript json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 190) Angular 5 Service to read local .json file How to import JSON File into a TypeScript file? Use Async/Await with Axios in React.js Uncaught SyntaxError: Unexpected token u in JSON at position 0 how to remove json object key and value.?

Examples related to jackson

No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value How to convert JSON string into List of Java object? Deserialize Java 8 LocalDateTime with JacksonMapper java.lang.IllegalArgumentException: No converter found for return value of type java.lang.ClassNotFoundException: com.fasterxml.jackson.annotation.JsonInclude$Value How to modify JsonNode in Java? Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error Convert Map to JSON using Jackson Required request body content is missing: org.springframework.web.method.HandlerMethod$HandlerMethodParameter