How to represent "unset" Java members so that they can be serialized unset in JSON with Jackson

Viewed 4798

I have a system where explicitly null fields are different from absent fields in JSON. What would be the best way to represent this in Java for Jackson serialization?

For example, I have a data object like

class Data {
  public String string;
  public String other;
}

By default, Jackson serializes null fields so new ObjectMapper().writeValueAsString(new Data()) results in {"string":null,"other":null}. However, I would like this to result in {} as the fields were "unset." Obviously, in java they are null, but I'm looking for a way around that.

If I were to do new ObjectMapper().wirteValueAsString(new Data(null, null)), I want that to result in {"string":null,"other":null}. And only the "string" field to result in {"string":"value"}.

The only way I can think of right now is to have booleans for each field and use a custom serializer to check the booleans but that seems pretty messy. Is there an easier way to do this?

Edit: This is not about not serializing null values. I want null values to be serialized if the field was explicitly set to null.

I thought maybe I could use Optionals and Include.NON_ABSENT, but unfortunately this does not work either as Jackson treats an Optional reference to null the same as Optional.empty().

4 Answers

This is how I solved it. My requirement is similar that I have one micro-service sending messages to another one.

  • Sometimes the producer will not know values of certain fields
  • Sometimes it would want to set the value to null
  • Sometimes it will have a value.

Producer Code:

// object Mapper
ObjectMapper mapper = new ObjectMapper()
               .registerModule(new Jdk8Module())
               .registerModule(new ParameterNamesModule())
               .registerModule(new JavaTimeModule());


// DTO
@Data
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
// employee object in producer
public class PEmployee {
    private Optional<String> name;
    private Optional<Integer> age;
    private Optional<BigDecimal> salary;
    private Optional<Boolean> active;
    private Optional<LocalDate> dateOfJoining;
}

// serialize before sending it
PEmployee emp = new PEmployee();
emp.setActive(Optional.ofNullable(Boolean.FALSE));
emp.setName(Optional.of("Johnson"));
emp.setDateOfJoining(Optional.empty());

// PEmployee(name=Optional[Johnson], age=null, 
//salary=null, active=Optional[false], dateOfJoining=Optional.empty)

String payload = mapper.writeValueAsString(emp);
// {"name":"Johnson","active":false,"dateOfJoining":null}

Consumer Code:

// DTO
@Data
@ToString
public class CEmployee {
    private Optional<String> name;
    private Optional<Integer> age;
    private Optional<BigDecimal> salary;
    private Optional<Boolean> active;
    private Optional<LocalDate> dateOfJoining;
}

// deserialize
CEmployee emp = mapper.readValue(payload, CEmployee.class);

// CEmployee(name=Optional[Johnson], age=null, 
// salary=null, active=Optional[false], dateOfJoining=Optional.empty)

Maven

    <dependency>
        <groupId>com.fasterxml.jackson.module</groupId>
        <artifactId>jackson-module-parameter-names</artifactId>
        <version>2.8.10</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jdk8</artifactId>
        <version>2.8.10</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.8.10</version>
    </dependency>

This way

  • getName().isPresent() - indicates that sender knew a value
  • getAge() == null - indicates that sender did not send any value
  • !getDateOfJoining().isPresent() - indicates that sender wants to set it to null
Related