11

I got an Android app which receives Json responses from a web service. One of the responses is a json string with a date inside. I get the date in the form of a number like "1476399300000". When I try to create an object with it using GSON I get this error:

Failed to parse date ["1476399300000']: Invalid time zone indicator '0' (at offset 0)

Both sides are working with java.util.Date

How can I fix this issue?

CodeMonkey
  • 11,196
  • 30
  • 112
  • 203
  • That's propably because it's not a timestamp with seconds but with milliseconds. This could help you out: http://stackoverflow.com/questions/5671373/unparseable-date-1302828677828-trying-to-deserialize-with-gson-a-millisecond – Nico Jan 31 '17 at 12:53
  • This seems to be milliseconds since epoch. – IQV Jan 31 '17 at 12:54

1 Answers1

28

The value 1476399300000 looks like ms from the Unix epoch beginning. Just add a type adapter to your Gson:

final class UnixEpochDateTypeAdapter
        extends TypeAdapter<Date> {

    private static final TypeAdapter<Date> unixEpochDateTypeAdapter = new UnixEpochDateTypeAdapter();

    private UnixEpochDateTypeAdapter() {
    }

    static TypeAdapter<Date> getUnixEpochDateTypeAdapter() {
        return unixEpochDateTypeAdapter;
    }

    @Override
    public Date read(final JsonReader in)
            throws IOException {
        // this is where the conversion is performed
        return new Date(in.nextLong());
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final Date value)
            throws IOException {
        // write back if necessary or throw UnsupportedOperationException
        out.value(value.getTime());
    }

}

Configure your Gson instance:

final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Date.class, getUnixEpochDateTypeAdapter())
        .create();

Gson instances are thread-safe as well as UnixEpochDateTypeAdapter is, and can exist as one instance globally. Example:

final class Mapping {   
    final Date date = null; 
}
final String json = "{\"date\":1476399300000}";
final Mapping mapping = gson.fromJson(json, Mapping.class);
System.out.println(mapping.date);
System.out.println(gson.toJson(mapping));

would give the following output:

Fri Oct 14 01:55:00 EEST 2016
{"date":1476399300000}

Note that the type adapter is configured to override the default Gson date type adapter. So you might need to use a more complicated analysis to detect whether is just ms of the Unix epoch. Also note, that you could use JsonDeserializer, but the latter works in JSON-tree manner whilst type adapters work in the streaming way that's somewhat more efficient probably not accumulating intermediate results.

Edit:

Also, it may look confusing, but Gson can make value conversions for primitives. Despite your payload has a string value, JsonReader.nextLong() can read a string primitive as a long value. So the UnixEpochDateTypeAdapter.write should be out.value(String.valueOf(value.getTime())); in order not to modify JSON literals.

Edit

There's also a shorter solution (working with JSON in-memory trees rather than data streaming) which is simply:

final Gson builder = new GsonBuilder()
    .registerTypeAdapter(Date.class, new JsonDeserializer<Date>() { 
       public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
          return new Date(jsonElement.getAsJsonPrimitive().getAsLong()); 
       } 
    })
    .create();
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
  • @YonatanNir, please note that `JsonSerializer`/`JsonDeserializer` is slightly another thing that `TypeAdapter` is: the first two classes are used to convert JSON trees to values (assuming they have to be in-memory), whilst the latter works in streaming fashion. I did not suggest `JsonDeserializer` in favor of really simple-to-implement `TypeAdapter` and performance intentionally. – Lyubomyr Shaydariv Feb 01 '17 at 08:41
  • Register UnixEpochDateTypeAdapter such that it will be Null Safe and can omit the conversion of timestamp in write operation if the value is null during deserialization. This can be done by final Gson gson = new GsonBuilder() .registerTypeAdapter(Date.class, UnixEpochDateTypeAdapter.getUnixEpochDateTypeAdapter().nullSafe()).create() – Satyam Apr 19 '20 at 02:22