daydreamer daydreamer - 4 months ago 29
JSON Question

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

Answer

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