Search code examples
javajsongsonjava.util.date

Convert String date to Object yields Invalid time zone indicator '0'


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?


Solution

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