使用Jackson JSON mapper对Java 8 java.time进行序列化/反序列化

358
如何在Java 8中使用Jackson JSON映射器处理LocalDateTime?
出现错误信息:org.codehaus.jackson.map.JsonMappingException: 无法从JSON字符串实例化类型为[简单类型,类java.time.LocalDateTime]的值;没有单一字符串构造函数/工厂方法(通过引用链:MyDTO["field1"]->SubDTO["date"])

5
你确定要将LocalDateTime映射为JSON吗?据我所知,JSON没有日期格式,虽然JavaScript使用ISO-8601。问题是,LocalDateTime没有时区...因此,如果你使用JSON作为发送日期/时间信息的媒介,如果客户端将缺少时区解释为默认的UTC(或其自己的时区),你可能会遇到麻烦。当然,如果这就是你想做的,那没问题。但是请检查一下是否考虑过改用ZonedDateTime。 - arcuri82
26个回答

383

100
哦,是的!我之前认为这不起作用是因为我没有意识到必须对映射器对象进行这些自定义操作之一:registerModule(new JSR310Module()) findAndRegisterModules()。请参见https://github.com/FasterXML/jackson-datatype-jsr310,如果您使用Spring框架,则以下是如何自定义映射器对象的方法:https://dev59.com/Omsz5IYBdhLWcg3wg4Cv。 - Alexander Taylor
17
我尝试使用最简单的OffsetDateTime,但无法运行。@Test public void testJacksonOffsetDateTimeDeserializer() throws IOException { ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); String json = "\"2015-10-20T11:00:00-8:30\""; mapper.readValue(json, OffsetDateTime.class); } - Abhijit Sarkar
9
如果你使用最新的Spring框架,就不需要自定义设置了,因为众所周知的Jackson模块现在会在检测到它们存在于类路径中时自动注册:https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring#jackson-modules。 - vorburger
55
JSR310Module已经有些过时了,建议改用JavaTimeModule。 - Jacob Eckel
48
我建议补充一点,除了使用 jackson-datatype-jsr310,你需要在对象映射器中注册 JavaTimeModule,即 objectMapper.registerModule(new JavaTimeModule());,在序列化和反序列化时都要注册。 - GrandAdmiral
显示剩余12条评论

121

如果您正在使用 fasterxml 的 ObjectMapper 类,那么默认情况下 ObjectMapper 不会理解 LocalDateTime 类,因此您需要在 gradle/maven 中添加另一个依赖项:

如果您正在使用 fasterxml 的 ObjectMapper 类,那么默认情况下 ObjectMapper 不会理解 LocalDateTime 类,因此您需要在 gradle/maven 中添加另一个依赖项:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'

现在您需要将该库提供的数据类型支持注册到对象映射器对象中,方法如下:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();

现在,在你的jsonString中,你可以像下面这样轻松地放入你的java.LocalDateTime字段:

{
    "user_id": 1,
    "score": 9,
    "date_time": "2016-05-28T17:39:44.937"
}

通过这样做,您的 Json 文件转换为 Java 对象将正常工作,您可以按照以下步骤读取文件:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });

21
在构建 ObjectMapper 时,findAndRegisterModules() 是我所缺少的关键部分。 - Xaero Degreaz
2
什么是Instant? - powder366
3
我还添加了这一行代码: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - Janet
2
以下是所需的完整代码: ObjectMapper objectMapper = new ObjectMapper(); objectMapper.findAndRegisterModules(); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - Sukh
我不得不添加这行代码以在反序列化阶段正确读取时区:mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false); - CᴴᴀZ
最终这是一个完美的答案,Spring Boot已经有了依赖项,使用对象映射器加载数据需要这个解决方案。 - anuj joshi

115

更新:出于历史原因,保留此答案,但我不建议使用。请参见上面接受的答案。

告诉Jackson使用您自定义的[反]序列化类进行映射:

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;

提供自定义类:

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    @Override
    public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
        arg1.writeString(arg0.toString());
    }
}

public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    @Override
    public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
        return LocalDateTime.parse(arg0.getText());
    }
}

随机事实:如果我嵌套上述类并且不将它们设为静态,错误消息就会很奇怪:org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported


1
截至目前,这里相对于其他答案的唯一优势是不依赖于FasterXML,无论那是什么。 - Alexander Taylor
4
FasterXML就是Jackson。http://fasterxml.comhttps://github.com/fasterxml/jackson - Matt Ball
4
我明白了。我正在处理的项目中有许多古老的pom条目。所以不再使用org.codehaus,现在全部改用com.fasterxml。谢谢! - Alexander Taylor
2
我也无法使用内置的,因为我需要传递格式化程序ISO_DATE_TIME。不确定在使用JSR310Module时是否可以这样做。 - isaac.hazan
截至本文撰写时,如果作为内部类使用,则它们都必须是静态类。而且它们都必须有一个无参构造函数。无论哪个出现问题,都会抱怨“没有参数的构造函数”。我猜这个包已经不再得到很好的维护,或者他们会提出另一个解决方案。 - Tiina
显示剩余3条评论

