如何使用 GSON 将空字符串视为空对象? [英] How do I treat empty Strings as null objects with GSON?

查看:164
本文介绍了如何使用 GSON 将空字符串视为空对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在从 Reddit API 中检索评论.该模型是线程化的,因此每个评论可以在内部拥有一个评论列表,名为回复.下面是一个 JSON 响应的示例:

<预><代码>[{"kind":"列表",数据":{孩子们":[{数据":{"body":"评论",回复":{"kind":"列表",数据":{孩子们":[{数据":{"body":"回复评论",回复":"}}]}}}}]}}]

这是我如何使用 POJO 进行建模.上面的响应将被视为 CommentListings 列表.

公共类 CommentListing {@SerializedName("数据")私人 CommentListingData 数据;}公共最终类 CommentListingData {@SerializedName("儿童")私人列表<评论>注释;}公开课评论{@SerializedName("数据")私人评论数据数据;}公共类 CommentData {@SerializedName("body")私有字符串体;@SerializedName("回复")私人评论列表回复;}

注意底层的 CommentData POJO 如何引用另一个名为回复"的 CommentListing.

这个模型一直工作到 GSON 到达最后一个没有回复的子 CommentData 为止.API 提供了一个空字符串,而不是提供 null.自然地,这会导致 GSON 异常,它需要一个对象但找到一个字符串:

"回复":""

预期为 BEGIN_OBJECT,但为 STRING

我尝试在 CommentData 类上创建自定义反序列化器,但由于模型的递归性质,它似乎没有到达模型的底层.我想这是因为我使用了一个单独的 GSON 实例来完成反序列化.

@Singleton@提供Gson提供Gson(){Gson gson = new Gson();返回新的 GsonBuilder().registerTypeAdapter(CommentData.class, new JsonDeserializer() {@覆盖public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 抛出 JsonParseException {JsonObject commentDataJsonObj = json.getAsJsonObject();JsonElement 回复JsonObj = commentDataJsonObj.get("回复");if (replysJsonObj != null && repliesJsonObj.isJsonPrimitive()) {commentDataJsonObj.remove("回复");}返回 gson.fromJson(commentDataJsonObj, CommentData.class);}}).serializeNulls().创造();}

如何强制 GSON 返回空值而不是字符串,以便它不会尝试将字符串强制输入到我的 POJO 中?或者,如果这不可能,手动协调数据问题?如果您需要其他上下文或信息,请告诉我.谢谢.

解决方案

总的来说,你的代码看起来不错,但我会推荐一些东西:

  • 您的类型适配器不应从外部捕获 Gson 实例.类型适配器工厂 (TypeAdapterFactory) 就是为此目的而设计的.此外,在 JSON 序列化器和反序列化器中,您可以分别通过 JsonSerializationContextJsonDeserializationContext 隐式引用它(这可以避免在某些情况下无限递归).
  • 尽可能避免修改内存中的 JSON 对象:序列化器和反序列化器只是一种管道,不应为修改后的对象带来惊喜.
  • 您可以实现一个通用的空字符串为空"类型的反序列化器,并注释每个需要这种反序列化策略的坏"字段.您可能会认为它很乏味,但它可以让您在任何需要的地方完全控制(我不知道 Reddit API 是否有更多这样的怪癖).

public final class EmptyStringAsNullTypeAdapter实现 JsonDeserializer<T>{//让 Gson 自己实例化它私有 EmptyStringAsNullTypeAdapter() {}@覆盖public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)抛出 JsonParseException {如果 ( jsonElement.isJsonPrimitive() ) {最终 JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();如果 ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {返回空;}}返回 context.deserialize(jsonElement, type);}}

然后只需注释 replys 字段:

@SerializedName("replys")@JsonAdapter(EmptyStringAsNullTypeAdapter.class)私人评论列表回复;

I'm retrieving comments from the Reddit API. The model is threaded such that each Comment can internally have a List of Comments, named replies. Here's an example of how a JSON response would look:

[
   {
      "kind":"Listing",
      "data":{
         "children":[
            {
               "data":{
                  "body":"comment",
                  "replies":{
                     "kind":"Listing",
                     "data":{
                        "children":[
                           {
                              "data":{
                                 "body":"reply to comment",
                                 "replies":""
                              }
                           }
                        ]
                     }
                  }
               }
            }
         ]
      }
   }
]

Here is how I model this with POJOs. The response above would be considered a List of CommentListings.

public class CommentListing {
    @SerializedName("data")
    private CommentListingData data;
}

public final class CommentListingData {
    @SerializedName("children")
    private List<Comment> comments;
}

public class Comment {
    @SerializedName("data")
    private CommentData data;
}

public class CommentData {
    @SerializedName("body")
    private String body;

    @SerializedName("replies")
    private CommentListing replies;
}

Note how the bottom level CommentData POJO refers to another CommentListing called "replies".

This model works until GSON reaches the last child CommentData where there are no replies. Rather than providing a null, the API is providing an empty String. Naturally, this causes a GSON exception where it expects an object but finds a String:

"replies":""

Expected BEGIN_OBJECT but was STRING

I attempted to create a custom deserializer on the CommentData class, but due to the recursive nature of the model it seems not to reach the bottom levels of the model. I imagine this is because I'm using a separate GSON instance to complete deserialization.

@Singleton
@Provides
Gson provideGson() {
    Gson gson = new Gson();
    return new GsonBuilder()
            .registerTypeAdapter(CommentData.class, new JsonDeserializer<CommentData>() {
                @Override
                public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                    JsonObject commentDataJsonObj = json.getAsJsonObject();
                    JsonElement repliesJsonObj = commentDataJsonObj.get("replies");
                    if (repliesJsonObj != null && repliesJsonObj.isJsonPrimitive()) {
                        commentDataJsonObj.remove("replies");
                    }

                    return gson.fromJson(commentDataJsonObj, CommentData.class);
                }
            })
            .serializeNulls()
            .create();
}

How can I force GSON to return a null instead of a String so that it doesn't try to force a String into my POJO? Or if that's not possible, manually reconcile the data issue? Please let me know if you need additional context or information. Thanks.

解决方案

In general your code looks good, but I would recommend a few things:

  • Your type adapters should not capture Gson instances from outside. Type adapter factories (TypeAdapterFactory) are designed for this purpose. Also, in JSON serializers and deserializers you can implicitly refer it through JsonSerializationContext and JsonDeserializationContext respectively (this avoids infinite recursion in some cases).
  • Avoid modification JSON objects in memory as much as possible: serializers and deserializers are just a sort of pipes and should not bring you surprises with modified objects.
  • You can implement a generic "empty string as a null" type deserializer and annotate each "bad" field that requires this kind of deserialization strategy. You might consider it's tedious, but it gives you total control wherever you need it (I don't know if Reddit API has some more quirks like this).

public final class EmptyStringAsNullTypeAdapter<T>
        implements JsonDeserializer<T> {

    // Let Gson instantiate it itself
    private EmptyStringAsNullTypeAdapter() {
    }

    @Override
    public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        if ( jsonElement.isJsonPrimitive() ) {
            final JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
            if ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {
                return null;
            }
        }
        return context.deserialize(jsonElement, type);
    }

}

And then just annotate the replies field:

@SerializedName("replies")
@JsonAdapter(EmptyStringAsNullTypeAdapter.class)
private CommentListing replies;

这篇关于如何使用 GSON 将空字符串视为空对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