在这篇文章中,我们将探讨如何自定义 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。 当尝试优化应用网络使用情况时,这会非常有用。

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Gson 自定义序列化