web-dev-qa-db-ja.com

ジャクソン:カスタムオフセット日付時刻を解析します

タイムスタンププロパティを持つモデルがあります。

class Model {
    @JsonProperty("timestamp")
    private OffsetDateTime timestamp;
}

タイムスタンプの形式は次のとおりです。

2017-09-17 13:45:42.710576+02

OffsetDateTimeはこれを解析できません:

com.fasterxml.jackson.databind.exc.InvalidFormatException:タイプJava.time.OffsetDateTimeの値を文字列 "2017-09-17 13:45:42.710576 + 02"からデシリアライズできません:テキスト '2017-09-17 13:45: 42.710576 + 02 'はインデックス10で解析できませんでした

どうすれば修正できますか?

7
Kwers T

日付の形式はジャクソンに伝えなければなりません。基本的には、year-month-dayの後にhour:minute:second.microsecondsが続き、2桁のオフセット(+02)が続きます。したがって、パターンは次のようになります。

@JsonProperty("timestamp")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSSx")
private OffsetDateTime timestamp;

より詳細な説明については すべての日付/時刻パターン を見てください。


OffsetDateTimeで同じオフセット(+02)を保持する場合は、 DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEオプションfalseに調整することを忘れないでください。

このオプションが(私のテストでは)trueに設定されている場合、結果はUTCに変換されます(ただし、実際には、Jacksonで構成されているタイムゾーンに変換されます)。

2017-09-17T11:45:42.710576Z

falseに設定すると、入力で使用されるオフセットが保持されます。

2017-09-17T13:45:42.710576 + 02:00


上記のコードは、小数点以下6桁で機能します。ただし、この数が異なる場合は、[]で区切られたオプションのパターンを使用できます。

例:入力に6桁または3桁の10進数がある場合、pattern = "yyyy-MM-dd HH:mm:ss.[SSSSSS][SSS]x"を使用できます。オプションのセクション[SSSSSS]および[SSS]は、パーサーに6桁または3桁を考慮するように指示します。

オプションのパターンの問題は、シリアル化するとすべてのパターンが印刷されることです(そのため、秒の小数部が2回印刷されます:6およびと3数字)。


別の方法は、カスタムシリアライザーとデシリアライザーを作成することです(com.fasterxml.jackson.databind.JsonSerializercom.fasterxml.jackson.databind.JsonDeserializerを拡張することにより):

public class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {

    private DateTimeFormatter formatter;

    public CustomDeserializer(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    @Override
    public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        return OffsetDateTime.parse(parser.getText(), this.formatter);
    }
}

public class CustomSerializer extends JsonSerializer<OffsetDateTime> {

    private DateTimeFormatter formatter;

    public CustomSerializer(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    @Override
    public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
        gen.writeString(value.format(this.formatter));
    }
}

次に、それらをJavaTimeModuleに登録できます。これを構成する方法は、使用している環境によって異なります(例:Springでは xml files で構成できます)。例として、プログラムでそれを実行します。

まず、Java.time.format.DateTimeFormatterBuilderを使用してフォーマッタを作成します。

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // date/time
    .appendPattern("yyyy-MM-dd HH:mm:ss")
    // optional fraction of seconds (from 0 to 9 digits)
    .optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd()
    // offset
    .appendPattern("x")
    // create formatter
    .toFormatter();

このフォーマッタは、0〜9桁のオプションの秒の小数部を受け入れます。次に、上記のカスタムクラスを使用して、それらをObjectMapperに登録します。

// set formatter in the module and register in object mapper
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(OffsetDateTime.class, new CustomSerializer(formatter));
module.addDeserializer(OffsetDateTime.class, new CustomDeserializer(formatter));
mapper.registerModule(module);

また、フィールドから@JsonFormatアノテーションを削除します。

@JsonProperty("timestamp")
private OffsetDateTime timestamp;

そして今、それは2017-09-17 13:45:42+02(秒の小数部なし)や2017-09-17 13:45:42.71014+02(5桁の10進数)などの値を受け入れます。 0から9桁の10進数(9はAPIでサポートされている最大値)を解析でき、シリアライズ時にまったく同じ数を出力します。


上記の代替方法は、カスタムクラスでフォーマッターを設定できるため、非常に柔軟です。ただし、すべてのOffsetDateTimeフィールドのシリアル化と逆シリアル化も設定します。

それが必要ない場合は、固定フォーマッタを使用してクラスを作成することもできます。

static class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {

    private DateTimeFormatter formatter = // create formatter as above

    // deserialize method is the same
}

static class CustomSerializer extends JsonSerializer<OffsetDateTime> {

    private DateTimeFormatter formatter = // create formatter as above

    // serialize method is the same
}

次に、アノテーションcom.fasterxml.jackson.databind.annotation.JsonSerializeおよびcom.fasterxml.jackson.databind.annotation.JsonDeserializeを使用して、必要なフィールドのみにそれらを追加できます。

@JsonProperty("timestamp")
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private OffsetDateTime timestamp;

これにより、カスタムシリアライザーをモジュールに登録する必要がなくなり、注釈付きフィールドのみがカスタムクラスを使用します(他のOffsetDateTimeフィールドはデフォルト設定を使用します)。

14
user7605325