GSON serialisieren Datum von json string in java.util.date nachrüsten

81

Ich verwende die Retrofit-Bibliothek für meine REST-Aufrufe. Das meiste, was ich getan habe, war butterweich, aber aus irgendeinem Grund habe ich Probleme beim Konvertieren von JSON-Zeitstempelzeichenfolgen in java.util.DateObjekte. Der JSON, der hereinkommt, sieht so aus.

{
    "date": "2013-07-16",
    "created_at": "2013-07-16T22:52:36Z",
} 

Wie kann ich Retrofit oder Gson anweisen, diese Zeichenfolgen in zu konvertieren java.util.Date objects?

jpotts18
quelle
Sieht aus wie ein Duplikat dieser Frage .
Davmrtl
@Davmrtl: Es ist nicht dasselbe, da es hier zwei verschiedene Datumsformate gibt. Es erfordert also einen anderen Ansatz.
Giampaolo

Antworten:

161
Gson gson = new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
    .create();

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(API_BASE_URL)
    .setConverter(new GsonConverter.create(gson))
    .build();

Oder das Kotlin-Äquivalent:

val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()
RestAdapter restAdapter = Retrofit.Builder()
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
    .create(T::class.java)

Sie können Ihren benutzerdefinierten Gson-Parser auf Nachrüstung einstellen. Mehr hier: Nachrüst-Website

Sehen Sie sich die Antwort von Ondreju an, um zu sehen, wie dies in Retrofit 2 implementiert werden kann.

gderaco
quelle
8
Bitte erläutern Sie Ihren Code kurz, damit er für OP und andere Leser nützlicher ist.
Mohit Jain
Wie kann ich das für alle meine Anfragen tun? Also zu meinem hinzufügen ServiceGenerator?
Zapnologica
4
Dies gilt für mich eigentlich nicht für das Gebietsschema. 2016-02-11T13:42:14.401Zwird zu einem Datumsobjekt deserialisiert, das für 13:42:14mein Gebietsschema bestimmt ist.
Alireza Mirian
Ich habe dies versucht. Heruntergeladene Elemente scheinen jetzt zu funktionieren. Aber wenn ich ein Json-Objekt auf dem Server poste. Wird es dann meine Zeitzone entfernen?
Zapnologica
Nach der Implementierung dieser Lösung habe ich so viel Zeit damit verbracht, Probleme beim Absturz meiner App ohne Grund zu erkennen. Als ich das Dete-Format kopierte und einfügte, änderte etwas ein Anführungszeichen von 'T' in 'T' - es gibt ein anderes - pass gut auf!
LukaszTaraszka
86

@ gderacos Antwort auf Nachrüstung 2.0 aktualisiert:

Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();

Retrofit retrofitAdapter = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Andrzej Purtak
quelle
2
Achten Sie beim Kopieren des Datumsformats auf Anführungszeichen - damit hatte ich ein Problem!
LukaszTaraszka
14

So habe ich es gemacht:

Erstellen Sie eine DateTime-Klasse, die Date erweitert, und schreiben Sie dann einen benutzerdefinierten Deserializer:

public class DateTime extends java.util.Date {

    public DateTime(long readLong) {
        super(readLong);
    }

    public DateTime(Date date) {
        super(date.getTime());
    }       
}

Nun zum Deserializer-Teil, in dem wir sowohl Date- als auch DateTime-Konverter registrieren:

public static Gson gsonWithDate(){
    final GsonBuilder builder = new GsonBuilder();

    builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
        @Override  
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return df.parse(json.getAsString());  
            } catch (final java.text.ParseException e) {  
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    builder.registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        @Override  
        public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return new DateTime(df.parse(json.getAsString()));  
            } catch (final java.text.ParseException e) {
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    return builder.create();
}

Gehen Sie beim Erstellen Ihres RestAdapters wie folgt vor:

new RestAdapter.Builder().setConverter(gsonWithDate());

Dein Foo sollte so aussehen:

class Foo {
    Date date;
    DateTime created_at;
}
Kuanysh Raimbekov
quelle
Vielen Dank! Ich musste nur von return df.parse(json.getAsString()); zu long timeStamp = Long.parseLong(json.getAsString()); return new java.util.Date(timeStamp);
Juan Saravia
Toll! Vielen Dank!
NickUnuchek
7

Gson kann nur ein Datum / Uhrzeit-Format (die im Builder angegebenen) sowie das iso8601 verarbeiten, wenn das Parsen mit einem benutzerdefinierten Format nicht möglich ist. Eine Lösung könnte also darin bestehen, Ihren benutzerdefinierten Deserializer zu schreiben. Um Ihr Problem zu lösen, habe ich definiert:

package stackoverflow.questions.q18473011;

import java.util.Date;

public class Foo {

    Date date;
    Date created_at;

    public Foo(Date date, Date created_at){
       this.date = date;
       this.created_at = created_at;
    }

    @Override
    public String toString() {
       return "Foo [date=" + date + ", created_at=" + created_at + "]";
    }

}

mit diesem Deserializer:

package stackoverflow.questions.q18473011;

import java.lang.reflect.Type;
import java.text.*;
import java.util.Date;

import com.google.gson.*;

public class FooDeserializer implements JsonDeserializer<Foo> {

     public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        String a = json.getAsJsonObject().get("date").getAsString();
        String b = json.getAsJsonObject().get("created_at").getAsString();

        SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdfDateWithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        Date date, created;
        try {
           date = sdfDate.parse(a);
           created = sdfDateWithTime.parse(b);
        } catch (ParseException e) {
           throw new RuntimeException(e);
        }

        return new Foo(date, created);
    }

}

Der letzte Schritt besteht darin, eine GsonInstanz mit dem richtigen Adapter zu erstellen :

package stackoverflow.questions.q18473011;

import com.google.gson.*;

public class Question {

    /**
     * @param args
     */
    public static void main(String[] args) {
      String s = "{ \"date\": \"2013-07-16\",    \"created_at\": \"2013-07-16T22:52:36Z\"}";


      GsonBuilder builder = new GsonBuilder();
      builder.registerTypeAdapter(Foo.class, new FooDeserializer());

      Gson gson = builder.create();
      Foo myObject = gson.fromJson(s, Foo.class);

      System.out.println("Result: "+myObject);
    }

}

Mein Ergebnis:

Result: Foo [date=Tue Jul 16 00:00:00 CEST 2013, created_at=Tue Jul 16 22:52:36 CEST 2013]
Giampaolo
quelle
1
Was ist, wenn ich 15-20 Felder in Foo habe (einschließlich benutzerdefinierter Objekte), muss ich sie manuell deserialisieren, richtig. Das tötet dann den Zweck von Gson.
Farid
1

Wenn Sie in der Klasse, die Sie erstellen, bereits ein Date-Objekt mit dem Namen "created_at" haben, ist dies ganz einfach:

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").create();
YourObject parsedObject1 = gson.fromJson(JsonStringYouGotSomehow, YourObject.class);

Und du bist fertig. Kein kompliziertes Überschreiben erforderlich.

cpoole
quelle
Sie sollten es zuerst mit Retrofit versucht haben, bevor "Und Sie sind fertig. Es ist kein kompliziertes Überschreiben erforderlich."
Farid
1

Sie können zwei neue Klassen wie folgt definieren:

import java.util.Date;

public class MyDate extends Date {
}

und

import java.util.Date;

public class CreatedAtDate extends Date {
}

Ihr POJO wird so aussehen:

import MyDate;
import CreatedAtDate;

public class Foo {
    MyDate date;
    CreatedAtDate created_at;
}

Stellen Sie schließlich Ihren benutzerdefinierten Deserializer ein:

public class MyDateDeserializer implements JsonDeserializer<Date> {

    public static final SimpleDateFormat sServerDateDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public MyDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
         if (json != null) {
            final String jsonString = json.getAsString();
            try {
                return (MyDate) sServerDateDateFormat.parse(jsonString);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

und

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyDate.class, new MyDateDeserializer());
Shiami
quelle
0

Dies beantwortet die gestellte Frage nicht direkt, ist aber meiner Meinung nach der "Stand der Technik", wenn der Codierer die volle Wahlfreiheit bei der Lösung des Problems hat.

Erstens ist es nicht die beste Lösung, java.util.Date zu verwenden. Der Grund dafür ist, dass diese Klassen in einigen Eckfällen kein ideales Verhalten hatten. Wenn sie von der Java Instant-Klasse usw. abgelöst wurden, überprüfen Sie die Antwort von Basil Bourque in dieser SO-Frage: Erstellen von Datumsobjekten in Kotlin für API-Level kleiner oder gleich 16

Also habe ich die Instant-Klasse von ThreeTenABP und Kotlin unter Android verwendet:

val gson = GsonBuilder().registerTypeAdapter(Instant::class.java,
    JsonDeserializer<Instant> { json: JsonElement, _: Type?, _: JsonDeserializationContext? ->
        ZonedDateTime.parse(
            json.asJsonPrimitive.asString
        ).toInstant()
    }
).create()

val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
AndrewBloom
quelle