55

I have JSONs with a date-time attribute in the format "2014-03-10T18:46:40.000Z", which I want to deserialize into a java.time.LocalDateTime field using Gson.

When I tried to deserialize, I get the error:

java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
approxiblue
  • 6,982
  • 16
  • 51
  • 59
fische
  • 563
  • 1
  • 4
  • 7
  • Is this happening during serialization or de-serialization. If it's the second - check if your json is valid - for example it has to start with `{` (aka BEGIN_OBJECT), but instead your json starts with a character – Svetlin Zarev Mar 10 '14 at 21:06
  • I make a mistake its during de-serialization. Json -> Object. But I think my Json is ok. If I replace in my object my attribute LocalDateTime with String it works. – fische Mar 10 '14 at 21:13
  • 1
    json has the format `{"key":"value"}` Your json is definitely invalid. As I said in gson `{` is `BEGIN_OBJECT` and it says that it;s missing. Just `println()` the json to check if it's correct. Also you can print the `serialized` object to see how the json should look like :) – Svetlin Zarev Mar 10 '14 at 21:18
  • Ok I understand now why it doesn't work. But maybe you know if there is something I can do? Like a special options with Gson for say if I have a String with a special Date and Time to deserialize this field and create the object I need? – fische Mar 10 '14 at 21:38
  • I cannot tell you anything before I see the string. But either way, you cannot de-serialize it unless it;s valid json. IMHO the best (and maybe the only) thing you can do is to make this string valid json. – Svetlin Zarev Mar 10 '14 at 21:42
  • The String is like this: "2014-03-10T18:46:40.000Z". So I need to get an Object of this (datetime object from mysql?). – fische Mar 10 '14 at 21:45
  • Have you thought about that the timestamp string looks like an UTC-ISO-8601 global timestamp (letter Z at the end!)? It is not a local timestamp representation. I can actually not test the conversion to a `LocalDateTime` (no Java 8 with me) but suppose that this conversion might fail. – Meno Hochschild Mar 13 '14 at 12:16
  • 1
    @fische: As Meno says, what you have is an `OffsetDateTime`, not a `LocalDateTime`. Also, you might like to look at my custom serialisers for `java.time` entities: https://github.com/gkopff/gson-javatime-serialisers – Greg Kopff Apr 15 '14 at 00:46

6 Answers6

51

The error occurs when you are deserializing the LocalDateTime attribute because GSON fails to parse the value of the attribute as it's not aware of the LocalDateTime objects.

Use GsonBuilder's registerTypeAdapter method to define the custom LocalDateTime adapter. Following code snippet will help you to deserialize the LocalDateTime attribute.

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
    @Override
    public LocalDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        Instant instant = Instant.ofEpochMilli(json.getAsJsonPrimitive().getAsLong());
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }
}).create();
Randula
  • 1,537
  • 15
  • 11
33

To extend @Randula's answer, to parse a zoned date time string (2014-03-10T18:46:40.000Z) to JSON:

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
@Override
public LocalDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
    return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString()).toLocalDateTime();
}
}).create();
Evers
  • 1,858
  • 1
  • 17
  • 18
  • that parameter to deserialize of type "Type" what is that exactly? – mcgyver5 Feb 25 '17 at 17:16
  • 2
    @mcgyver5 - it's a `java.lang.reflect.Type` – Lambart Mar 27 '18 at 01:02
  • it is not a zoned date time string, but the instant (https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) – Francis Dec 21 '20 at 16:01
  • The input string is fits neither of the two classes you used. `LocalDateTime` ignores the input’s indicator `Z` of a pn offset form UTC of zero hours-minutes-seconds. `ZonedDateTime` is for representing a date-time in the context of a time zone, but the input has no time zone. The appropriate classes for this input are `Instant` or `OffsetDateTime`. – Basil Bourque Aug 06 '22 at 21:17
31

To even further extend @Evers answer:

You can further simplify with a lambda like so:

GSON GSON = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, type, jsonDeserializationContext) ->
    ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString()).toLocalDateTime()).create();
james.garriss
  • 12,959
  • 7
  • 83
  • 96
Nicholas Terry
  • 1,812
  • 24
  • 40
8

Following worked for me.

Java:

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() { 
@Override 
public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 

return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); } 

}).create();

Test test = gson.fromJson(stringJson, Test.class);

where stringJson is a Json which is stored as String type

Json:

"dateField":"2020-01-30 15:00"

where dateField is of LocalDateTime type which is present in the stringJson String variable.

greenhorn
  • 594
  • 6
  • 19
1

As mentioned in the comments above, you could also use already available serializer.

https://github.com/gkopff/gson-javatime-serialisers

You include it in your project.

<dependency>
  <groupId>com.fatboyindustrial.gson-javatime-serialisers</groupId>
  <artifactId>gson-javatime-serialisers</artifactId>
  <version>1.1.2</version>
</dependency>

Then include it to the GsonBuilder process

final Gson gson = Converters.registerOffsetDateTime(new GsonBuilder()).create();
final OffsetDateTime original = OffsetDateTime.now();

final String json = gson.toJson(original);
final OffsetDateTime reconstituted = gson.fromJson(json, OffsetDateTime.class);

Just in case its not clear, there exist different methods for different class types.

Syakur Rahman
  • 2,056
  • 32
  • 40
0

To further amplify @Nicholas Terry answer:

You might also need a serializer:

String dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
GSON gson = new GsonBuilder()
    .registerTypeAdapter(
        LocalDateTime.class,
        (JsonDeserializer<LocalDateTime>) (json, type, jsonDeserializationContext) ->
            ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString()).toLocalDateTime()
    )
    .registerTypeAdapter(
        LocalDateTime.class,
        (JsonSerializer<LocalDateTime>) (localDate, type, jsonSerializationContext) ->
            new JsonPrimitive(formatter.format(localDate)
    )
    .create();

Or the kotlin version:

val dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
val formatter = DateTimeFormatter.ofPattern(dateFormat)
val gson: Gson = GsonBuilder()
    .registerTypeAdapter(
        LocalDateTime::class.java,
        JsonDeserializer { json, type, jsonDeserializationContext -> 
            ZonedDateTime.parse(json.asJsonPrimitive.asString).toLocalDateTime()
        } as JsonDeserializer<LocalDateTime?>
    )
    .registerTypeAdapter(
        LocalDateTime::class.java,
        JsonSerializer<LocalDateTime?> { localDate, type, jsonDeserializationContext ->
            JsonPrimitive(formatter.format(localDate))
        }
    )
    .create()
rvazquezglez
  • 2,284
  • 28
  • 40