如何使用Gson处理具有相同属性名称的不同数据类型?

我目前正在使用Gson用Java编写一个RSS提要解析器。我正在将RSS的XML转换为JSON,然后使用Gson将JSON反序列化为Java POJO(有点迂回,但这是有原因的)。就下面列出的源#1(BBC)的反序列化而言,一切正常,但是对于下面列出的源#2(NPR),我开始抛出异常。

我想我已经确定了问题,但我不确定如何解决它:


以下两个 RSS 源(例如)会出现此问题:

  1. http://feeds.bbci.co.uk/news/rss.xml
  2. http://www.npr.org/rss/rss.php?id=1001

对于这些不同的 RSS 源,将返回一个名为“guid”的字段,作为 a) 具有 2 个字段的对象(如 BBC RSS 源中)或 b) 字符串(如 NPR RSS 源中)。

以下是相关 JSON 的一些释义版本:

英国广播公司RSS提要

// is returning 'guid' as an object
"item" : 
[
    {
        // omitted other fields for brevity
        "guid" : {
            "isPermalink" : false,
            "content" : "http:\/\/www.bbc.co.uk\/news\/uk-england-33745057"
        },
    },
    {
        // ...
    }
]

NPR RSS Feed

// is returning 'guid' as a string
"item" : 
[
    {
      // omitted other fields for brevity
      "guid" : "http:\/\/www.npr.org\/sections\/thetwo-way\/2015\/07\/31\/428188125\/chimps-in-habeas-corpus-case-will-no-longer-be-used-for-research?utm_medium=RSS&utm_campaign=news"
    },
    {
      // ...
    }
]

我在Java中建模,如下所示:

// RSSFeedItem.java
private Guid guid;

// GUID.java
private boolean isPermalink;
private String content;

因此,在这种情况下,它可以很好地调用

Gson gson = new Gson();
RssFeed rssFeed = gson.fromJson(jsonData, RssFeed.class);

对于 BBC RSS 源,但在解析 NPR RSS 源时会引发异常。

导致我得出这是类型错误的结论的具体错误如下(当尝试反序列化NPR RSS源时):

Severe:    com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
           Expected BEGIN_OBJECT but was STRING at line 1 column 673 path
           $.rss.channel.item[0].guid

所以无论如何,到了这个地步:我如何处理Gson的这种情况,其中字段作为可能不同的数据类型返回?我猜可能有某种技巧或注释可以用来达到这种效果,但我不确定,在检查了Gson的文档后,我找不到现成的答案。


答案 1

您可以使用 .我们的想法是仅在不同的情况(字符串或对象)之间进行选择,并委托实际的反序列化。TypeAdapter

注册工厂 :

public class RSSFeedItem {

    @JsonAdapter(GuidAdapterFactory.class)
    private Guid guid;
}

这将创建适配器:

public class GuidAdapterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        return (TypeAdapter<T>) new GuidAdapter(gson);
    }
}

这决定了如何处理guid:

public class GuidAdapter extends TypeAdapter<Guid> {

    private final Gson gson;

    public GuidAdapter(Gson gson) {
        this.gson = gson;
    }

    @Override
    public void write(JsonWriter jsonWriter, Guid guid) throws IOException {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public Guid read(JsonReader jsonReader) throws IOException {
        switch (jsonReader.peek()) {
            case STRING:
                // only a String, create the object
                return new Guid(jsonReader.nextString(), true);

            case BEGIN_OBJECT:
                // full object, forward to Gson
                return gson.fromJson(jsonReader, Guid.class);

            default:
                throw new RuntimeException("Expected object or string, not " + jsonReader.peek());
        }
    }
}

几点说明:

  • 它之所以有效,是因为适配器已注册了属性。全局注册它会在委派实际反序列化时触发递归调用。

  • 之所以需要工厂,是因为我们需要对对象的引用,否则我们可以直接注册适配器类。Gson

  • 我相信 a 比 a 更有效,因为它不需要构建树,尽管在这种情况下,差异可以忽略不计。TypeAdapterDeserializerJsonElement


答案 2

我的答案是利用类层次结构。

abstract class Guid {
    private boolean isPermalink;
    private String content;
    // getters and setters omitted
}

class GuidObject extends Guid {} 
class GuidString extends Guid {}

class RssFeedItem {
    // super class to receive instances of sub classes
    private Guid guid; 
}

并注册一个反序列化程序:Guid

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Guid.class, new JsonDeserializer<Guid>() {
        @Override
        public Guid deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            // Dispatch based on the type of json
            if (json.isJsonObject()) {
                // If it's an object, it's essential we deserialize
                // into a sub class, otherwise we'll have an infinite loop
                return context.deserialize(json, GuidObject.class);
            } else if (json.isJsonPrimitive()) {
                // Primitive is easy, just set the most
                // meaningful field. We can also use GuidObject here
                // But better to keep it clear.
                Guid guid = new GuidString();
                guid.setContent(json.getAsString());
                return guid;
            }
            // Cannot parse, throw exception
            throw new JsonParseException("Expected Json Object or Primitive, was " + json + ".");
        }
    });

通过这种方式,您可以潜在地处理更复杂的 JSON 对象,并根据您喜欢的任何条件进行调度。