32

My server JSON is returning with two different type of DateFormat. "MMM dd, yyyy" and "MMM dd, yyyy HH:mm:ss"

When I convert the JSON with the following it is fine:

Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy").create();

But when I want the detailed date format and changed it to this, it throws exception com.google.gson.JsonSyntaxException: Mar 21, 2013

Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create();

Is there a way for gson to handle two different DateFormat for its Json conversion?

wangyif2
  • 2,843
  • 2
  • 24
  • 29

3 Answers3

70

I was facing the same issue. Here is my solution via custom deserialization:

new GsonBuilder().registerTypeAdapter(Date.class, new DateDeserializer());

private static final String[] DATE_FORMATS = new String[] {
        "MMM dd, yyyy HH:mm:ss",
        "MMM dd, yyyy"
};


private class DateDeserializer implements JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonElement jsonElement, Type typeOF,
            JsonDeserializationContext context) throws JsonParseException {
        for (String format : DATE_FORMATS) {
            try {
                return new SimpleDateFormat(format, Locale.US).parse(jsonElement.getAsString());
            } catch (ParseException e) {
            }
        }
        throw new JsonParseException("Unparseable date: \"" + jsonElement.getAsString()
                + "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
    }
}
Evan Leis
  • 494
  • 5
  • 13
andrew
  • 6,533
  • 1
  • 22
  • 19
  • I have used this snippet in Retrofit but overridden method never gets called. However, when using it outside Retrofit context (in a test class) it works properly. I am still confused about it – Farid Jul 29 '18 at 18:54
  • Works with Retrofit with no problem. – farid_z Aug 20 '19 at 16:13
  • Is there a way to try out formatting without throwing exceptions (I think this is not optimal for performance)? – User Jan 16 '20 at 08:39
  • @Ixx best option would be to find a way to use one format, but if you need multiple formats I think you can order them to reduce amount of Exceptions. If that doesn't work you can try to use regex and apply formatting afterwards, but then you need to measure regex vs exception to make sure it worth doing. – andrew Jan 20 '20 at 13:14
11

Although the answer has been accepted I wanted to share a similar yet more extensible solution. You can find the gist here.

DateDeserializer.java

public class DateDeserializer<T extends Date> implements JsonDeserializer<T> {

    private static final String TAG = DateDeserializer.class.getSimpleName();

    private final SimpleDateFormat mSimpleDateFormat;
    private final Class<T> mClazz;

    public DateDeserializer(SimpleDateFormat simpleDateFormat, Class<T> clazz) {
        mSimpleDateFormat = simpleDateFormat;
        mClazz = clazz;
    }

    @Override
    public T deserialize(JsonElement element, Type arg1, JsonDeserializationContext context) throws JsonParseException {
        String dateString = element.getAsString();
        try {
            T date = mClazz.newInstance();
            date.setTime(mSimpleDateFormat.parse(dateString).getTime());
            return date;
        } catch (InstantiationException e) {
            throw new JsonParseException(e.getMessage(), e);
        } catch (IllegalAccessException e) {
            throw new JsonParseException(e.getMessage(), e);
        } catch (ParseException e) {
            throw new JsonParseException(e.getMessage(), e);
        }
    }
}

Then register the different formats as...

sGson = new GsonBuilder()
                    .registerTypeAdapter(Event.EventDateTime.class,
                            new DateDeserializer<Event.EventDateTime>(
                                    Event.EventDateTime.DATE_FORMAT, Event.EventDateTime.class))
                    .registerTypeAdapter(Event.StartEndDateTime.class,
                            new DateDeserializer<Event.StartEndDateTime>(
                                    Event.StartEndDateTime.DATE_FORMAT, Event.StartEndDateTime.class))
                    .registerTypeAdapter(Event.SimpleDate.class,
                            new DateDeserializer<Event.SimpleDate>(
                                    Event.SimpleDate.DATE_FORMAT, Event.SimpleDate.class))
                    .create();

Each format is then mapped to a class...

public class Event {

    @SerializedName("created")
    private EventDateTime mCreated;

    //@SerializedName("updated")
    private EventDateTime mUpdated;

    ...

    @SerializedName("start")
    private ConditionalDateTime mStart;

    @SerializedName("end")
    private ConditionalDateTime mEnd;

    public static class ConditionalDateTime {
        @SerializedName("dateTime")
        private StartEndDateTime mDateTime;

        @SerializedName("date")
        private SimpleDate mDate;

        public SimpleDate getDate() {
            return mDate;
        }

        public StartEndDateTime getDateTime() {
            return mDateTime;
        }

        /**
         * If it is an all day event then only date is populated (not DateTime)
         * @return
         */
        public boolean isAllDayEvent() {
            return mDate != null;
        }
    }

    public static class EventDateTime extends Date {
        public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
    }

    public static class StartEndDateTime extends Date {
        public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ");
    }

    public static class SimpleDate extends java.util.Date {
        public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    }
}
JRomero
  • 4,878
  • 1
  • 27
  • 49
  • Very nice solution, and definitely more extensible. – Muhammad Alfaifi May 11 '16 at 11:58
  • 2
    I like this a lot, but I'm wondering if there may be a problem with the SimpleDateFormats and thread safety. If so, maybe simplest to store the format String instead, and create a new SimpleDateFormat in each call to DateDeserializer.deserialize()? – Brad Tofel Oct 25 '16 at 23:43
  • Oh I like this one! This is really clean and works like a charm, thanks a lot! – dvkch Apr 10 '19 at 13:40
  • As @BradTofel mentioned there is indeed some thread safety issue at play here, as I discovered while parsing a JSON containing roughly 300 dates. Storing the date format string and creating a new `SimpleDateFormat` for each (de)serializing solved the issue ! – dvkch May 02 '19 at 14:39
2

Custom deserialization is necessary. A decent solution would be to make use of the Apache Commons DateUtil, which can handle multiple date formats at once. Also, the JodaTime API might have a similar feature.

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97