asieira
asieira

Reputation: 3683

Using Jackson ObjectMapper with Java 8 Optional values

I was trying to use Jackson to write a class value to JSON that has Optional as fields:

public class Test {
    Optional<String> field = Optional.of("hello, world!");

    public Optional<String> getField() {
        return field;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(new Test()));
    }
}

When executed, this class generates the following output:

{"field":{"present":true}}

I understand the present/not present field being included and could work around it when reading the JSON data, however I can't get around the fact that the actual content of the optional is never written to the output. :(

Any workarounds here except not using ObjectMapper at all?

Upvotes: 75

Views: 88379

Answers (7)

Jonas
Jonas

Reputation: 3020

You could use jackson-datatype-jdk8 which is described as:

Support for new JDK8-specific types, such as Optional

In order to do this:

  • add com.fasterxml.jackson.datatype:jackson-datatype-jdk8 as a dependency
  • register the module with your object mapper: objectMapper.registerModule(new Jdk8Module());

Upvotes: 79

cakraww
cakraww

Reputation: 2757

You only need to register module Jdk8Module. Then it will serialize any Optional<T> as T if it is present or null otherwise.

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());

Let's modify Test class a bit so we can test for an Optional.empty() value. This is similar to the original Test class when serialized because the object mapper is looking for the getter (since the field is private). Using the original Test class will work too, though.

class Test {
    private final String field;

    public Test(String field) {
        this.field = field;
    }

    public Optional<String> getField() {
        return Optional.ofNullable(field);
    }
}

Then in our main class:

Test testFieldNull = new Test(null);
Test testFieldNotNull = new Test("foo");

// output: {"field":null}
System.out.println(objectMapper.writeValueAsString(testFieldNull)); 

// output: {"field":"foo"}
System.out.println(objectMapper.writeValueAsString(testFieldNotNull)); 

Upvotes: 16

jiantongc
jiantongc

Reputation: 971

Try passing in options into @JsonInclude annotation.
For example, if you don't want to show field when the value is null. You might need to use Jackson-Modules >2.8.5.

import com.fasterxml.jackson.annotation.JsonInclude;

public class Test {
    @JsonInclude(JsonInclude.Include.NON_NULL)
    Optional<String> field;
}

Upvotes: 6

seenimurugan
seenimurugan

Reputation: 464

If you are using latest version of Spring-boot then you could achieve this by adding the following dependency in the pom file

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>

And auto wire the JacksonObjectMapper.

@Autowired
private ObjectMapper jacksonObjectMapper;

Then use the above Spring container's mapper instance to convert Object to String

jacksonObjectMapper.writeValueAsString(user);

Spring blog says :

Some well known Jackson modules are automatically registered if they are detected on the classpath:

  • jackson-datatype-jdk7: Java 7 types like java.nio.file.Path (as of 4.2.1 release)
  • jackson-datatype-joda: Joda-Time types
  • jackson-datatype-jsr310: Java 8 Date & Time API data types
  • jackson-datatype-jdk8: other Java 8 types like Optional (as of 4.2.0 release)

Upvotes: 3

mjj1409
mjj1409

Reputation: 3174

Similar to @Manikandan's answer but add @JsonProperty to the private field instead of a getter so you don't expose your work around on the public api.

public class Test {

    @JsonProperty("field")
    private String field;

    @JsonIgnore
    public Optional<String> getField() {
        return Optional.of(field); // or Optional.ofNullable(field);
    }
}

Upvotes: 17

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280102

The Optional class has a value field, but no standard getter/setter for it. By default, Jackson looks for getters/setters to find class properties.

You can add a custom Mixin to identify the field as a property

final class OptionalMixin {
    private Mixin(){}
    @JsonProperty
    private Object value;
}

and register it with your ObjectMapper.

ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Optional.class, OptionalMixin.class);

You can now serialize your object.

System.out.println(mapper.writeValueAsString(new Test()));

will print

{"field":{"value":"hello, world!","present":true}}

Consider also looking at jackson-datatype-guava. There's a Jackson Module implementation for Guava types including their Optional. It's possibly more complete than what I've shown above.

Upvotes: 15

Manikandan
Manikandan

Reputation: 3165

Define new getter which will return String instead of Optional.

public class Test {
    Optional<String> field = Optional.of("hello, world!");

    @JsonIgnore
    public Optional<String> getField() {
        return field;
    }

    @JsonProperty("field")
    public String getFieldName() {
        return field.orElse(null);
    }

}

Upvotes: 3

Related Questions