60

这个 Maven 依赖项将解决你的问题:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.4</version>
</dependency>

有一件事情困扰了我,那就是在反序列化过程中,ZonedDateTime的时区被更改为GMT。 事实证明,Jackson默认会用上下文环境中的一个时区替换它。 若要保留原来的时区,必须禁用此“功能”。

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)

6
非常感谢 DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE 提示。 - Andrej Urvantsev
8
为了使一切正常工作,除了禁用DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE之外,我还必须禁用SerializationFeature.WRITE_DATES_AS_TIMESTAMPS - Matt Watson

36

所有您需要知道的都在Jackson文档中。 https://www.baeldung.com/jackson-serialize-dates

Ad.9迅速为我解决了问题。

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

2
谢谢Dave,你救了我一命。在我的情况下,声明jackson-datatype-jsr310依赖项是不够的,你提到的三行代码解决了我的问题。感谢您发布参考资料。 - Francisco Acevedo

24

当我使用Spring Boot时,我遇到了类似的问题。 在使用Spring Boot 1.5.1.RELEASE时,我所需要做的就是添加依赖项:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

谢谢Witold。大家好,还要注意@DaveKraczo的帖子,我需要同时应用这两个建议来解决我的问题。谢谢你们俩。 - Francisco Acevedo

17

在较新版本的Jackson JSR中,例如registerModule(new JSR310Module())已被弃用,现在建议使用的是JavaTimeModule


import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonFactory {
    private static ObjectMapper objectMapper = null;
    public static ObjectMapper getObjectMapper() {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
        }
        return objectMapper;
    }
}

2
运行完美!添加objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)。 - Gaurav

13

如果由于任何原因无法使用jackson-modules-java8,则可以使用@JsonIgnore@JsonGetter@JsonSetter将即时字段序列化为long

public class MyBean {

    private Instant time = Instant.now();

    @JsonIgnore
    public Instant getTime() {
        return this.time;
    }

    public void setTime(Instant time) {
        this.time = time;
    }

    @JsonGetter
    private long getEpochTime() {
        return this.time.toEpochMilli();
    }

    @JsonSetter
    private void setEpochTime(long time) {
        this.time = Instant.ofEpochMilli(time);
    }
}

例子:

@Test
public void testJsonTime() throws Exception {
    String json = new ObjectMapper().writeValueAsString(new MyBean());
    System.out.println(json);
    MyBean myBean = new ObjectMapper().readValue(json, MyBean.class);
    System.out.println(myBean.getTime());
}
产量
{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z

这是一个非常干净的方法,允许你的类的用户使用任何他们想要的方式。 - Ashhar Hasan
这个程序将如何反序列化timezone信息? - CᴴᴀZ
java.time.Instant has no time zone - it's "An instantaneous point on the time-line." If you want to (de-)serialize TimeZone however you can do it in the same way as above by simply serializing TimeZone.getID() (annotated as @JsonGetter). For deserialization you can use TimeZone.getTimeZone(timeZoneId) (annotated as @JsonSetter). The methods get/setTimeZone() are - again - annotated as @JsonIgnore - Udo

10

如果你正在使用Jersey,那么你需要像其他人建议的那样添加Maven依赖项(jackson-datatype-jsr310),并像下面这样注册你的对象映射器实例:

@Provider
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {

  final ObjectMapper defaultObjectMapper;

  public JacksonObjectMapper() {
    defaultObjectMapper = createDefaultMapper();
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return defaultObjectMapper;
  }

  private static ObjectMapper createDefaultMapper() {
    final ObjectMapper mapper = new ObjectMapper();    
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}

在注册Jackson资源时,您需要像这样添加此映射器:

final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
  .register(JacksonObjectMapper.class)
  .register(JacksonJaxbJsonProvider.class);

7

如果您正在使用Jackson序列化器,那么以下是使用日期模块的方法:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.kafka.common.serialization.Serializer;

public class JacksonSerializer<T> implements Serializer<T> {

    private final ObjectMapper mapper = new ObjectMapper()
            .registerModule(new ParameterNamesModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule());

    @Override
    public byte[] serialize(String s, T object) {
        try {
            return mapper.writeValueAsBytes(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在我看来,带有e.printStackTrace()的代码片段不应该被发布。只需捕获异常并抛出即可。 - Dariusz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接