Gson 无法序列化 Android(Java)中的 LocalDateTime 数据

huangapple go评论77阅读模式
英文:

Gson is unable to serialize LocalDateTime data in Android (Java)

问题

[已更新:2023年02月20日] 在我的示例中,我错误地在LocalDateTime和Instant数据类型之间混用,所以我进行了调整。对于这个不便,我感到抱歉。谢谢

我在练习编程时遇到了与Java Android中的Gson和LocalDateTime有关的问题。例如,我创建了一个Java对象

public class Person {
    private String name;
    private LocalDateTime createdAt;
    
    // 构造函数
    // Getter和Setter方法
}

然后我使用Gson将这个Person对象转换为Json

Person person = new Person("John Doe", LocalDateTime.now()); // 例如:2022-02-02T11:11:045701100
String personJsonAsString = new Gson().toJson(person);

我期望的Json应该是

{
    "name": "John Doe",
    "createdAt": "2022-02-02T11:11:045701100"
}

但我得到的结果是

{
    "name": "John Doe",
    "createdAt": {} // <- 这里是空的
}

之后,我进行了研究,发现可以将自定义序列化器类添加到GsonBuilder实例中,所以我再次尝试

Person person = new Person("John Doe", LocalDateTime.now()); // 例如:2022-02-02T11:11:510Z
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeJsonSerializer()).create();
String personJsonAsString = gson.toJson(person);

LocalDateTimeJsonSerializer 是我编写的一个类

public class LocalDateTimeJsonSerializer implements JsonSerializer<LocalDateTime> {
    @Override
    public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    }
}

当我尝试在Android应用程序上持久保存数据时,会抛出异常:
Gson 无法序列化 Android(Java)中的 LocalDateTime 数据

目前,我无法弄清楚是什么原因导致Gson无法在Android平台上对LocalDateTime进行序列化。请帮助我。非常感谢您的时间。

我尝试过的方法:

  • 在Stackoverflow上研究其他问题。
  • 尝试任何可用的解决方案。

我期望的结果是:
我希望能够了解异常的真正根本原因,而不仅仅是修复错误并使其运行。

英文:

[UPDATED:02.20.2023] I was misused between LocalDateTime and Instant data type in my example so I adjusted it. Sorry for this inconvenient. Thank you

I am practicing coding and facing an issue relating to Gson and LocalDateTime in Java Android.
For example, I created a Java object

public class Person {
private String name;
private LocalDateTime createdAt;

// Constructors
// Getters and Setters
}

Then I used Gson to convert this Person object in to Json

Person person = new Person(&quot;John Doe&quot;, LocalDateTime.now()); //For example: 2022-02-02T11:11:045701100
String personJsonAsString = new Gson().toJson(person);

I expect the Json should be

{
&quot;name&quot;: &quot;John Doe&quot;,
&quot;createdAt&quot;: &quot;2022-02-02T11:11:045701100&quot;
}

But the result I got was

{
&quot;name&quot;: &quot;John Doe&quot;,
&quot;createdAt&quot;: {} // &lt;- It&#39;s empty here
}

After that, I researched and found that I can add custom serializer class into GsonBuilder instance so I tried again

Person person = new Person(&quot;John Doe&quot;, LocalDateTime.now()); //For example: 2022-02-02T11:11:510Z
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeJsonSerializer()).create();
String personJsonAsString = gson.toJson(person);

While LocalDateTimeJsonSerializer is a class that I wrote

public class LocalDateTimeJsonSerializer implements JsonSerializer&lt;LocalDateTime&gt; {
    @Override
    public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    }
}

The exception was throw when I try to persist the data on the Android app like this:
Gson 无法序列化 Android(Java)中的 LocalDateTime 数据

For now, I cannot figure out what makes LocalDateTime cannot be serialized in Android Platform by Gson. Please help me. Thank you so much for your time.

What I did try:
Researching other questions on Stackoverflow.
Try out any available solutions.
What I was expecting:
I was expecting to know the actual root cause of the exception instead of fixing the bug and making it run only

答案1

得分: 1

Square peg in a round hole

错误的类别。使用 Instant 而不是 LocalDateTime

public class Person {
    private String name;
    private Instant createdAt;
    
}

您的输入 &quot;2022-02-02T11:11:510Z&quot; 在末尾有一个 Z。这个字母表示与协调世界时的偏移为零小时-分钟-秒。

Instant instant = Instant.parse("2022-02-02T11:11:510Z");

LocalDateTime 不包含任何偏移或时区的概念。因此,该类不能表示一个瞬间,不是时间线上的特定点。该类不能表示您的输入意图的含义。该类不能用于记录某事物的创建时刻。

您的输入字符串符合 ISO 8601 标准。请参阅维基百科。

您显示了一行代码:

Person person = new Person(&quot;John Doe&quot;, LocalDateTime.now());

我无法想象在哪种情况下调用 LocalDateTime.now 是正确的做法。

改为:

Person person = new Person("John Doe", Instant.now());

示例代码

我不是 Gson 的专家。但似乎我已经成功使以下内容工作。

为简洁起见,让我们将您的 Person 类定义为一个 record。我很高兴看到 Gson 的当前版本 2.10.1 似乎与 records 配合得很好。

package work.basil.example.gson;

import java.time.Instant;

public record Person(String name, Instant createdAt) {}

显然,GSON 捆绑的唯一日期时间类型适配器是用于 java.util.Date 的。这个糟糕的遗留类应该避免使用。只使用 java.time 类。

我们使用 java.time.Instant。因此,我们需要编写自己的类型适配器。您可以找到第三方开源类型适配器实现。但为 Instant 编写自己的适配器足够简单。

java.time 类在解析/生成文本时默认使用 ISO 8601 标准格式。因此,我们将使用该标准。

