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

我目前正在使用Gson在Java中编写RSS提要解析器。 我正在将RSS’XML转换为JSON,然后使用Gson将JSON反序列化为Java POJO(有点迂回,但有一个原因)。 对于下面列出的Feed#1( BBC )进行反序列化,一切都运行正常,但对于下面列出的Feed#2( NPR ),我开始抛出exception。

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


问题出现在这两个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提要时会抛出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 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) 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对象的引用,否则我们可以直接注册适配器类。

  • 我相信TypeAdapterDeserializer更有效,因为它不需要构建JsonElement树,尽管在这种情况下差异可能是微不足道的。

根据调用将其设置为Object Class而不是Other Class Type和Type cast

 // RSSFeedItem.java private Object guid;