簡體   English   中英

如何使用Gson處理具有相同屬性名稱的不同數據類型?

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

我目前正在使用Gson在Java中編寫RSS提要解析器。 我正在將RSS'XML轉換為JSON,然后使用Gson將JSON反序列化為Java POJO(有點迂回,但有一個原因)。 對於下面列出的Feed#1( BBC )進行反序列化,一切都運行正常,但對於下面列出的Feed#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 Feed中)或b) 字符串 (如在NPR RSS Feed中)返回。

以下是相關JSON的一些釋義版本:

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

// 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的文檔后,我找不到一個現成的答案。

我的答案是使用類層次結構。

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對象,並根據您喜歡的任何條件進行調度。

您可以使用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對象,否則我們可以直接注冊適配器類。

  • 我相信TypeAdapterDeserializer更有效,因為它不需要構建JsonElement樹,盡管在這種情況下差異可能是微不足道的。

根據調用將其設置為Object Class而不是Other Class Type和Type cast

// RSSFeedItem.java
private Object guid;

這是我的示例代碼,希望您覺得它有用

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

另一個是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();
        }
    }

更新:您還可以在Streams類中引用parse(JsonReader reader) (gson-2.3.1.jar)

像這樣

JsonElement jsonElement = Streams.parse(jsonReader);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM