Actualizar GSON serialize Date de la cadena json a java.util.date


81

Estoy usando la biblioteca Retrofit para mis llamadas REST. La mayor parte de lo que he hecho ha sido fluido como la mantequilla, pero por alguna razón tengo problemas para convertir cadenas de marca de tiempo JSON en java.util.Dateobjetos. El JSON que está entrando se ve así.

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

¿Cómo puedo decirle a Retrofit o Gson que convierta estas cadenas en java.util.Date objects?


Parece un duplicado de esta pregunta .
Davmrtl

@Davmrtl: no es lo mismo ya que aquí hay dos formatos de fecha diferentes. Por lo que requiere un enfoque diferente.
giampaolo

Respuestas:


162
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();

O el equivalente de Kotlin:

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)

Puede configurar su analizador Gson personalizado para que se actualice. Más aquí: Sitio web de Retrofit

Mire la respuesta de Ondreju para ver cómo implementar esto en la actualización 2.


8
Explique brevemente su código para que sea más útil para OP y otros lectores.
Mohit Jain

¿Cómo puedo hacer esto para todas mis solicitudes? Entonces, ¿agregarlo a mi ServiceGenerator?
Zapnologica

4
En realidad, esto no se aplica a la configuración regional para mí. 2016-02-11T13:42:14.401Zse deserializa a un objeto de fecha que es para 13:42:14en mi configuración regional.
Alireza Mirian

He intentado esto, los elementos descargados parecen estar funcionando ahora. Pero cuando publico un objeto Json en el servidor. ¿Eliminará entonces mi zona horaria?
Zapnologica

Pasé mucho tiempo para detectar problemas con el bloqueo de mi aplicación sin ningún motivo después de implementar esta solución. Cuando copié y pegué el formato dete, algo cambió una comilla de 'T' a 'T' - hay una diferente - ¡observe con atención!
LukaszTaraszka

86

La respuesta de @ gderaco se actualizó a la actualización 2.0:

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();

2
Esté atento a las comillas al copiar el formato de fecha, ¡tuve un problema con eso!
LukaszTaraszka

14

Así es como lo hice:

Cree la clase DateTime extendiendo Date y luego escriba un deserializador personalizado:

public class DateTime extends java.util.Date {

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

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

Ahora, para la parte del deserializador donde registramos convertidores de fecha y fecha y hora:

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();
}

Y cuando cree su RestAdapter, haga lo siguiente:

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

Tu Foo debería verse así:

class Foo {
    Date date;
    DateTime created_at;
}

¡Gracias! Solo tuve que cambiar de return df.parse(json.getAsString()); a long timeStamp = Long.parseLong(json.getAsString()); return new java.util.Date(timeStamp);
Juan Saravia

¡Excelente! ¡Gracias!
NickUnuchek

7

Gson puede manejar solo un formato de fecha y hora (los especificados en el constructor) más el iso8601 si no es posible analizar con un formato personalizado. Por lo tanto, una solución podría ser escribir su deserializador personalizado. Para solucionar tu problema definí:

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 + "]";
    }

}

con este deserializador:

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);
    }

}

El último paso es crear una Gsoninstancia con el adaptador correcto:

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);
    }

}

Mi resultado:

Result: Foo [date=Tue Jul 16 00:00:00 CEST 2013, created_at=Tue Jul 16 22:52:36 CEST 2013]

1
¿Qué pasa si tengo 15-20 campos dentro de Foo (incluidos los objetos personalizados), tengo que deserializarlos manualmente, correcto? Eso mata el propósito de Gson entonces.
Farid

1

Literalmente, si ya tiene un objeto Date con el nombre "created_at" en la clase que está creando, entonces es así de fácil:

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

Y tu estas listo. no se necesita anulación complicada.


Debería haberlo probado con Retrofit primero antes de "Y ya está. No se necesita una anulación complicada".
Farid

1

Puede definir dos nuevas clases como esta:

import java.util.Date;

public class MyDate extends Date {
}

y

import java.util.Date;

public class CreatedAtDate extends Date {
}

Tu POJO será así:

import MyDate;
import CreatedAtDate;

public class Foo {
    MyDate date;
    CreatedAtDate created_at;
}

Finalmente configure su deserializador personalizado:

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;
    }
}

y

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

0

Esto no responde directamente a la pregunta formulada, pero, en mi opinión, es el "estado del arte" si el codificador tiene total libertad para elegir cómo resolver el problema.

Primero que nada, no es la mejor solución usar java.util.Date. La razón es que esas clases no tuvieron un comportamiento ideal en algunos casos de esquina, por lo que cuando fueron reemplazadas por la clase Java Instant, etc., verifique la respuesta de Basil Bourque en esta pregunta SO: Creación de objetos Date en Kotlin para un nivel de API menor o igual a 16

Entonces usé la clase Instant de ThreeTenABP, y usando Kotlin, en Android:

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()
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.