【问题标题】:Error deserialising LocalDateTime in POST request在 POST 请求中反序列化 LocalDateTime 时出错
【发布时间】:2019-04-07 13:35:00
【问题描述】:

我有一个包含两个 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。

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

【问题讨论】:

标签: java rest spring-boot jackson


【解决方案1】:

简单地用@JsonCreator注解构造函数:

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

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


如果你坚持ISO 8601,你可以依赖JavaTimeModule:它将为你提供一组serializers和deserializers,用于JSR-310datatypes。

查看详情here。

【讨论】:

  • 那么,以这种方式注释 ctor 意味着我不需要默认 ctor?这是实现它的首选方式吗?
  • @ksl Jackson 如果您使用@JsonCreator,则不需要默认构造函数。
【解决方案2】:

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 可以找到关于 Jackson 的非常好的文档。

【讨论】:

  • 那么如果字段是私有的并且没有 setter,Jackson 如何修改这些字段呢?
  • @ksl 可以使用反射修改私有字段,假设没有 SecurityManager 阻止这一点。
猜你喜欢
  • 2021-01-19
  • 2019-06-27
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
  • 1970-01-01
  • 2019-03-16
  • 1970-01-01
相关资源
最近更新 更多