I have an Optional<Double>
field which I am trying to deserialize from a JSON, but getting the following Exception:
Cannot construct instance of `java.util.Optional`
(although at least one Creator exists):
no BigDecimal/double/Double-argument constructor/factory method
to deserialize from Number value (2.406)
My class:
public final class TestDims implements JsonTyped, Serializable {
private final Optional<Double> carMeters;
@JsonCreator
public TestDims(@JsonProperty("carMeters") Optional<Double> carMeters) {
this.carMeters = carMeters;
}
@JsonProperty("carMeters")
public Optional<Double> getCarMeters() {
return Optional.of(carMeters.orElse(2.2d));
}
}
Sample JSON:
{
"carMeters": 2.406
}
I would advise getting rid of the Optional.
Firstly, Optional
doesn't implement Serializable
hence your class is broken (because for an object to be serializable, the whole object graph should be serializable).
Secondly, using Optional
as a field is an antipattern, getters returning an optional are inconvenient, because instead of exposing the actual value they're forcing the caller to deal with an Optional.
A possible solution is to change the type of carMeters
to double
:
public final class TestDims implements JsonTyped, Serializable {
public static final double DEFAULT_METERS = 2.2;
private final double carMeters;
@JsonCreator
public TestDims(@JsonProperty("carMeters") double carMeters) {
this.carMeters = carMeters == 0 ? DEFAULT_METERS : carMeters;
}
@JsonProperty("carMeters")
public double getCarMeters() {
return carMeters;
}
}
main()
public static void main(String[] args) throws IOException {
String json1 = """
{
"carMeters": 2.406
}""";
String json2 = """
{
"carMeters": null
}""";
String json3 = """
{ }
""";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json1, TestDims.class));
System.out.println(mapper.readValue(json2, TestDims.class));
System.out.println(mapper.readValue(json3, TestDims.class));
}
Output:
TestDims{carMeters=2.406} // a regular value
TestDims{carMeters=2.2} // provided value is null
TestDims{carMeters=2.2} // the property is not present
If you want to use Optional as a field at all costs, then your class would not be serializable. There's no workaround.
To let the Jackson know how to deal with Optional, you would need to add a dependency for Jackson Datatype: JDK8 module, and register it.
If you're not using any framework, then you would need to register the module manually using ObjectMapper.registerModule()
before deserializing JSON.
With Spring Boot, it would be sufficient to add this module to the Spring's Context by using a method annotated with @Bean
.
@Bean
public Jdk8Module jdk8Module() {
return new Jdk8Module();
}
And it would be registered automatically while configuring ObjectMapper
.
Also note, that it doesn't make sense to use Optional<Double>
since there's a type OptionalDouble
wrapping a primitive.
public final static class TestDimsss implements JsonTyped {
public static final OptionalDouble DEFAULT_METERS = OptionalDouble.of(2.2D);
private final OptionalDouble carMeters;
@JsonCreator
public TestDimsss(@JsonProperty("carMeters") OptionalDouble carMeters){
this.carMeters = carMeters;
}
@JsonProperty("carMeters")
public OptionalDouble getCarMeters() {
return carMeters.isPresent() ? carMeters : DEFAULT_METERS;
}
@Override
public String toString() {
return "TestDimsss{" +
"carMeters=" + carMeters +
'}';
}
}
main()
public static void main(String[] args) throws IOException {
String json1 = """
{
"carMeters": 2.406
}""";
String json2 = """
{
"carMeters": null
}""";
String json3 = """
{ }
""";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
System.out.println(mapper.readValue(json1, TestDims.class));
System.out.println(mapper.readValue(json2, TestDims.class));
System.out.println(mapper.readValue(json3, TestDims.class));
}
Output:
TestDimsss{carMeters=OptionalDouble[2.406]} // a regular value
TestDimsss{carMeters=OptionalDouble.empty} // provided value is null
TestDimsss{carMeters=OptionalDouble.empty} // the property is not present