在POST请求中反序列化LocalDateTime时出错

时间:2018-11-03 16:26:52

标签: java rest spring-boot jackson

我有一个包含两个LocalDateTime成员的类:

public class Foo
{
    private final LocalDateTime start;
    private final LocalDateTime end;

    public Foo(LocalDateTime start, LocalDateTime end)
    {
        this.start = start;
        this.end = end;
    }
}

我有一个Spring Boot控制器来处理POST请求:

@PostMapping(value = "/my-resource")
public ResponseEntity<?> bar(@RequestBody Foo foo)
{
   return ResponseEntity.ok().build();
}

如果我发送POST请求,如下所示:

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]
}
'

返回以下错误:

"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]"

经过一些挖掘,我向Foo添加了一个默认的ctor,从而解决了该错误。我还必须从成员中删除final

我不明白为什么该解决方案有效。有人可以解释吗?

2 个答案:

答案 0 :(得分:1)

@JsonCreator注释构造函数:

@JsonCreator
public Foo(LocalDateTime start, LocalDateTime end) {
    this.start = start;
    this.end = end;
}

一旦日期使用一种非常特殊的格式,您将需要编写一个自定义的反序列化器来处理它们。


如果您坚持使用ISO 8601,则可以依靠JavaTimeModule:它将为您提供serializers { {3}}。

查看详细信息deserializers

答案 1 :(得分:0)

Jackson使用反射来实例化Foo类,并且该机制要求存在默认构造函数。

在实例化Foo类之后,再次使用反射,Jackson将设置该实例的字段值-当前所有字段值均为空。由于您声明了这些字段final,因此无法对其进行修改。

这里是一个例子:

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 + "]";
    }

}

Jackson的序列化/反序列化机制非常灵活。您可以设置和配置很多注释。

这里是另一个示例:

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

上面的示例不起作用,因为age字段没有访问器方法。为了克服这个问题,我们可以添加:

mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

上面的示例现在可以正常工作。

@JB Nizet在评论中指出,here是关于杰克逊的非常好的文档。