简体   繁体   English

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

[英]How to handle different data types with same attribute name with Gson?

I'm currently writing an RSS feed parser in Java utilizing Gson. 我目前正在使用Gson在Java中编写RSS提要解析器。 I'm converting the RSS' XML into JSON, and then subsequently using Gson to deserialize the JSON into Java POJOs (somewhat roundabout but there's a reason for it). 我正在将RSS'XML转换为JSON,然后使用Gson将JSON反序列化为Java POJO(有点迂回,但有一个原因)。 Everything was working fine as far as deserializing for the feed #1 ( BBC ) listed below, but for the feed #2 ( NPR ) listed below, I started getting exceptions being thrown. 对于下面列出的Feed#1( BBC )进行反序列化,一切都运行正常,但对于下面列出的Feed#2( NPR ),我开始抛出异常。

I think I have identified the problem, but I'm uncertain as to how to resolve it: 我想我已经确定了问题,但我不确定如何解决它:


The issue is arising with these two RSS Feeds (for example): 问题出现在这两个RSS源(例如):

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

For these different RSS feeds, a field called "guid" is being returned as either a) an object with 2 fields (as in the BBC RSS Feed) or b) a string (as in the NPR RSS Feed). 对于这些不同的RSS源,称为“guid”的字段作为a)具有2个字段的对象 (如在BBC RSS Feed中)或b) 字符串 (如在NPR RSS Feed中)返回。

Here's some paraphrased versions of the relevant JSON: 以下是相关JSON的一些释义版本:

BBC RSS Feed BBC RSS Feed

// 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 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"
    },
    {
      // ...
    }
]

I'm modeling this in Java like this: 我在Java中用这样建模:

// RSSFeedItem.java
private Guid guid;

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

So in this case, it works perfectly fine calling 所以在这种情况下,它可以完美地调用

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

for the BBC RSS feed, but it throws an exception when parsing the NPR RSS feed. 对于BBC RSS提要,但在解析NPR RSS提要时会抛出异常。

The specific error that led me to the conclusion that this is a type error was the following (when trying to deserialize the NPR RSS feed): 导致我得出这个类型错误的结论的具体错误如下(当试图反序列化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

So anyway, to the point: how can I handle this situation with Gson, where a field is being returned as potentially different data types? 所以,无论如何,关键是: 我如何处理Gson的这种情况,其中一个字段作为可能不同的数据类型被返回? I'm guessing there might be some sort of trick or annotation I could use to this effect, but I'm not certain and after checking the documentation for Gson I couldn't find a readily available answer. 我猜可能有某种技巧或注释我可以使用这种效果,但我不确定,在检查了Gson的文档后,我找不到一个现成的答案。

My answer is to make use of a class hierarchy. 我的答案是使用类层次结构。

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; 
}

And register a deserializer for 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 + ".");
        }
    });

This way you can potentially handle much more complex JSON objects, and dispatch based on whatever criteria you like. 这样您就可以处理更复杂的JSON对象,并根据您喜欢的任何条件进行调度。

You can use a TypeAdapter . 您可以使用TypeAdapter The idea is to only choose between the different cases (string or object), and delegate the actual deserialization. 我们的想法是只在不同的情况(字符串或对象)之间进行选择,并委托实际的反序列化。

Register the Factory : 注册工厂:

public class RSSFeedItem {

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

which creates the adapter: 这会创建适配器:

public class GuidAdapterFactory implements TypeAdapterFactory {

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

which makes the decision how to handle the guid : 这决定了如何处理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());
        }
    }
}

A few remarks : 几点评论:

  • It only works because the adapter is registered with an attribute. 它只能工作,因为适配器已注册属性。 Registering it globally triggers a recursive call when the actual deserialization is delegated. 在委托实际反序列化时,全局注册会触发递归调用。

  • The factory is only needed because we need a reference to the Gson object, otherwise we could directly register the adapter class. 只需要工厂,因为我们需要引用Gson对象,否则我们可以直接注册适配器类。

  • I believe a TypeAdapter is more efficient than a Deserializer because it does not need a JsonElement tree to be build, although in this case the difference is probably negligible. 我相信TypeAdapterDeserializer更有效,因为它不需要构建JsonElement树,尽管在这种情况下差异可能是微不足道的。

Make it as Object Class instead of Other Class Type and Type cast according to the call 根据调用将其设置为Object Class而不是Other Class Type和Type cast

// RSSFeedItem.java
private Object guid;

Here is my sample code, hope you find it helpful 这是我的示例代码,希望您觉得它有用

public <T> List<T> readData(InputStream inputStream, Class<T> clazz) throws Exception {        
            ArrayList<Object> arrayList = new ArrayList<>();            
            GsonBuilder gsonBuilder = new GsonBuilder();
            Gson gson = gsonBuilder.create();
            JsonReader jsonReader = new JsonReader(new InputStreamReader(inputStream, "UTF_8"));
            jsonReader.setLenient(true);
            JsonToken jsonToken = jsonReader.peek();
            switch (jsonToken) {
                case BEGIN_ARRAY:
                    jsonReader.beginArray();
                    while (jsonReader.hasNext()) {
                        arrayList.add(gson.fromJson(jsonReader, clazz));
                    }
                    jsonReader.endArray();
                    break;
                case BEGIN_OBJECT:
                    T data = clazz.cast(gson.fromJson(jsonReader, clazz));
                    arrayList.add(data);
                    break;
                case NUMBER:
                    Integer number = Integer.parseInt(jsonReader.nextString());
                    arrayList.add(number);
                    break;
                default:
                    jsonReader.close();
                    inputStream.close();
                    return Collections.emptyList();
            }
            jsonReader.close();
            inputStream.close();
            return (List<T>) arrayList;        
    }

Another one is parseRecursive in Streams.java (you can Google search) as below: 另一个是parseRecursive中的Streams.java (你可以谷歌搜索)如下:

private static JsonElement parseRecursive(JsonReader reader)
            throws IOException {
        switch (reader.peek()) {
        case STRING:
            return new JsonPrimitive(reader.nextString());
        case NUMBER:
            String number = reader.nextString();
            return new JsonPrimitive(JsonPrimitive.stringToNumber(number));
        case BOOLEAN:
            return new JsonPrimitive(reader.nextBoolean());
        case NULL:
            reader.nextNull();
            return JsonNull.createJsonNull();
        case BEGIN_ARRAY:
            JsonArray array = new JsonArray();
            reader.beginArray();
            while (reader.hasNext()) {
                array.add(parseRecursive(reader));
            }
            reader.endArray();
            return array;
        case BEGIN_OBJECT:
            JsonObject object = new JsonObject();
            reader.beginObject();
            while (reader.hasNext()) {
                object.add(reader.nextName(), parseRecursive(reader));
            }
            reader.endObject();
            return object;
        case END_DOCUMENT:
        case NAME:
        case END_OBJECT:
        case END_ARRAY:
        default:
            throw new IllegalArgumentException();
        }
    }

UPDATE: you can also refer to parse(JsonReader reader) in Streams class (gson-2.3.1.jar) 更新:您还可以在Streams类中引用parse(JsonReader reader) (gson-2.3.1.jar)

Like this 像这样

JsonElement jsonElement = Streams.parse(jsonReader);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM