如何使用 objectMapper 设置 java.time.Instant 的字符串格式?
How to set format of string for java.time.Instant using objectMapper?
我有一个实体 java.time.Instant
用于创建数据字段:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Item {
private String id;
private String url;
private Instant createdDate;
}
我正在使用 com.fasterxml.jackson.databind.ObjectMapper
将项目保存到 Elasticsearch 为 JSON:
bulkRequestBody.append(objectMapper.writeValueAsString(item));
ObjectMapper
将这个字段序列化为一个对象:
"createdDate": {
"epochSecond": 1502643595,
"nano": 466000000
}
我正在尝试注释 @JsonFormat(shape = JsonFormat.Shape.STRING)
但它对我不起作用。
我的问题是如何将此字段序列化为 2010-05-30 22:15:52
字符串?
您需要添加以下依赖
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
然后如下注册模块:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
一种解决方案是使用 jackson-modules-java8。然后你可以添加一个 JavaTimeModule
到你的对象映射器:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
默认情况下,Instant
被序列化为纪元值(单个数字中的秒和纳秒):
{"createdDate":1502713067.720000000}
您可以通过在对象映射器中设置来更改它:
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
这将产生输出:
{"createdDate":"2017-08-14T12:17:47.720Z"}
以上两种格式均已反序列化,无需任何额外配置。
要更改序列化格式,只需在字段中添加 JsonFormat
注释:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Instant createdDate;
你需要设置时区,否则Instant
不能正常序列化(会抛出异常)。输出将是:
{"createdDate":"2017-08-14 12:17:47"}
如果您不想(或不能)使用 java8 模块,另一种选择是创建自定义序列化器和反序列化器,使用 java.time.format.DateTimeFormatter
:
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
}
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Instant.from(fmt.parse(p.getText()));
}
}
然后你用这些自定义注释字段 类:
@JsonDeserialize(using = MyCustomDeserializer.class)
@JsonSerialize(using = MyCustomSerializer.class)
private Instant createdDate;
输出将是:
{"createdDate":"2017-08-14 12:17:47"}
一个细节是,在序列化字符串中,您丢弃了秒的小数部分(小数点后的所有内容)。因此,在反序列化时,此信息无法恢复(它将被设置为零)。
在上面的例子中,原来的Instant
是2017-08-14T12:17:47.720Z
,但是序列化后的字符串是2017-08-14 12:17:47
(没有秒的小数部分),所以反序列化后的结果是Instant
是 2017-08-14T12:17:47Z
(丢失 .720
毫秒)。
对于那些希望解析 Java 8 个时间戳的人。您需要在 POM 中使用最新版本的 jackson-datatype-jsr310
并注册以下模块:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
测试此代码
@Test
void testSeliarization() throws IOException {
String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));
// serialization
assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);
// deserialization
assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}
这里有一些格式化的 Kotlin 代码Instant
,所以它不包含毫秒,你可以使用自定义日期格式化程序
ObjectMapper().apply {
val javaTimeModule = JavaTimeModule()
javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
registerModule(javaTimeModule)
disable(WRITE_DATES_AS_TIMESTAMPS)
}
private class Iso8601WithoutMillisInstantSerializer
: InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())
在我的例子中,注册 JavaTimeModule 就足够了:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
messageObject = objectMapper.writeValueAsString(event);
在事件对象中,我有一个 Instant 类型的字段。
在反序列化中还需要注册java时间模块:
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
Event event = objectMapper.readValue(record.value(), Event.class);
您可以使用 Spring 已经配置了 JavaTimeModule 的 ObjectMapper。只需从 Spring 上下文注入它,不要使用 new ObjectMapper()
.
如果使用 Spring,并且 spring-web
在类路径中,您可以使用 Jackson2ObjectMapperBuilder
创建一个 ObjectMapper
。它在方法registerWellKnownModulesIfAvailable
.
中注册了以下常用模块
com.fasterxml.jackson.datatype.jdk8.Jdk8Module
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
com.fasterxml.jackson.datatype.joda.JodaModule
com.fasterxml.jackson.module.kotlin.KotlinModule
其中一些模块已合并到 Jackson 3 中;参见 here。
我有一个实体 java.time.Instant
用于创建数据字段:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Item {
private String id;
private String url;
private Instant createdDate;
}
我正在使用 com.fasterxml.jackson.databind.ObjectMapper
将项目保存到 Elasticsearch 为 JSON:
bulkRequestBody.append(objectMapper.writeValueAsString(item));
ObjectMapper
将这个字段序列化为一个对象:
"createdDate": {
"epochSecond": 1502643595,
"nano": 466000000
}
我正在尝试注释 @JsonFormat(shape = JsonFormat.Shape.STRING)
但它对我不起作用。
我的问题是如何将此字段序列化为 2010-05-30 22:15:52
字符串?
您需要添加以下依赖
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
然后如下注册模块:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
一种解决方案是使用 jackson-modules-java8。然后你可以添加一个 JavaTimeModule
到你的对象映射器:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
默认情况下,Instant
被序列化为纪元值(单个数字中的秒和纳秒):
{"createdDate":1502713067.720000000}
您可以通过在对象映射器中设置来更改它:
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
这将产生输出:
{"createdDate":"2017-08-14T12:17:47.720Z"}
以上两种格式均已反序列化,无需任何额外配置。
要更改序列化格式,只需在字段中添加 JsonFormat
注释:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Instant createdDate;
你需要设置时区,否则Instant
不能正常序列化(会抛出异常)。输出将是:
{"createdDate":"2017-08-14 12:17:47"}
如果您不想(或不能)使用 java8 模块,另一种选择是创建自定义序列化器和反序列化器,使用 java.time.format.DateTimeFormatter
:
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
}
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Instant.from(fmt.parse(p.getText()));
}
}
然后你用这些自定义注释字段 类:
@JsonDeserialize(using = MyCustomDeserializer.class)
@JsonSerialize(using = MyCustomSerializer.class)
private Instant createdDate;
输出将是:
{"createdDate":"2017-08-14 12:17:47"}
一个细节是,在序列化字符串中,您丢弃了秒的小数部分(小数点后的所有内容)。因此,在反序列化时,此信息无法恢复(它将被设置为零)。
在上面的例子中,原来的Instant
是2017-08-14T12:17:47.720Z
,但是序列化后的字符串是2017-08-14 12:17:47
(没有秒的小数部分),所以反序列化后的结果是Instant
是 2017-08-14T12:17:47Z
(丢失 .720
毫秒)。
对于那些希望解析 Java 8 个时间戳的人。您需要在 POM 中使用最新版本的 jackson-datatype-jsr310
并注册以下模块:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
测试此代码
@Test
void testSeliarization() throws IOException {
String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));
// serialization
assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);
// deserialization
assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}
这里有一些格式化的 Kotlin 代码Instant
,所以它不包含毫秒,你可以使用自定义日期格式化程序
ObjectMapper().apply {
val javaTimeModule = JavaTimeModule()
javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
registerModule(javaTimeModule)
disable(WRITE_DATES_AS_TIMESTAMPS)
}
private class Iso8601WithoutMillisInstantSerializer
: InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())
在我的例子中,注册 JavaTimeModule 就足够了:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
messageObject = objectMapper.writeValueAsString(event);
在事件对象中,我有一个 Instant 类型的字段。
在反序列化中还需要注册java时间模块:
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
Event event = objectMapper.readValue(record.value(), Event.class);
您可以使用 Spring 已经配置了 JavaTimeModule 的 ObjectMapper。只需从 Spring 上下文注入它,不要使用 new ObjectMapper()
.
如果使用 Spring,并且 spring-web
在类路径中,您可以使用 Jackson2ObjectMapperBuilder
创建一个 ObjectMapper
。它在方法registerWellKnownModulesIfAvailable
.
com.fasterxml.jackson.datatype.jdk8.Jdk8Module
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
com.fasterxml.jackson.datatype.joda.JodaModule
com.fasterxml.jackson.module.kotlin.KotlinModule
其中一些模块已合并到 Jackson 3 中;参见 here。