package work.basil.example.gson;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.time.Instant;

public class Gson_InstantTypeAdapter extends TypeAdapter<Instant> {
    @Override
    public void write(JsonWriter jsonWriter, Instant instant) throws IOException {
        jsonWriter.value(instant.toString());  // 以标准的 ISO 8601 格式写入。
    }

    @Override
    public Instant read(JsonReader jsonReader) throws IOException {
        return Instant.parse(jsonReader.nextString());   // 解析标准的 ISO 8601 格式。
    }
}

现在我们编写一个应用程序来使用这些类。

首先,定义一个 Gson 对象,用于生成和解析 JSON 文本。

Gson gson =
        new GsonBuilder()
                .registerTypeAdapter(Instant.class, new Gson_InstantTypeAdapter())
                .create();

写一些 JSON。

// 写入 JSON。
Person person = new Person("John Doe", Instant.now()); // 例如:2022-02-02T11:11:510Z
String personAsJson = gson.toJson(person);

personAsJson = {"name":"John Doe","createdAt":"2023-02-19T22:47:42.566132Z"}

然后读取一些 JSON。

// 解析 JSON。
Person p = gson.fromJson(personAsJson, Person.class);

p.toString() = Person[name=John Doe, createdAt=2023-02-19T22:47:42.566132Z]

将代码整合在一起。

package work.basil.example.gson;

import com.google.gson.*;

import java.time.Instant;

public class App {
    public static void main(String[] args) {
        App app = new App();
        app.demo();
    }

    private void demo() {
        Gson gson =
                new GsonBuilder()
                        .registerTypeAdapter(Instant.class, new Gson_InstantTypeAdapter())
                        .create();

        // 写入 JSON。
        Person person = new Person("John Doe", Instant.now()); // 例如:2022-02-02T11:11:510Z
        String personAsJson = gson.toJson(person);

        System.out.println("personAsJson = " + personAsJson);

        // 解析 JSON。
        Person p = gson.fromJson(personAsJson, Person.class);

        System.out.println("p.toString() = " + p);
    }
}
英文:

Square peg in a round hole

Wrong class. Use Instant, not LocalDateTime.

public class Person {
    private String name;
    private Instant createdAt;
    
}

Your input &quot;2022-02-02T11:11:510Z&quot; has a Z on the end. That letter indicates an offset from UTC of zero hours-minutes-seconds.

Instant instant = Instant.parse( &quot;2022-02-02T11:11:510Z&quot; ) ;

The LocalDateTime lacks any concept of offset or time zone. So that class cannot represent a moment, is not a specific point on the timeline. That class cannot represent the meaning intended by your input. That class cannot be used to record the moment something was created.

Your input string complies with ISO 8601 standard. See Wikipedia.

You showed line of code:

>Person person = new Person(&quot;John Doe&quot;, LocalDateTime.now());

I cannot imagine a case where calling LocalDateTime.now is the right thing to do.

Change to:

Person person = new Person( &quot;John Doe&quot; , Instant.now() ) ;

Example code

I am no expert on Gson. But I seem to have gotten the following to work.

For brevity, let's define your Person class as a record. I am pleased to see that the current version 2.10.1 of Gson seems to be working well with records.

package work.basil.example.gson;

import java.time.Instant;

public record Person( String name , Instant createdAt ) { }

Apparently the only date-time type adapter bundled with GSON is for java.util.Date. That terrible legacy class should be avoided. Use only java.time classes.

We are using java.time.Instant. So we need to write our own type adapter. You can find 3rd party open-source type adapter implementations. But writing your own for Instant is simple enough.

The java.time classes by default use ISO 8601 standard formats when parsing/generating text. So we will use that standard.

package work.basil.example.gson;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.time.Instant;

public class Gson_InstantTypeAdapter extends TypeAdapter &lt; Instant &gt;
{
    @Override
    public void write ( JsonWriter jsonWriter , Instant instant ) throws IOException
    {
        jsonWriter.value( instant.toString() );  // Writes in standard ISO 8601 format.
    }

    @Override
    public Instant read ( JsonReader jsonReader ) throws IOException
    {
        return Instant.parse( jsonReader.nextString() );   // Parses standard ISO 8601 format.
    }
}

Now we write an app to exercise these classes.

First, define a Gson object to be using for generating and parsing JSON text.

Gson gson =
        new GsonBuilder()
                .registerTypeAdapter( Instant.class , new Gson_InstantTypeAdapter() )
                .create();

Write some JSON.

// Write to JSON.
Person person = new Person( &quot;John Doe&quot; , Instant.now() ); //For example: 2022-02-02T11:11:510Z
String personAsJson = gson.toJson( person );

>personAsJson = {"name":"John Doe","createdAt":"2023-02-19T22:47:42.566132Z"}

And read some JSON.

// Parse JSON.
Person p = gson.fromJson( personAsJson , Person.class );

>p.toString() = Person[name=John Doe, createdAt=2023-02-19T22:47:42.566132Z]

Bring that code together.

package work.basil.example.gson;

import com.google.gson.*;

import java.time.Instant;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    private void demo ( )
    {
        Gson gson =
                new GsonBuilder()
                        .registerTypeAdapter( Instant.class , new Gson_InstantTypeAdapter() )
                        .create();

        // Write to JSON.
        Person person = new Person( &quot;John Doe&quot; , Instant.now() ); //For example: 2022-02-02T11:11:510Z
        String personAsJson = gson.toJson( person );

        System.out.println( &quot;personAsJson = &quot; + personAsJson );

        // Parse JSON.
        Person p = gson.fromJson( personAsJson , Person.class );

        System.out.println( &quot;p.toString() = &quot; + p );
    }
}

huangapple
  • 本文由 发表于 2023年2月20日 00:13:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75501527.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定