如何使用 GSON 将空字符串视为空对象?
How do I treat empty Strings as null objects with GSON?
我正在从 Reddit API 中检索评论。该模型是线程化的,因此每个评论都可以在内部拥有一个名为 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 是如何引用另一个名为 "replies" 的 CommentListing。
此模型一直有效,直到 GSON 到达没有回复的最后一个子 CommentData。 API 提供的不是空字符串,而是空字符串。自然地,这会导致 GSON 异常,它需要一个对象但找到一个字符串:
"replies":""
预期 BEGIN_OBJECT 但实际是 STRING
我试图在 CommentData class 上创建自定义反序列化器,但由于模型的递归性质,它似乎没有到达模型的底层。我想这是因为我正在使用一个单独的 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 return 一个 null 而不是一个字符串,这样它就不会试图将一个字符串强制到我的 POJO 中?或者,如果那不可能,请手动协调数据问题?如果您需要其他上下文或信息,请告诉我。谢谢
总的来说你的代码看起来不错,但我会推荐一些东西:
- 您的类型适配器不应从外部捕获
Gson
实例。类型适配器工厂 (TypeAdapterFactory
) 就是为此目的而设计的。此外,在 JSON 序列化器和反序列化器中,您可以分别通过 JsonSerializationContext
和 JsonDeserializationContext
隐式引用它(这在某些情况下避免了无限递归)。
- 尽可能避免修改JSON内存中的对象:序列化器和反序列化器只是一种管道,修改后的对象不会给您带来惊喜。
- 您可以实现一个通用的 "empty string as a null" 类型的反序列化器并注释每个需要这种反序列化策略的 "bad" 字段。你可能会认为它很乏味,但它可以让你在任何需要的地方完全控制(我不知道 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;
我正在从 Reddit API 中检索评论。该模型是线程化的,因此每个评论都可以在内部拥有一个名为 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 是如何引用另一个名为 "replies" 的 CommentListing。
此模型一直有效,直到 GSON 到达没有回复的最后一个子 CommentData。 API 提供的不是空字符串,而是空字符串。自然地,这会导致 GSON 异常,它需要一个对象但找到一个字符串:
"replies":""
预期 BEGIN_OBJECT 但实际是 STRING
我试图在 CommentData class 上创建自定义反序列化器,但由于模型的递归性质,它似乎没有到达模型的底层。我想这是因为我正在使用一个单独的 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 return 一个 null 而不是一个字符串,这样它就不会试图将一个字符串强制到我的 POJO 中?或者,如果那不可能,请手动协调数据问题?如果您需要其他上下文或信息,请告诉我。谢谢
总的来说你的代码看起来不错,但我会推荐一些东西:
- 您的类型适配器不应从外部捕获
Gson
实例。类型适配器工厂 (TypeAdapterFactory
) 就是为此目的而设计的。此外,在 JSON 序列化器和反序列化器中,您可以分别通过JsonSerializationContext
和JsonDeserializationContext
隐式引用它(这在某些情况下避免了无限递归)。 - 尽可能避免修改JSON内存中的对象:序列化器和反序列化器只是一种管道,修改后的对象不会给您带来惊喜。
- 您可以实现一个通用的 "empty string as a null" 类型的反序列化器并注释每个需要这种反序列化策略的 "bad" 字段。你可能会认为它很乏味,但它可以让你在任何需要的地方完全控制(我不知道 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;