Gson 自定义序列化
在这篇文章中,我们将探讨如何自定义 Java 对象的 Gson 序列化。 我们可能想要更改序列化的原因有很多,例如 简化我们的模型以减少发送的数据量或删除个人信息。 现在我们将通过实现自定义序列化程序来研究对象的简化。 我们不再需要将完整的对象发送到服务器。 我们只需要发送对象的 ID。 如果有兴趣了解如何实现这一目标,并了解如何自定义序列化,请继续阅读!
自定义序列化
假设我们的应用程序满足以下场景:应用程序从服务器拉入可用商家列表。 用户可以选择该列表的子集作为他的新订阅。 应用程序需要在网络请求中将用户信息和他的选择发送回服务器。
首先,我们需要为来回发送的数据创建模型类。
Models 模型
用户信息将包含在入门文章中的以下 UserSimple
类中:
public class UserSimple {
private String name;
private String email;
private boolean isDeveloper;
private int age;
}
此外,我们将为商家提供一个模型:
public class Merchant {
private int Id;
private String name;
// 可能还有更多其他的属性
}
当应用程序从服务器拉入信息时,我们会得到一个商家列表。 在用户选择了他的商家后,我们需要将用户信息和商家子集都发送回来。 因此,我们将用户模型 UserSimple
扩展为更复杂的 UserSubscription
类,其中包括商家列表:
public class UserSubscription {
String name;
String email;
int age;
boolean isDeveloper;
// new!
List<Merchant> merchantList;
}
问题
从理论上讲,我们现在可以开始了。 在应用程序设置了必要的属性后,Gson 将创建以下 JSON:
{
"age": 26,
"email": "jiyik_onmpw@163.com",
"isDeveloper": true,
"merchantList": [
{
"Id": 23,
"name": "Future Studio"
},
{
"Id": 42,
"name": "Coffee Shop"
}
],
"name": "jiyik"
}
这么小的模型可能不那么明显,但是如果我们想象商家模型要复杂得多,我们的请求 JSON 会变得非常大。
然而,这不是必须的! 服务器已经知道商家信息。 整个商人对象都是多余的! 服务器只需要知道用户想要订阅的商家ID。
使用属性排除进行简化
第一种方法可能是调整要序列化的商家属性。 正如我们在关于排除策略的文章中了解到的,我们可以更改哪些属性被(反)序列化。 让我们更改 Merchant 类:
public class Merchant {
private int Id;
@Expose(serialize = false)
private String name;
// 可能还有更多其他属性
}
添加一些注解后,我们可以将请求 JSON 简化为:
{
"age": 26,
"email": "jiyik_onmpw@163.com",
"isDeveloper": true,
"merchantList": [
{
"Id": 23
},
{
"Id": 42
}
],
"name": "jiyik"
}
这使我们非常接近最佳结果。 尽管如此,我们仍然可以进一步减少它。 此外,如果应用程序在需要完整对象时将商家对象发送到其他端点,这种方法可能会出现问题。
通过将自定义序列化对单个对象进行简化
由于第一种方法有其局限性,是时候看看更好的解决方案了:自定义序列化。 我们希望根据请求限制商家对象的序列化。 听起来很复杂,但 Gson 让它变得非常简单。
让我们逐步完成自定义序列化。 之前没有优化的方法如下所示:
// 从 API 端点获取商家列表
Merchant futureStudio = new Merchant(23, "Future Studio", null);
Merchant coffeeShop = new Merchant(42, "Coffee Shop", null);
// 创建一个新的订阅对象并将商家传递给它
List<Merchant> subscribedMerchants = Arrays.asList(futureStudio, coffeeShop);
UserSubscription subscription = new UserSubscription(
"jiyik",
"jiyik_onmpw@163.com",
26,
true,
subscribedMerchants);
Gson gson = new Gson();
String fullJSON = gson.toJson(subscription);
为了优化这一点,我们需要使用自定义的 Gson 实例,为 Merchant
类注册一个类型适配器,然后调用 toJson()
方法:
GsonBuilder gsonBuilder = new GsonBuilder();
JsonSerializer<Merchant> serializer = ...;
gsonBuilder.registerTypeAdapter(Merchant.class, serializer);
Gson customGson = gsonBuilder.create();
String customJSON = customGson.toJson(subscription);
如果大家完成了前面的教程,上面的大部分代码应该看起来很熟悉。 唯一未知的是 registerTypeAdapter()
方法。 它需要两个参数。 第一个是需要自定义序列化的对象的类型。 第二个参数是 JsonSerializer
接口的实现。
让我们做这最后一步:
JsonSerializer<Merchant> serializer = new JsonSerializer<Merchant>() {
@Override
public JsonElement serialize(Merchant src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonMerchant = new JsonObject();
jsonMerchant.addProperty("Id", src.getId());
return jsonMerchant;
}
};
如大家所见,我们键入 JsonSerializer
并覆盖 serialize
方法。 它为我们提供了需要序列化的对象(作为 src)。 返回的是一个 JsonElement。 如何从 src 对象创建 JsonElement 取决于具体情况。 在上面的片段中,我们简单地创建了一个新的空 JsonObject 并添加了一个带有商家 ID 的属性。
每次 Gson 需要序列化 Merchant 对象时,都会调用此 serialize
回调方法。 在我们的例子中,这将适用于商家列表中的每个对象。
一旦我们运行此代码,它将产生以下 JSON:
{
"age": 26,
"email": "jiyik_onmpw@163.com",
"isDeveloper": true,
"merchantList": [
{
"Id": 23
},
{
"Id": 42
}
],
"name": "jiyik"
}
如我们所见,结果与我们通过注解自定义序列化所获得的结果完全相同。 此外,为列表中的每个元素调用 serialize
回调。 在下一节中,我们将自定义整个列表的序列化,而不仅仅是单个列表项。
使用自定义序列化作为列表对象进行简化
我们已经在上一节中看到了自定义序列化的结构。 现在我们将调整它以使我们的请求 JSON 更小。 诀窍是针对 JSON 的 List<Merchant>
部分。
GsonBuilder gsonBuilder = new GsonBuilder();
Type merchantListType = new TypeToken<List<Merchant>>() {}.getType();
JsonSerializer<List<Merchant>> serializer = ...;
gsonBuilder.registerTypeAdapter(merchantListType, serializer);
Gson customGson = gsonBuilder.create();
String customJSON = customGson.toJson(subscription);
由于我们现在要使用列表对象,因此我们需要使用 TypeToken
类。 如果各位有点不确定为什么需要这样做或 TypeToken 的用途是什么,大家可以返回我们的 Java Lists 映射来更新一下记忆。
好的,关键步骤是实现 serializer 对象:
JsonSerializer<List<Merchant>> serializer =
new JsonSerializer<List<Merchant>>() {
@Override
public JsonElement serialize(List<Merchant> src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonMerchant = new JsonObject();
List<String> merchantIds = new ArrayList<>(src.size());
for (Merchant merchant : src) {
merchantIds.add("" + merchant.getId());
}
String merchantIdsAsString = TextUtils.join(",", merchantIds);
jsonMerchant.addProperty("Ids", merchantIdsAsString);
return jsonMerchant;
}
}
在 serialize()
回调中,我们正在创建一个新的 JsonObject 并仅添加一个属性。 该属性 Ids 包含一个包含所有商家 ID 的字符串。
{
"age": 26,
"email": "jiyik_onmpw@163.com",
"isDeveloper": true,
"merchantList": {
"Ids": "23,42"
},
"name": "jiyik"
}
优点是减小了 JSON 的大小。 尤其是对于更多的商家来说,这意味着传输回服务器的数据更少,这会让应用程序更快一点,让应用程序用户的体验更好。
但是,这个解决方案有点奇怪。 在串联的字符串中发送 ID 不是标准的做法。 JSON 世界中最好的方法是将 merchantList 作为一个数组传递,而不是一个对象。
使用自定义序列化作为列表数组进行简化
最后的优化是调整序列化器以创建数组而不是对象。 一般方法保持不变,我们只需要更改 serializer
:
JsonSerializer<List<Merchant>> serializer =
new JsonSerializer<List<Merchant>>() {
@Override
public JsonElement serialize(List<Merchant> src, Type typeOfSrc, JsonSerializationContext context) {
JsonArray jsonMerchant = new JsonArray();
for (Merchant merchant : src) {
jsonMerchant.add("" + merchant.getId());
}
return jsonMerchant;
}
}
正如我们在上面的代码片段中看到的,不同之处在于我们创建了一个新的 JsonArray
而不是 JsonObject
。 我们可以使用标准的 add()
函数来添加新元素并返回该数组。 这将产生我们最终的最小化 JSON:
{
"age": 26,
"email": "jiyik_onmpw@163.com",
"isDeveloper": true,
"merchantList": [
"23",
"42"
],
"name": "jiyik"
}
我们在过去几节中已经看到,使用 GSON 自定义序列化在技术方面相当简单,但在逻辑方面却不那么直接。 我们确实必须考虑如何构建要发送到服务器的数据。
Gson 非常灵活,可以涵盖很多不同的情况。 尽管功能强大,但也存在一些缺陷。
常见问题
一个常见的意外问题是(意外)覆盖自定义类型适配器。 如果你多次为同一个类型声明一个类型适配器,Gson 将只考虑最后一次 registerTypeAdapter()
调用。
GsonBuilder gsonBuilder = new GsonBuilder();
Type merchantListType = new TypeToken<List<Merchant>>() {}.getType();
JsonSerializer<List<Merchant>> serializerA = ...;
JsonSerializer<List<Merchant>> serializerB = ...;
gsonBuilder.registerTypeAdapter(merchantListType, serializerA);
gsonBuilder.registerTypeAdapter(merchantListType, serializerB);
Gson customGson = gsonBuilder.create();
String customJSON = customGson.toJson(subscription);
正如我们在上面的代码片段中看到的,只有最后一个 registerTypeAdapter()
是相关的。 如果不注意,多次为同一类型注册(反)序列化程序很容易发生。 因此,如果要添加新的自定义类型适配器,请仔细检查是否尚未在其他地方为该类型实现和注册某些内容。
其次,我们在 Gson 新用户中看到的一个常见问题是在 serializer
实现中使用 Gson 的 serialize()
方法。 检查以下代码片段:
new JsonSerializer<UserSubscription>() {
@Override
public JsonElement serialize(UserSubscription src, Type typeOfSrc, JsonSerializationContext context) {
JsonElement jsonSubscription = context.serialize(src, typeOfSrc);
// 自定义 jsonSubscription
return jsonSubscription;
}
}
这个想法非常聪明:当我们自定义 JSON 映射时,通常必须手动映射一堆属性(请参阅前面部分中的代码示例)。 在自定义序列化程序中调用 serialize()
的方法对我们非常有用。
但是,要非常小心。 如果 serialize()
调用与我们的自定义序列化程序具有相同的类型,那我们将陷入无限循环。 serialize()
调用将再次结束我们的自定义序列化程序,该序列化程序再次调用 serialize()
,依此类推……
总结
在这篇文章中,我们了解了如何利用 Gson 的自定义序列化来最小化请求 JSON。 当尝试优化应用网络使用情况时,这会非常有用。
相关文章
Gson 通过@JsonAdapter 自定义(反)序列化
发布时间:2022/07/24 浏览次数:217 分类:编程语言
-
在这篇文章中,我们将展示如何简化(反)序列化的自定义。 所有这些选项都只能通过自定义 Gson 实例和一些样板代码获得。 Gson 2.7 引入了一个简单的注解,我们可以节省大量代码并获
Gson 自定义反序列化基础
发布时间:2022/07/21 浏览次数:81 分类:编程语言
-
在这篇文章中,我们将了解如何实现自定义 Gson 反序列化。 如果服务器以与客户端的应用程序数据模型不匹配的格式向我们发送数据,请继续阅读!
Gson 自定义实例创建器
发布时间:2022/07/14 浏览次数:121 分类:编程语言
-
在这篇文章中,我们将讨论自定义反序列化的另一个组件。 在过去的几篇文章中,我们探讨了如何自定义数据的序列化和反序列化。 在这两种情况下,我们都试图减轻服务器和客户端之
Gson 循环引用的映射
发布时间:2022/07/13 浏览次数:173 分类:编程语言
-
在这篇文章中,我们将讨论一个特别讨厌的话题:循环引用。 我们可能在计算机科学或图表数学课上听说过循环引用。 在更实际的解释中:它处理对象具有指向不同对象的嵌套属性的情
Gson Builder Floats 和 Doubles 的特殊值
发布时间:2022/07/12 浏览次数:193 分类:编程语言
-
在上一篇 Gson 的文章中,我们研究了使 JSON 转换降低标准的选项。 Lenient 允许传入的 JSON 在某种程度上是非标准的,Gson 仍然能够将其解析为 Java 对象。 在这篇文章中,我们将研究一个
Gson 映射 Enum 枚举
发布时间:2022/07/10 浏览次数:156 分类:编程语言
-
在之前的文章中我们介绍了如何映射嵌套对象、数组和列表、Java Map 等。 在这篇文章中,您将学习如何(反)序列化 Java 枚举 Enum。
Gson 如何反序列化多态对象列表
发布时间:2022/07/09 浏览次数:286 分类:编程语言
-
最近,我们遇到了一种情况,我从 REST 端点接收到 JSON 格式的对象列表。 到目前为止,这没有什么不寻常的,也不是问题。 然而,问题是对象是多态的,需要解析子类特定的字段。 以
Gson Builder 使用 Lenient 放宽 Gson 的转换标准
发布时间:2022/06/26 浏览次数:100 分类:编程语言
-
JSON 内容的格式必须满足一些特定的规则才能符合标准。 该标准在 RFC4627 规范中进行了描述。 它为键和值的分离方式、数组的结构等奠定了基础。 在这篇文章中,我们将探讨 Gson 与 J