JSON Jacksonへの日付形式のマッピング


154

私はこのようなAPIから来る日付形式を持っています:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

YYYY-DD-MM HH:MM am / pm GMTタイムスタンプです。この値をPOJOの日付変数にマッピングしています。明らかに、その変換エラーを示しています。

2つのことを知りたいです。

  1. ジャクソンで変換を実行するために使用する必要があるフォーマットは何ですか?日付はこれに適したフィールドタイプですか?
  2. 一般に、ジャクソンによってオブジェクトメンバーにマップされる前に変数を処理する方法はありますか?形式、計算などの変更

これは本当に良い例です。クラスのフィールドに注釈を付けます:java.dzone.com/articles/how-serialize-javautildate
digz6666

今、彼らは日付処理のためのwikiページを持っています:wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

最近では、Dateクラスを使用しないでください。最新のJava日時APIであるjava.timeは、 5年近く前に置き換えられました。それとFasterXML / jackson-modules-java8を使用してください。
Ole VV

回答:


124

ジャクソンで変換を実行するために使用する必要があるフォーマットは何ですか?日付はこれに適したフィールドタイプですか?

Dateこれはファインフィールドタイプです。JSONを使用すると、JSONをかなり簡単に解析可能にすることができますObjectMapper.setDateFormat

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

一般に、ジャクソンによってオブジェクトメンバーにマップされる前に変数を処理する方法はありますか?形式、計算などの変更

はい。カスタムの実装(JsonDeserializer拡張など)を含むいくつかのオプションがありますJsonDeserializer<Date>これは良いスタートです。


2
形式にAM / PM指定も含まれている場合は、12時間形式の方が適しています。DateFormat df = new SimpleDateFormat( "yyyy-MM-dd hh:mm a z");
John Scattergood 2016

これらすべてのソリューションを試してみましたが、私のPOJOのDate変数をMapキー値に、Dateとしても格納できませんでした。これで、マップからBasicDbObject(MongoDB API)をインスタンス化し、その結果、変数をMongoDB DBコレクションに(LongまたはStringではなく)Dateとして格納します。これは可能ですか?ありがとう
RedEagle

1
それは同じようにJavaの8使いやすいですLocalDateTimeZonedDateTimeの代わりにDate?以来Date、基本的には非推奨(またはそのメソッドの少なくとも多くの)されて、私はそれらの選択肢を使用したいと思います。
houcros 2016年

setSateFormat()のjavadocによると、この呼び出しによりObjectMapperはスレッドセーフではなくなります。JsonSerializerクラスとJsonDeserializerクラスを作成しました。
MiguelMunoz 2018

質問はjava.util.Date明確に言及していないので、これはうまくいかないことを指摘したいと思いjava.sql.Date.ます。以下の私の回答も参照してください。
ブラスモンキー

329

Jackson v2.0以降では、@ JsonFormatアノテーションをObjectメンバーに直接使用できます。

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

61
タイムゾーンを含める場合:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK 2015年

こんにちは、どのjarにその注釈があります。jackson-mapper-asl 1.9.10バージョンを使用しています。
わかり

1
@Ramki:jackson-annotations> = 2.0
Olivier Lecrivain

3
この注釈はシリアライズ段階でのみ完全に機能しますが、デシリアライズ中はタイムゾーンとロケール情報はまったく使用されません。私は、locale = "hu"でtimezone = "CET"およびtimezone "Europe / Budapest"を試しましたが、どちらも機能せず、カレンダーで不自然な時間の会話を引き起こします。必要に応じてタイムゾーンを処理するために使用したのは、逆シリアル化を使用したカスタムシリアル化のみでした。これは、baeldung.com / jackson
Miklos Krivan '12

1
これは、Jackson Annotationsと呼ばれる別のjarプロジェクトです。pomエントリの
bub

52

もちろん、シリアライゼーションとデシリアライゼーションと呼ばれる自動化された方法がありpb2qでも言及されているように、特定のアノテーション(@JsonSerialize@JsonDeserialize)でそれを定義できます。

java.util.Dateとjava.util.Calendar ...の両方、そしておそらくJodaTimeも使用できます。

逆シリアル化シリアル化は完全に機能しました)中に@ JsonFormatアノテーションが希望どおりに機能しませんタイムゾーンが別の値に調整されました)。

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

予測結果が必要な場合は、@ JsonFormatアノテーションの代わりにカスタムシリアライザーとカスタムデシリアライザーを使用する必要があります。私はここで本当に良いチュートリアルと解決策を見つけましたhttp://www.baeldung.com/jackson-serialize-dates

日付フィールドの例がありますが、カレンダーフィールドに必要だったので、これが私の実装です。

シリアライザクラス:

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

デシリアライザのクラス:

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

上記のクラスの使用法

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

この実装を使用すると、シリアライゼーションおよびデシリアライゼーションプロセスを連続して実行すると、元の値が得られます。

@JsonFormatアノテーションのみを使用すると、デシリアライゼーションによって異なる結果が得られます。これは、ライブラリの内部タイムゾーンのデフォルト設定により、アノテーションパラメータでは変更できないものです(これは、Jacksonライブラリ2.5.3および2.6.3バージョンでも同様です)。


4
私は昨日私の回答に反対票を投じました。私はこのトピックについて多くの作業をしたので、理解できません。そこから学ぶためのフィードバックをいただけますか?反対票が出た場合のメモをお願いします。このようにして、お互いからより多くを学ぶことができます。
Miklos Krivan 2016年

