如何使用Gson处理具有相同属性名称的不同数据类型?
我目前正在使用Gson在Java中编写RSS提要解析器。 我正在将RSS’XML转换为JSON,然后使用Gson将JSON反序列化为Java POJO(有点迂回,但有一个原因)。 对于下面列出的Feed#1( BBC )进行反序列化,一切都运行正常,但对于下面列出的Feed#2( NPR ),我开始抛出exception。
我想我已经确定了问题,但我不确定如何解决它:
问题出现在这两个RSS源(例如):
- http://feeds.bbci.co.uk/news/rss.xml
- 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提要时会抛出exception。
导致我得出这个类型错误的结论的具体错误如下(当试图反序列化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的文档后,我找不到一个现成的答案。
这是我的示例代码,希望您觉得它有用
public List readData(InputStream inputStream, Class clazz) throws Exception { 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);
我的答案是使用类层次结构。
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() { @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 TypeAdapter create(Gson gson, TypeToken type) { return (TypeAdapter ) new GuidAdapter(gson); } }
这决定了如何处理guid:
public class GuidAdapter extends TypeAdapter { 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
对象的引用,否则我们可以直接注册适配器类。 -
我相信
TypeAdapter
比Deserializer
更有效,因为它不需要构建JsonElement
树,尽管在这种情况下差异可能是微不足道的。
根据调用将其设置为Object Class而不是Other Class Type和Type cast
// RSSFeedItem.java private Object guid;