使用Gson反序列化Java 8的LocalDateTime

55

我有一些带有日期时间属性的JSON,格式为"2014-03-10T18:46:40.000Z",我想使用Gson将其反序列化为java.time.LocalDateTime字段。

当我尝试反序列化时,我遇到了错误:

java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING

这是在序列化或反序列化期间发生的吗?如果是后者-请检查您的JSON是否有效-例如,它必须以“{”(又名BEGIN_OBJECT)开头,但是您的JSON却以一个字符开头。 - Svetlin Zarev
我犯了一个错误,是在反序列化期间。Json -> Object。但我认为我的Json没问题。如果我用String替换我的对象中的LocalDateTime属性,它就可以工作了。 - fische
1
JSON 的格式为 {"key":"value"}。你的 JSON 明显是无效的。就像我在 Gson 中所说的,{BEGIN_OBJECT,它表示缺失了。只需使用 println() 打印 JSON 来检查其是否正确。此外,你可以打印 serialized 对象以查看 JSON 应该是什么样子的 :) - Svetlin Zarev
好的,我现在明白为什么它不起作用了。但是也许你知道是否有什么我可以做的吗?比如说,使用Gson的特殊选项,如果我有一个带有特殊日期和时间的字符串来反序列化这个字段并创建我需要的对象? - fische
1
@fische:正如Meno所说,你拥有的是一个OffsetDateTime而不是LocalDateTime。此外,你可能想看一下我为java.time实体定制的序列化器:https://github.com/gkopff/gson-javatime-serialisers - Greg Kopff
显示剩余3条评论
6个回答

51

当您对LocalDateTime属性进行反序列化时,会出现错误,因为GSON无法解析该属性的值,因为它不知道LocalDateTime对象。

使用GsonBuilder的registerTypeAdapter方法来定义自定义的LocalDateTime适配器。以下代码片段将帮助您反序列化LocalDateTime属性。

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

33
为了扩展@Randula的答案,将带有时区的日期时间字符串(2014-03-10T18:46:40.000Z)转换为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();

那个要反序列化的参数类型是“Type”,具体是什么意思? - mcgyver5
2
@mcgyver5 - 这是一个 java.lang.reflect.Type - Lambart
这不是一个带时区的日期时间字符串,而是一个瞬间(https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。 - Francis
输入字符串不符合您使用的两个类中的任何一个。 LocalDateTime 忽略了输入的指示器 Z,该指示器表示与 UTC 的零小时-分钟-秒偏移量。 ZonedDateTime 用于表示时区上下文中的日期时间,但输入没有时区。 此输入的适当类别为 InstantOffsetDateTime - Basil Bourque

32

进一步扩展@Evers的答案:

你可以使用lambda进一步简化,如下所示:

GSON GSON = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, type, jsonDeserializationContext) ->
    ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString()).toLocalDateTime()).create();

8
以下方法适用于我。
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);

其中stringJson是以String类型存储的Json字符串。

Json:

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

其中dateFieldLocalDateTime 类型,在字符串变量stringJson中存在。


如果JSON中有其他类型的键值,比如字符串/整数键值,这个代码还能正常工作吗? - codeforHarman

1

如上方的评论所提到的,您也可以使用已经可用的序列化器。

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

你可以将它包含在你的项目中。

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

然后将其包含到GsonBuilder过程中

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

以防不清楚,不同的类类型存在着不同的方法。


建议使用这个。 - Gagan

1
进一步扩大@Nicholas Terry的回答:
你可能还需要一个序列化器:
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();

或者是Kotlin版本:
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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接