68

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?

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
asieira
  • 3,513
  • 3
  • 23
  • 23
  • 14
    Optionals are not meant to be used as fields (or properties for that matter). They should only be used as return values. – a better oliver Sep 06 '14 at 09:28
  • 15
    @zeroflagL could you please provide any credible sources describing what Optional is meant to be? – Jonas Dec 03 '14 at 12:54
  • 10
    @Jonas [this answer](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555) e.g.: _Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result"_. Also not implementing `Serializable` is pretty self-evident. – a better oliver Dec 04 '14 at 07:03
  • 9
    Even when considering `Optional` only as a return type, we might still want to put the returned `Optional` in a JSON representation. e.g. you have a service returning the language for a given text. The service method call returns an `Optional` and that's what you want in your json. – matthieus Jun 11 '15 at 15:10
  • [Using Optional with Jackson | Baeldung](https://www.baeldung.com/jackson-optional) – Jason Law May 08 '20 at 07:02

7 Answers7

72

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());
Vic Seedoubleyew
  • 9,888
  • 6
  • 55
  • 76
Jonas
  • 2,910
  • 2
  • 26
  • 36
  • 8
    As per the linked GitHub repository: `NOTE: This module has become part of Jackson Java 8 Modules as of Jackson 2.8.5 This repo still exists to allow release of patch versions of older versions; it will be hidden (made private) in near future.` – Carsten Nov 08 '16 at 08:22
  • 11
    You also need to register a module like ```ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module());``` – harsh Nov 08 '17 at 11:35
15

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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • That works, thank you. Is there a way to make that Mixin a default for all new mappers? – asieira Sep 07 '14 at 15:03
  • 1
    @asieira I believe you'll need to register `Module` which registers the mixin. You have to then create the mapper with this `Module`. (I don't know if that suits you better than only registering the mixin.) – Sotirios Delimanolis Sep 07 '14 at 15:05
15

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);
    }
}
mjj1409
  • 3,075
  • 6
  • 28
  • 28
13

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)); 
cakraww
  • 2,493
  • 28
  • 30
5

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;
}
jiantongc
  • 941
  • 1
  • 11
  • 14
3

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

}
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Manikandan
  • 3,025
  • 2
  • 19
  • 28
  • 11
    That would work, yes, but would defeat the purpose or returning an Optional value in the first place. – asieira Sep 07 '14 at 15:04
3

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)
seenimurugan
  • 464
  • 4
  • 19