更新时间:2023-11-01 16:50:46
总的来说,你的代码看起来不错,但我会推荐一些东西:
Gson
实例.类型适配器工厂 (TypeAdapterFactory
) 就是为此目的而设计的.此外,在 JSON 序列化器和反序列化器中,您可以分别通过 JsonSerializationContext
和 JsonDeserializationContext
隐式引用它(这可以避免在某些情况下无限递归).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:
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).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;