【问题标题】:How to handle different data types with same attribute name with Gson?如何使用 Gson 处理具有相同属性名称的不同数据类型?
【发布时间】:2015-10-23 21:10:55
【问题描述】:

我目前正在使用 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 提要)或b) 一个字符串(如 NPR RSS 提要)。

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

BBC 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 提要

// 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 的文档后,我找不到现成的答案。

【问题讨论】:

    标签: java json gson deserialization json-deserialization


    【解决方案1】:

    这是我的示例代码,希望对您有所帮助

    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 in 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);
    

    【讨论】:

      【解决方案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 对象,并根据您喜欢的任何标准进行分派。

      【讨论】:

        【解决方案3】:

        根据调用将其作为对象类而不是其他类类型和类型转换

        // RSSFeedItem.java
        private Object guid;
        

        【讨论】:

        • 这是一个很好的建议,因为它可以阻止崩溃,但我最终得到了一个 LinkedTreeMap,你有一个简单的方法将 Map 转换为我的真实对象吗?否则我仍然必须使用反序列化器来避免遍历树来填充我的对象层次结构
        • 尝试在@NickCardoso的请求和评论中反序列化后获取对象的类型
        • 我有一个不相关的修复,稍后会发布。你无法获取对象的类型,Gson 无法知道。如果您将属性设置为 Object,它会创建默认的 LinkedTreeMap,它实际上是一个平展为地图的 JsonElement
        • 将make序列化为相关类型,同时淡化make作为对象@NickCardoso
        • 这不是我自己要序列化的提要,所以这不是解决方案
        【解决方案4】:

        您可以使用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 树,尽管在这种情况下差异可能可以忽略不计。

          李>

        【讨论】:

          猜你喜欢
          • 2021-02-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多