【问题标题】:How do I treat empty Strings as null objects with GSON?如何使用 GSON 将空字符串视为空对象?
【发布时间】:2018-07-25 19:13:55
【问题描述】:

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

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

这是我使用 POJO 建模的方法。上面的响应将被视为 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;
}

注意底层的 CommentData POJO 如何引用另一个名为“回复”的 CommentListing。

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

"replies":""

应为 BEGIN_OBJECT,但为 STRING

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

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

如何强制 GSON 返回 null 而不是 String,这样它就不会尝试将 String 强制到我的 POJO 中?或者,如果这不可能,手动协调数据问题?如果您需要其他上下文或信息,请告诉我。谢谢。

【问题讨论】:

    标签: java json gson reddit


    【解决方案1】:

    总的来说,您的代码看起来不错,但我建议您做一些事情:

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

    然后只注释replies 字段:

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

    【讨论】:

    • 太棒了,谢谢Lyubomyr!非常干净和灵活的解决方案。
    • 谢谢你,你拯救了我的一天
    • 我对为什么我的代码打印奇怪感到困扰。我的班级看起来像: class Person { @JsonAdapter(EmptyStringAsNullTypeAdapter::class) String name;当我尝试将此类打印为字符串时,我得到了这一行: {"name":{"value":[],"coder":0,"hash":0}}
    • SerializedName 注释是干什么用的?
    • 我正在尝试使用示例模型,我也得到了同样的结果,我的代码打印很奇怪。我的班级看起来像: class Person { @JsonAdapter(EmptyStringAsNullTypeAdapter::class) String name;当我尝试将此类打印为字符串时,我得到了这一行: {"name":{"value":[],"coder":0,"hash":0}}
    猜你喜欢
    • 2020-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-09
    • 2018-11-16
    • 1970-01-01
    相关资源
    最近更新 更多