Using Jackson ObjectMapper with Java 8 Optional values

JavaJacksonJava 8

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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.

Solution 3 - Java

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);
    }
}

Solution 4 - Java

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)); 

Solution 5 - Java

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;
}

Solution 6 - Java

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)

Solution 7 - Java

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);
    }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionasieiraView Question on Stackoverflow
Solution 1 - JavaJonasView Answer on Stackoverflow
Solution 2 - JavaSotirios DelimanolisView Answer on Stackoverflow
Solution 3 - Javamjj1409View Answer on Stackoverflow
Solution 4 - JavacakrawwView Answer on Stackoverflow
Solution 5 - JavajiantongcView Answer on Stackoverflow
Solution 6 - JavaseenimuruganView Answer on Stackoverflow
Solution 7 - JavaManikandanView Answer on Stackoverflow