daydreamer
daydreamer

Reputation: 92019

jackson does not read back data from json when fields are Optional

I am using Java 8 to perform this task. I also following dependency work with JDK8 datatypes.

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

I have a class that looks like

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Optional;

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Optional<Address> address;
    private Optional<String> phone;

    private Person() {
    }

    public Person(String firstName, String lastName, int age) {
        this(firstName, lastName, age, Optional.empty(), Optional.empty());
    }

    public Person(String firstName, String lastName, int age,
                  Optional<Address> address, Optional<String> phone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.address = address;
        this.phone = phone;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    @JsonIgnore
    public Optional<Address> getAddress() {
        return address;
    }

    @JsonIgnore
    public Optional<String> getPhone() {
        return phone;
    }

    @JsonProperty("address")
    private Address getAddressForJson(){
        return address.orElse(null);
    }

    @JsonProperty("phone")
    private String getPhoneForJson() {
        return phone.orElse(null);
    }
}

and

public class Address {
    private String street;
    private String city;
    private String state;
    private int zip;
    private String country;

    public Address(String street, String city, String state, int zip, String country) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zip = zip;
        this.country = country;
    }

    public String getStreet() {
        return street;
    }

    public String getCity() {
        return city;
    }

    public String getState() {
        return state;
    }

    public int getZip() {
        return zip;
    }

    public String getCountry() {
        return country;
    }
}

I write a test to write a valid Person object to a file and and read it back to a Person object. My test is

@Test
    public void writeAndReadPersonAsJsonOnFile() throws Exception {
        Address address = new Address("1 Infinite Loop", "Cupertino", "CA", 95014, "USA");
        String phone = "1-800-My-Apple";
        Person person = new Person("john", "doe", 21, Optional.of(address), Optional.of(phone));
        ObjectMapper objectMapper = registerJdkModuleAndGetMapper();
        File file = temporaryFolder.newFile("person.json");
        objectMapper.writeValue(file, person);

        assertTrue(file.exists());
        assertTrue(file.length() > 0);

        Person personFromFile = objectMapper.readValue(file, Person.class);
        assertEquals(person, personFromFile);

    }

    private ObjectMapper registerJdkModuleAndGetMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        return objectMapper;
    }

The file created as part of test has following contents

{
    "firstName": "john",
    "lastName": "doe",
    "age": 21,
    "address": {
        "street": "1 Infinite Loop",
        "city": "Cupertino",
        "state": "CA",
        "zip": 95014,
        "country": "USA"
    },
    "phone": "1-800-My-Apple"
}

But when reading back, I get personFromFile which looks like following

personFromFile = {Person@1178} 
 firstName = "john"
 lastName = "doe"
 age = 21
 address = null
 phone = null

as you can see, the address and phone they both are null, even though they are present in the file.

What is wrong here?

UPDATE The codebase is https://github.com/101bits/java8-optional-json. This also contains the failing test

Upvotes: 0

Views: 397

Answers (2)

vaski thakur
vaski thakur

Reputation: 92

As far as i have read optional does not get serialized and hence, while deserializing you wont get the value if you are using default java serialization. However, if you are using your serialization, it should be fine.

Refer this link for more details: Why java.util.Optional is not Serializable, how to serialize the object with such fields

Upvotes: 0

jdgilday
jdgilday

Reputation: 886

Try marking one of the constructors with @JsonCreator to tell Jackson which constructor to use. Note: this also requires you to mark each of the constructor's parameters with @JsonProperty

You should use the @JsonCreator annotation when you want Jackson to constructor objects with a constructor or factory method as opposed letting Jackson use setters or public (non-final) fields

Additionally, your test will not pass until you override "equals" for both Person and Address

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Optional<Address> address;
    private Optional<String> phone;

    public Person(String firstName, String lastName, int age) {
        this(firstName, lastName, age, Optional.empty(), Optional.empty());
    }

    @JsonCreator
    public Person(
            @JsonProperty("firstName") String firstName,
            @JsonProperty("lastName") String lastName,
            @JsonProperty("age") int age,
            @JsonProperty("address") Optional<Address> address,
            @JsonProperty("phone") Optional<String> phone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.address = address;
        this.phone = phone;
    }

Update: Pull Request with passing tests

Upvotes: 2

Related Questions