すばらしい回答です。ありがとうございました。マイナーな提案-CustomCalendarSerializerとCustomCalendarDeserializerを、親クラス内の静的クラスとして配置することを検討してください。これにより、コードが少し良くなると思います:)
Stuart

@Stuart-このコードの再編成を別の回答として提供するか、編集を提案するだけです。ミクロスにはそれを行う時間がないかもしれません。
ocodo

@MiklosKrivan私はいくつかの理由であなたに反対票を投じました。SimpleDateFormatはスレッドセーフではないことを知っておく必要があります。一般的に、日付の書式設定には代替ライブラリ(Joda、Commons-lang FastDateFormatなど)を使用する必要があります。もう1つの理由は、タイムゾーンとロケールの設定です。シリアル化環境でGMTを使用し、それがどのタイムゾーンであるかをクライアントに通知するか、優先tzを別の文字列として付加することは、はるかに好ましいです。サーバーをGMT(別名UTC)に設定します。ジャクソンには、ISOフォーマットが組み込まれています。
Adam Gent

1
@AdamGentのフィードバックをお寄せください。私はあなたの提案を理解して受け入れます。しかし、この特定のケースでは、ロケール情報を含むJsonFormatアノテーションが期待どおりに機能しないことに焦点を当てたかっただけです。そして、どのように解決することができます。
Miklos Krivan 2016年

4

日時形式のスプリングブートアプリケーションの完全な例RFC3339

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}

4

日付にTやZなどの文字を追加するには

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

出力

{
    "currentTime": "2019-12-11T11:40:49Z"
}

3

私のために働いています。SpringBoot。

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

出力:

{ 
   "createTime": "2019-06-14 13:07:21"
}

2

@ miklov-krivenの非常に役立つ回答に基づいて、私はこれらの2つの追加の考慮点が誰かに役立つことを証明します:

(1)同じクラスの静的内部クラスとしてシリアライザとデシリアライザを含めるのは良い考えです。注:SimpleDateFormatのスレッドセーフのためにThreadLocalを使用します。

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2)個々のクラスメンバーで@JsonSerializeおよび@JsonDeserializeアノテーションを使用する代わりに、アプリケーションレベルでカスタムシリアル化を適用することにより、Jacksonのデフォルトのシリアル化をオーバーライドすることもできます。各フィールドに明示的な注釈なしでこのカスタムシリアル化を使用します。たとえばSpring Bootを使用している場合、これを行う1つの方法は次のようになります:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}

私はあなたに反対票を投じました(反対投票は嫌いですが、人々にあなたの答えを使わせたくないだけです)。SimpleDateFormatはスレッドセーフではありません。2016年です(2016年回答)。より高速でスレッドセーフなオプションが多数ある場合は、SimpleDateFormatを使用しないでください。:あなたはここでQ / Aを提案しているかの正確な誤用でもありstackoverflow.com/questions/25680728/...
アダム・ゲント

2
@AdamGentこれを指摘してくれてありがとう。このコンテキストでは、Jacksonを使用すると、ObjectMapperクラスはスレッドセーフであるため、重要ではありません。ただし、コードはコピーされ、スレッドセーフではないコンテキストで使用される可能性があることを、私はあなたの見解を示します。そのため、私は自分の回答を編集して、SimpleDateFormatスレッドへのアクセスを安全にしました。また、主にjava.timeパッケージなどの代替手段があることも認識しています。
スチュアート、

2

java.sql.Dateのカスタム日付形式の使用に問題がある場合、これが最も簡単な解決策です。

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(このSO-answerは私に多くのトラブルを救いました:https : //stackoverflow.com/a/35212795/3149048

ジャクソンはjava.sql.DateのデフォルトでSqlDateSerializerを使用しますが、現在、このシリアライザーはdateformatを考慮していません。この問題を参照してください:https : //github.com/FasterXML/jackson-databind/issues/1407。回避策は、コード例に示すように、java.sql.Dateに別のシリアライザーを登録することです。


1

SimpleDateFormat他の回答で説明されているような設定java.util.Dateは、質問で意図されているものに対してのみ機能することを指摘しておきます。しかしjava.sql.Date、フォーマッタは機能しません。私の場合、シリアル化する必要があるモデルではフィールドは実際にはa java.utl.Dateだったので、フォーマッタが機能しなかった理由はそれほど明確ではありませんでしたが、実際のオブジェクトは最終的にjava.sql.Dateでした。これは、

public class java.sql extends java.util.Date

これは実際に有効です

java.util.Date date = new java.sql.Date(1542381115815L);

したがって、日付フィールドが正しくフォーマットされていない理由がわからない場合は、オブジェクトが本当にであることを確認してくださいjava.util.Date

ここでは、処理java.sql.Dateが追加されない理由についても説明します。

そうなると、これは重大な変化となり、それが正当化されるとは思いません。私たちがゼロから始めた場合、私は変更に同意しますが、物事はそれほど多くありません。


1
2つの異なるDateクラスのこの悪い設計の1つの結果を指摘していただきありがとうございます。しかし最近では、それらのいずれもすべきではありませんSimpleDateFormat最新のJava日時APIであるjava.timeは、ほぼ5年前に置き換えられました。それとFasterXML / jackson-modules-java8を使用してください。
Ole VV
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.