jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Inconsistency between @JsonInclude and ObjectMapper.setDefaultPropertyInclusion() for default values

Open radulecu opened this issue 2 years ago • 0 comments

Describe the bug There is an inconsistency in how @JsonInclude and ObjectMapper.setDefaultPropertyInclusion(). If you use @JsonInclude you can use custom default values and for those defaults the value will be removed at serialisation. If you use ObjectMapper.setDefaultPropertyInclusion() it will consider as default values 0 for numbers and empty string instead those you define.

Version information 2.13.3

To Reproduce

public class JsonIncludeNonDefaults {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper objectMapper =  new ObjectMapper();
        objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
        System.out.println(objectMapper.writer().writeValueAsString(new A(0,"")));
        System.out.println(objectMapper.writer().writeValueAsString(new A(1,"value")));
        System.out.println(objectMapper.writer().writeValueAsString(new A(2,"default")));
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
//    @JsonInclude(JsonInclude.Include.NON_DEFAULT) // uncomment for version with annotation
    private static class A{
        private int i=2;
        private String s="default";
    }
}

Result with @JsonInclude:

{"i":0,"s":""}
{"i":1,"s":"value"}
{}

Here the default values are 2 and "default" and those are not included in the serialisation.

Result without @JsonInclude that uses the default value set by objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);':

{}
{"i":1,"s":"value"}
{"i":2,"s":"default"}

Here it completely ignores your default values and it uses 0 and empty String which is not as expected (you want to use your defined default values).

I believe the issue is in Property builder where _useRealPropertyDefaults is true only for annotation version.

    public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc) {
        this._config = config;
        this._beanDesc = beanDesc;
        Value inclPerType = Value.merge(beanDesc.findPropertyInclusion(Value.empty()), config.getDefaultPropertyInclusion(beanDesc.getBeanClass(), Value.empty()));
        this._defaultInclusion = Value.merge(config.getDefaultPropertyInclusion(), inclPerType);
        this._useRealPropertyDefaults = inclPerType.getValueInclusion() == Include.NON_DEFAULT;
        this._annotationIntrospector = this._config.getAnnotationIntrospector();
    }

Expected behavior Both @JsonInclude and ObjectMapper.setDefaultPropertyInclusion() should be equivalent and both should take into account your default value. The purpose for default is to define a programatic way to configure when you don't have access to the class (e.g. the class is generated).

radulecu avatar Jun 22 '22 09:06 radulecu