50

I am using Java 8 and the latest RELEASE version (via Maven) of Gson. If I serialize a LocalDate I get something like this

"birthday": {
        "year": 1997,
        "month": 11,
        "day": 25
}

where I would have preferred "birthday": "1997-11-25". Does Gson also support the more concise format out-of-the-box, or do I have to implement a custom serializer for LocalDates?

(I've tried gsonBuilder.setDateFormat(DateFormat.SHORT), but that does not seem to make a difference.)

Drux
  • 11,992
  • 13
  • 66
  • 116

5 Answers5

78

Until further notice, I have implemented a custom serializer like so:

class LocalDateAdapter implements JsonSerializer<LocalDate> {

    public JsonElement serialize(LocalDate date, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(date.format(DateTimeFormatter.ISO_LOCAL_DATE)); // "yyyy-mm-dd"
    }
}

It can be installed e.g. like so:

Gson gson = new GsonBuilder()
        .setPrettyPrinting()
        .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
        .create();
Drux
  • 11,992
  • 13
  • 66
  • 116
  • Simple and perfect. Thanks :) +1 – Anish B. Feb 12 '21 at 12:47
  • Note that it is recommended to prefer TypeAdapter over JsonSerializer and JsonDeserializer since it reads the JSON data in a streaming way instead of parsing it as an in-memory representation of JsonElements. as Sam Barnum answer . – Lunatic Apr 07 '22 at 08:04
40

I use the following, supports read/write and null values:

class LocalDateAdapter extends TypeAdapter<LocalDate> {
    @Override
    public void write(final JsonWriter jsonWriter, final LocalDate localDate) throws IOException {
        if (localDate == null) {
            jsonWriter.nullValue();
        } else {
            jsonWriter.value(localDate.toString());
        }
    }

    @Override
    public LocalDate read(final JsonReader jsonReader) throws IOException {
        if (jsonReader.peek() == JsonToken.NULL) {
            jsonReader.nextNull();
            return null;
        } else {
            return LocalDate.parse(jsonReader.nextString());
        }
    }
}

Registered as @Drux says:

return new GsonBuilder()
        .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
        .create();

EDIT 2019-04-04 A simpler implementation

private static final class LocalDateAdapter extends TypeAdapter<LocalDate> {
    @Override
    public void write( final JsonWriter jsonWriter, final LocalDate localDate ) throws IOException {
        jsonWriter.value(localDate.toString());
    }

    @Override
    public LocalDate read( final JsonReader jsonReader ) throws IOException {
        return LocalDate.parse(jsonReader.nextString());
    }
}

Which you can add null support to by registering the nullSafe() wrapped version:

new GsonBuilder()
      .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
Sam Barnum
  • 10,559
  • 3
  • 54
  • 60
14

Kotlin version which supports serializing and deserializing:

class LocalDateTypeAdapter : TypeAdapter<LocalDate>() {

    override fun write(out: JsonWriter, value: LocalDate) {
        out.value(DateTimeFormatter.ISO_LOCAL_DATE.format(value))
    }

    override fun read(input: JsonReader): LocalDate = LocalDate.parse(input.nextString())
}

Register with your GsonBuilder. Wrap using nullSafe() for null support:

GsonBuilder().registerTypeAdapter(LocalDate::class.java, LocalDateTypeAdapter().nullSafe())
Cristan
  • 12,083
  • 7
  • 65
  • 69
1

Reported upstream as https://github.com/google/gson/issues/1059 . Current suggestion from the upstream is to use the https://github.com/gkopff/gson-javatime-serialisers addon.

Martin Vysny
  • 3,088
  • 28
  • 39
0

For some reason using the above classes with .nullSafe() didn't work me as I still got this exception with JDK 17 and Gson:

Unable to make field private final java.time.LocalDate java.time.LocalDateTime.date accessible: module java.base does not "opens java.time"

so I used this custom TypeAdapter with null checks:

public class LocalDateTimeTypeAdapter extends TypeAdapter<LocalDateTime> {

    @Override
    public void write(final JsonWriter jsonWriter, final LocalDateTime localDate) throws IOException {
        if (localDate == null) {
            jsonWriter.nullValue();
            return;
        }
        jsonWriter.value(localDate.toString());
    }

    @Override
    public LocalDateTime read(final JsonReader jsonReader) throws IOException {
        if (jsonReader.peek() == JsonToken.NULL) {
            jsonReader.nextNull();
            return null;
        }
        return ZonedDateTime.parse(jsonReader.nextString()).toLocalDateTime();
    }
}
nbarm
  • 21
  • 3
  • I get: Class 'LocalDateTimeTypeAdapter' must either be declared abstract or implement abstract method 'write(JsonWriter, T)' in 'TypeAdapter'" when trying to use this code? – John Little Apr 26 '23 at 10:30