Search code examples
javajacksonjackson-databind

Serialization of a read-only class ignoring nulls – without creating a custom ObjectMapper


Suppose, I want Jackson to ignore nulls when serializing this particular object. I tried this (keep in mind, though, that the actual class being serialized is read-only)

@RestController
public class SomeObjectController {
    @GetMapping("/some-object-with-no-nulls")
    @JsonInclude(content = JsonInclude.Include.NON_NULL)
    public Mono<SomeObject> getSomeObject() {
        return Mono.just(new SomeObject(null, "string"));
    }
    @NoArgsContructor
    @Getter
    @Setter
    public class SomeObject {
        private String firstField;
        private String secondField;
    }
}

However, it doesn't work

{
    firstField: null,
    secondField: "string"
}

The expected result is:

{
    secondField: "string"
}

I get what I want if I register a custom ObjectMapper. But I may not necessarily want that behavior for all my endpoints (even if they serve SomeObject too)

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }

I also would like to avoid writing a custom serializator. It may be possible, but I prefer declarative solutions (while realizing their drawbacks)

What should I do?


Solution

  • The cleaner solution is to to write custom serializer for SomeObject and register it with the existing instance of ObjectMapper.

    Since you don't want to do that, if SomeObject is extensible (non final), you can use inheritance and add @JsonInclude(JsonInclude.Include.NON_NULL) on your class.

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class SomeObjectInheritor extends SomeObject {
    
      public static SomeObjectInheritor fromParent(SomeObject someObject) {
        SomeObjectInheritor inheritor = new SomeObjectInheritor();
        inheritor.setFirstField(someObject.getFirstField());
        inheritor.setSecondField(someObject.getSecondField());
        return inheritor;
      }
    }
    

    You can replace SomeObject instances with SomeObjectInheritor, or you can use it only for the actual serialization.

    public class Main {
    
      public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        //use SomeObjectInheritor instead of SomeObject
        SomeObject someObject = new SomeObjectInheritor();
        someObject.setSecondField("val");
        System.out.println(mapper.writeValueAsString(someObject));
    
        //use SomeObjectInheritor only for the actual serialization
        SomeObject anotherObj = new SomeObject();
        anotherObj.setSecondField("second value");
        System.out.println(mapper.writeValueAsString(SomeObjectInheritor.fromParent(anotherObj)));
      }
    }
    

    Prints:

    {"secondField":"val"}
    

    for someObject and

    {"secondField":"second value"}
    

    for anotherObj respectively. In both cases the null property is not serialized.

    If SomeObject is final, I am afraid, that your only option is writing a serializer.