Reputation: 4719
I have a class which contains two LocalDateTime members:
public class Foo
{
private final LocalDateTime start;
private final LocalDateTime end;
public Foo(LocalDateTime start, LocalDateTime end)
{
this.start = start;
this.end = end;
}
}
I have a Spring Boot controller to handle a POST request:
@PostMapping(value = "/my-resource")
public ResponseEntity<?> bar(@RequestBody Foo foo)
{
return ResponseEntity.ok().build();
}
If I send a POST request as follows:
curl -X POST \
http://localhost:8080/my-resource \
-H 'Content-Type: application/json' \
-d '{
"start":[2016, 1, 1, 10, 24],
"end":[2016, 1, 1, 10, 24]
}
'
the following error is returned:
"message": "Type definition error: [simple type, class Foo]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Foo` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 2, column: 2]"
After some digging around I added a default ctor to Foo and that resolved the error. I also had to remove final
from the members.
I don't understand why this solution works. Can someone explain this?
Upvotes: 1
Views: 128
Reputation: 131137
Simply annotate the constructor with @JsonCreator
:
@JsonCreator
public Foo(LocalDateTime start, LocalDateTime end) {
this.start = start;
this.end = end;
}
Once your dates use a very particular format, you'll need to write a custom deserializer to handle them.
If you stick to ISO 8601, you can rely on the JavaTimeModule
: It will provide you with a set of serializers and deserializers for JSR-310 datatypes.
See details here.
Upvotes: 1
Reputation:
Jackson uses reflection to instantiate the Foo
class and this mechanism requires the existence of a default constructor.
After instantiating the Foo
class, using reflection again, Jackson will set the field values of that instance - currently all field values are null. Since you declared those fields final
it fails to modify them.
Here is an example:
import java.lang.reflect.Field;
public class Reflection {
public static void main(String[] args) {
try {
Class<?> clazz = Child.class;
Object child = clazz.newInstance();
Field f1 = child.getClass().getDeclaredField("age");
// Because 'age' field is private
// we make it accesible
f1.setAccessible(true);
// Set the desired value
f1.set(child, 10);
System.out.println("Child: " + child);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Child {
private int age;
@Override
public String toString() {
return "Child [age=" + age + "]";
}
}
The Jackson's serialization/deserialization mechanism is very flexible. There are lots of annotations you can set and configuration you can make.
Here is another example:
public class Jackson {
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
String jsonAsString = "{\"age\":10}";
Child child = mapper.readValue(jsonAsString, Child.class);
System.out.println(child);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The above example doesn't work because age
field doesn't have accessor methods. To overcome this, we can add:
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
and the above example will be working now.
Very good documentation about Jackson can be found here as @JB Nizet pointed out in a comment.
Upvotes: 0