3

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().

oberlies
  • 11,503
  • 4
  • 63
  • 110
Panda
  • 877
  • 9
  • 21
  • Possible duplicate of [How to tell Jackson to ignore a field during serialization if its value is null?](https://stackoverflow.com/questions/11757487/how-to-tell-jackson-to-ignore-a-field-during-serialization-if-its-value-is-null) – Sharon Ben Asher Jul 30 '17 at 06:56
  • Look at the `@JsonInclude` annotation. – shmosel Jul 30 '17 at 06:58
  • I *want* to serialize null if it was explicitly set to null (not just initialized to null). I think perhaps Include.NON_ABSENT might work, if I wrap the fields in Optional. Can I tell Jackson to take the value out of the optional? – Panda Jul 30 '17 at 07:06

4 Answers4

6

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
maniB
  • 201
  • 3
  • 7
  • tested with 2.9.6 version with only jdk8 module, the Optional worked as expected. no need of parameter name module – Qianlong Sep 08 '20 at 08:44
0

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

That is the default behavior of ObjectMapper without any additional configuration. E.g. below are the code snippet and output:

Data data = new Data();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(data));

{"string":null,"other":null}

Now, if you only want to include not null fields (or set fields) then you can do it via two ways:

  • At class level, by annotating class with @JsonInclude(Include.NON_NULL), this will only apply to Data class
  • At ObjectMapper level, by setting mapper.setSerializationInclusion(Include.NON_NULL);, this will apply to all the classes

Once done, you will get the following:

Data data = new Data("value", null);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(data));

{"string":"value"}
Darshan Mehta
  • 30,102
  • 11
  • 68
  • 102
  • Sorry, I think I have explained myself poorly. In your last example, I would want that to result in `{"string":"value", "other":null}`. Because I explicitly set it to null, rather than leaving it default. – Panda Jul 30 '17 at 07:13
  • @grandmind1 Jackson will by default print both the fields without any `JsonInclude` configuration. – Darshan Mehta Jul 30 '17 at 07:15
  • Right, but it will serialize the null fields whether Java initialized them to null or I *explicitly* set them to null. I'm thinking I need to either get Optional working right or come up with default values that aren't null (because obviously Jackson can't tell the difference.) The issue is that for primitives, null, 0, and non-zero are all valid values so I don't know what to use for a default value. – Panda Jul 30 '17 at 07:19
  • In that case, have a look at [this](https://stackoverflow.com/questions/18828362/jackson-json-mapper-no-field-or-empty-field) answer. Also, I would not recommend using primitives in the class being deserialised as it will throw an `NPE` is corresponding value is `null` in json. – Darshan Mehta Jul 30 '17 at 07:26
0

You will not achieve that only by configuring jackson.

You are saying that you want to differentiate a null being set by your logic from a null being just the default init value. That's something your code will need to handle: how to differentiate these 2 scenarios. If you are using setters this can be as easy as to have flags for each field with true/false (you could have other less cumbersome approaches, but out of the scope of this question).

Once you have your flags implemented, write your own serializer for your classes that handles this flag appropiately.

In any case, i would just not try to differentiate this "2 kind of nulls" unless you are trying to do partial updates on a json or things like that. Explicit nulls vs Implicit nulls is something problematic IMO.

albert_nil
  • 1,648
  • 7
  • 9
  • I am actually trying to do partial updates. Thanks for the answer, I will look for a different approach. – Panda Jul 30 '17 at 07:42
-2

It can be done with JsonInclude annotation with Include.NON_NULL value

@JsonInclude(Include.NON_NULL)
class IgnoreNullFields
{
  String someString;
}

someString will be included only if its value is different from null

Cheers

Carlo Bertuccini
  • 19,615
  • 3
  • 28
  • 39