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

Java 如何使用Gson处理具有相同属性名称的不同数据类型?,java,json,gson,deserialization,json-deserialization,Java,Json,Gson,Deserialization,Json Deserialization,我目前正在使用Gson用Java编写RSS提要解析器。我将RSS的XML转换为JSON,然后使用Gson将JSON反序列化为JavaPOJO(有点迂回,但这是有原因的)。对于下面列出的提要1(BBC)反序列化,一切都进行得很好,但是对于下面列出的提要2(NPR),我开始抛出异常 我想我已经发现了问题,但我不确定如何解决它: 这两个RSS源会出现问题(例如): 对于这些不同的RSS源,一个名为“guid”的字段被返回为a)一个包含两个字段的对象(如在BBC RSS源中)或b)一个字符串(如在

我目前正在使用Gson用Java编写RSS提要解析器。我将RSS的XML转换为JSON,然后使用Gson将JSON反序列化为JavaPOJO(有点迂回,但这是有原因的)。对于下面列出的提要1(BBC)反序列化,一切都进行得很好,但是对于下面列出的提要2(NPR),我开始抛出异常

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


这两个RSS源会出现问题(例如):

  • 对于这些不同的RSS源,一个名为“guid”的字段被返回为a)一个包含两个字段的对象(如在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"
            },
        },
        {
            // ...
        }
    ]
    
    // 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"
        },
        {
          // ...
        }
    ]
    
    NPR 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"
            },
        },
        {
            // ...
        }
    ]
    
    // 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);
    
    对于bbcss提要,但在解析nprss提要时引发异常

    导致我得出这是一个类型错误的结论的具体错误如下(在尝试反序列化NPR RSS提要时):



    因此,无论如何,我要说的是:我如何处理Gson的这种情况,其中一个字段被作为可能不同的数据类型返回?我猜可能有某种技巧或注释可以达到这种效果,但我不确定,在查看Gson文档后,我找不到现成的答案。

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

    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;        
        }
    
    更新:您还可以参考
    Streams
    class(gson-2.3.1.jar)中的
    parse(JsonReader-reader)

    像这样

    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<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 + ".");
            }
        });
    
    GsonBuilder=newgsonbuilder();
    registerTypeAdapter(Guid.class,新的JsonDeserializer()){
    @凌驾
    公共Guid反序列化(JsonElement json,类型typeOfT,JsonDeserializationContext)引发JsonParseException{
    //基于json类型的分派
    if(json.isJsonObject()){
    //如果它是一个对象,我们必须反序列化它
    //进入一个子类,否则我们将有一个无限循环
    反序列化(json,GuidObject.class);
    }else if(json.isJsonPrimitive()){
    //原语很简单,只需设置最简单的
    //有意义的字段。我们也可以在这里使用GuidObject
    //但最好保持清楚。
    Guid=new GuidString();
    setContent(json.getAsString());
    返回guid;
    }
    //无法分析,引发异常
    抛出新的JsonParseException(“预期的Json对象或原语为“+Json+”);
    }
    });
    

    通过这种方式,您可以潜在地处理更复杂的JSON对象,并根据您喜欢的任何条件进行分派。

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

    // RSSFeedItem.java
    private Object guid;
    

    您可以使用
    类型适配器
    。其思想是只在不同的情况(字符串或对象)之间进行选择,并委托实际的反序列化

    注册工厂:

    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);
        }
    }
    
    public类GuidAdapterFactory实现TypeAdapterFactory{
    @凌驾
    公共类型适配器创建(Gson Gson,TypeToken类型){
    返回(TypeAdapter)新guidapter(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());
            }
        }
    }
    
    public类GuidAdapter扩展了TypeAdapter{
    私人最终Gson Gson;
    公共引导器(Gson Gson){
    this.gson=gson;
    }
    @凌驾
    公共无效写入(JsonWriter JsonWriter,Guid)引发IOException{
    抛出新的RuntimeException(“未实现”);
    }
    @凌驾
    公共Guid读取(JsonReader JsonReader)引发IOException{
    开关(jsonReader.peek()){
    大小写字符串:
    //仅一个字符串,创建对象
    返回新的Guid(jsonReader.nextString(),true);
    案例开始对象:
    //完整对象,转发到Gson
    返回gson.fromJson(jsonReader,Guid.class);
    违约:
    抛出新的RuntimeException(“预期的对象或字符串,而不是“+jsonReader.peek()”);
    }
    }
    }
    
    几句话:

    • 它只会工作,因为适配器是用属性注册的。在委派实际的反序列化时,全局注册它会触发递归调用

    • 之所以需要工厂,是因为我们需要对
      Gson
      对象的引用,否则我们可以直接注册适配器类

    • 我相信
      类型适配器
      反序列化器
      更有效,因为它不需要构建
      JsonElement
      树,尽管在这种情况下差异可能可以忽略不计


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