Home > OS >  Why does ProjectionFactory convert nulls to empty Optionals?
Why does ProjectionFactory convert nulls to empty Optionals?

Time:10-27

Recently I updated my project from Spring Boot 2.1.0 to Spring Boot 2.5.6. Since then I see differences in JSON serialization.

To distinguish between cases (a) any value, (b) explicit null, and (c) no value I use java.util.Optional and Jackson's @JsonInclude(NON_NULL) annotation. Furthermore I use Spring Data JPA's projection pattern to define the JSON format as follows:

public interface MyProjection {

    @JsonInclude(Include.NON_NULL)
    Optional<String> getMyAttribute();
}

This worked perfect with Spring Boot 2.1.0.

Field value Rendered JSON
Optional.of("something") { "myAttribute": "something" }
Optional.empty() { "myAttribute": null }
null {}

As of now (after my update to Spring Boot 2.5.6) null is rendered like Optional.empty():

Field value Rendered JSON
Optional.of("something") { "myAttribute": "something" }
Optional.empty() { "myAttribute": null }
null { "myAttribute": null }

My first assumption was that Jackson's NON_NULL no longer works correctly with Optional. But that was not the case. As @Pedro said, it works correctly. After some investigation I found out that Spring's ProjectionFactory seems to handle null values different. It converts null to Optional.empty(), while it didn't do that formerly.

I'd like the old behavior back. Does anyone know how to prevent Spring from converting nulls to empty Optionals and instead keep it nulls? Is there a new default configuration? I didn't find any.

CodePudding user response:

After a few time of searching, i found the interested test case.

Here is the excerpt of test case with optional in jackson projects:

public void testConfigAbsentsAsNullsTrue() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(true));

        OptionalData data = new OptionalData();
        String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data);
        assertEquals("{}", value);
    }

    public void testConfigAbsentsAsNullsFalse() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(false));

        OptionalData data = new OptionalData();
        String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data);
        assertEquals("{\"myString\":null}", value);
    }

    class OptionalData {
        public Optional<String> myString = Optional.empty();
    }

Unit test Optional Link:

https://github.com/FasterXML/jackson-datatype-jdk8/blob/master/src/test/java/com/fasterxml/jackson/datatype/jdk8/TestConfigureAbsentsAsNulls.java

And there is a note configureAbsentsAsNulls. https://github.com/FasterXML/jackson-datatype-jdk8/blob/master/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8Module.java

For compatibility with older versions
     * of other "optional" values (like Guava optionals), it can be set to 'true'. The
     * default is `false` for backwards compatibility. 
public Jdk8Module configureAbsentsAsNulls(boolean state) {
    _cfgHandleAbsentAsNull = state;
    return this;
}

So you just need to set configureAbsentsAsNulls(true) to roll back your previous state.

CodePudding user response:

I have used the @JsonSerialize(include = Inclusion.NON_NULL) to solve the problem and it worked.

CodePudding user response:

There is a three-way to ignore the null field at 1). Field level 2). Class level 3). Globally

@JsonInclude(Include.NON_NULL) private String userName;

@JsonInclude(Include.NON_NULL)
 public class Book implements Comparable<Book> {
 private String title;
 private String author;
 private int price;

  public Book(String title, String author, int price) {
   this.title = title;
    this.author = author;
    this.price = price;
   }
  }


// let's create a book with author as null 
 Book book = new Book(null, null, 42);  
 ObjectMapper mapper = new ObjectMapper();   
 // configure ObjectMapper to exclude null fields whiel serializing 
 mapper.setSerializationInclusion(Include.NON_NULL);   
 String json =mapper.writeValueAsString(cleanCode); 
 System.out.println(json);
  • Related