Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom gson deserializer for Date never gets called

As far as I can tell, gson does not automatically serialize and deserialize java.util.Date objects into ISO strings like "yyyy-MM-ddTHH:mm:ssZ" or for example "2014-04-15T18:22:00-05:00". So in order for me to communicate dates properly between my client (using Retrofit with gson) and server, I need to specify the DateFormat to gson. Here is what I've done:

// code defining the creation of a RestAdapter
// ...
new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
    .create()

Adding the .setDateFormat line was enough to get gson to properly deserialize timestamp strings into Date objects. However, it did not serialize Date objects into timestamp strings. So I assumed that I would have to create a custom serializer like so:

// code defining the creation of a RestAdapter
// ...
new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
    .registerTypeAdapter(Date.class, new DateSerializer())
    .create()

and the DateSerializer class:

class DateSerializer implements JsonSerializer<Date> {
    @Override
    public JsonElement serialize(Date arg0, Type arg1, JsonSerializationContext arg2) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
        return new JsonPrimitive(df.format(arg0));
    }
}

Unfortunately, the serialize function is ignored. Instead gson formats the date as a string like "Tues Mar 15 18:22:00 EST 2014". So to test, I tried replacing the serialize function with:

public JsonElement serialize(Date arg0, Type arg1, JsonSerializationContext arg2) {
    throw new RuntimeException("Serialize function called!");
}

But of course that RuntimeException is never thrown.

Does anyone know why my serialize function is ignored? I think I read somewhere that for some types a registerTypeAdapter will be ignored if there is one defined for a superclass, but since this is java.util.Date I would be confused if that was the problem. I'm probably just doing something stupid, but I probably don't know Date or gson well enough to realize it.

EDIT: Provided more context around the code below:

MyApplication.java

public class MyApplication extends Application {
    public static RestAdapter restAdapter;
    public static The1Api the1Api;

    public static void createRestAdapter(String server_url){
        // enable cookies
        CookieManager cookieManager = new CookieManager();
        cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        CookieHandler.setDefault(cookieManager);

        // create rest adapter
        restAdapter = new RestAdapter.Builder()
            .setEndpoint(server_url)
            .setConverter(new GsonConverter(new GsonBuilder()
                    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                    .registerTypeAdapter(Date.class, new DateSerializer())
                    .create()))
            .setLogLevel(LogLevel.FULL)
            .setLog(new ResponseInterceptor())
            .build();

        // create API
        the1Api = restAdapter.create(The1Api.class);
    }
}

The1Api.java

public interface The1Api {
    /* Chat */

    public class PublicMessage {
        String from_user;
        String message;
        Date time;
        Integer microsecond;
        Boolean in_1_percent;
    }
    public class PublicMessageList {
        Integer last_message_id;
        ArrayList<PublicMessage> messages;
    }

    @GET("/chat/get_public_messages/")
    public PublicMessageList getPublicMessages(
            @Query("last_message_id") Integer last_message_id, // optional
            @Query("since") Date since, // optional
            @Query("max") Integer max // optional
            );   

    // ...
}

LoginActivity.java

public class LoginActivity extends Activity {
    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Crashlytics.start(this);
        MyApplication.createRestAdapter(getString(R.string.server_url));
        setContentView(R.layout.activity_login);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState){
        super.onPostCreate(savedInstanceState);

        Thread thread = new Thread(){
            @Override
            public void run(){
                ArrayList<The1Api.PublicMessage> publicMessages = MyApplication.the1Api.getPublicMessages(null, null, null).messages;
                for (The1Api.PublicMessage m : publicMessages){
                    Log.d("The1", "[" + m.time.toString() + "] " + m.from_user + ": " + m.message);
                }
                // when the following line gets executed, my server receives a request including the date below,
                // but the server does not understand the format of the date because it does not get serialized properly
                MyApplication.the1Api.getPublicMessages(null, new Date(1000000000), null);
            }
        };
        thread.start();
    }

    // ...    
}

DateSerializer.java

class DateSerializer implements JsonSerializer<Date> {
    @Override
    public JsonElement serialize(Date arg0, Type arg1, JsonSerializationContext arg2) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
        return new JsonPrimitive(df.format(arg0));
    }
}

EDIT 2: No solutions as of yet, but as a workaround you can manually convert the Date to some other format before sending it. In the comments Kalel suggested converting it to a String, I converted it to a Long (number of seconds since UNIX epoch).

like image 761
RevolutionTech Avatar asked Nov 10 '22 09:11

RevolutionTech


1 Answers

Are you still able to replicate this issue? I'm seeing Gson serialize and deserialize correctly. It's possible this is a Retrofit issue (I've not used it before), but looking at the docs for GsonConverter I see no reason it would do anything other than delegate to the provided in Gson object.

Here's an SSCCE:

public class GsonDateDemo {
  public static void main(String[] args) {
    Gson gson = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
        .registerTypeAdapter(Date.class, new DateSerializer())
        .create();
    Date d = new Date(1438806977000L);

    System.out.println("Serializing "+d);
    String json = gson.toJson(d);
    System.out.println("JSON: "+json);
    Date d2 = gson.fromJson(json, Date.class);
    System.out.println("Deserialized: "+d2);
    System.out.println("Equal? "+d.equals(d2));
  }

  static class DateSerializer implements JsonSerializer<Date> {
    @Override
    public JsonElement serialize(Date arg0, Type arg1, JsonSerializationContext arg2) {
      System.out.println("Serializer received "+arg0);
      SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
      return new JsonPrimitive(df.format(arg0));
    }
  }
}

This outputs:

Calling: local.GsonDateDemo.main[]
Serializing Wed Aug 05 16:36:17 EDT 2015
Serializer received Wed Aug 05 16:36:17 EDT 2015
JSON: "2015-08-05T16:36:17-0400"
Deserialized: Wed Aug 05 16:36:17 EDT 2015
Equal? true

If you can, modify this SSCCE to use Retrofit and see if you can replicate the failure that way.

like image 125
dimo414 Avatar answered Nov 15 '22 07:11

dimo414