Как установить формат строки для java.time.Instant с помощью 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
?
Ответы
Ответ 1
Одним из решений является использование jackson-modules-java8. Затем вы можете добавить JavaTimeModule
к вашему объекту mapper:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
По умолчанию Instant
сериализуется как значение эпохи (секунды и наносекунды в одном числе):
{"createdDate":1502713067.720000000}
Вы можете изменить это, установив в mapper:
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
миллисекунд).
Ответ 2
Для тех, кто ищет синтаксис Java 8. Вам нужна последняя версия jackson-datatype-jsr310
в вашем POM и зарегистрирован следующий модуль:
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);
}
Ответ 3
Вы должны добавить ниже зависимость
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
Затем зарегистрируйте модули следующим образом:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Ответ 4
Вот какой-то кодлин 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())
Ответ 5
В моем случае достаточно было зарегистрировать 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);
Ответ 6
Вы можете использовать Spring ObjectMapper, который уже настроен с JavaTimeModule. Просто вставьте его из контекста Spring и не используйте new ObjectMapper()
.