web-dev-qa-db-fra.com

Comment définir le format de chaîne pour Java.time.Instant en utilisant objectMapper?

J'ai une entité avec Java.time.Instant pour le champ de données créé:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Item {
    private String id;
    private String url;
    private Instant createdDate;
}

J'utilise com.fasterxml.jackson.databind.ObjectMapper pour enregistrer l'élément dans Elasticsearch en tant que JSON:

bulkRequestBody.append(objectMapper.writeValueAsString(item));

ObjectMapper sérialise ce champ sous forme d'objet: 

"createdDate": {
    "epochSecond": 1502643595,
    "nano": 466000000
}

J'essayais l'annotation @JsonFormat(shape = JsonFormat.Shape.STRING) mais cela ne fonctionne pas pour moi.

Ma question est de savoir comment je pourrais sérialiser ce champ en tant que chaîne 2010-05-30 22:15:52?

9
Uladzislau Kaminski

Une solution consiste à utiliser jackson-modules-Java8 . Ensuite, vous pouvez ajouter une JavaTimeModule à votre mappeur d'objets:

ObjectMapper objectMapper = new ObjectMapper();

JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);

Par défaut, Instant est sérialisé en tant que valeur Epoch (secondes et nanosecondes dans un nombre unique):

{"createdDate":1502713067.720000000}

Vous pouvez changer cela en définissant dans le mappeur d'objets:

objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

Cela produira la sortie:

{"createdDate":"2017-08-14T12:17:47.720Z"}

Les deux formats ci-dessus sont désérialisés sans configuration supplémentaire.

Pour changer le format de la sérialisation, ajoutez simplement une annotation JsonFormat au champ:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Instant createdDate;

Vous devez définir le fuseau horaire, sinon la Instant ne peut pas être sérialisée correctement (une exception est générée). La sortie sera:

{"createdDate":"2017-08-14 12:17:47"}

Si vous ne voulez pas (ou ne pouvez pas) utiliser les modules Java8, vous pouvez également créer un sérialiseur et un désérialiseur personnalisés, à l’aide du 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()));
    }
}

Ensuite, vous annotez le champ avec ces classes personnalisées:

@JsonDeserialize(using = MyCustomDeserializer.class)
@JsonSerialize(using = MyCustomSerializer.class)
private Instant createdDate;

La sortie sera:

{"createdDate":"2017-08-14 12:17:47"}

Un détail est que, dans la chaîne sérialisée, vous supprimez la fraction de seconde (tout ce qui suit le point décimal). Ainsi, lors de la désérialisation, cette information ne peut pas être récupérée (elle sera mise à zéro).

Dans l'exemple ci-dessus, la Instant d'origine est 2017-08-14T12:17:47.720Z, mais la chaîne sérialisée est 2017-08-14 12:17:47 (sans la fraction de secondes). Ainsi, lors de la désérialisation, la Instant résultante est 2017-08-14T12:17:47Z (les .720 millisecondes sont perdues).

17
user7605325

Voici un code Kotlin de formatage Instant, de sorte qu'il ne contient pas des millisecondes, vous pouvez utiliser des formateurs de date personnalisés

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())
2
ZZ 5

Vous devez ajouter ci-dessous la dépendance

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

Et puis enregistrez les modules comme ci-dessous:

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

Pour ceux qui cherchent à analyser les timestamps Java 8. Vous avez besoin d'une version récente de jackson-datatype-jsr310 dans votre POM et le module suivant est enregistré:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

Pour tester ce code

@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);
}
2
UltimaWeapon

Dans mon cas, il suffisait d'enregistrer le JavaTimeModule:

  ObjectMapper objectMapper = new ObjectMapper();
  JavaTimeModule module = new JavaTimeModule();
  objectMapper.registerModule(module);

  messageObject = objectMapper.writeValueAsString(event);

Dans le cas objet, j'ai un champ de type Instant.

Dans la désérialisation, vous devez également enregistrer le module de temps Java:

ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());

Event event = objectMapper.readValue(record.value(), Event.class);
0
Laura Liparulo