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

前端 未结 4 1517
北恋
北恋 2020-12-10 04:31

I\'m currently writing an RSS feed parser in Java utilizing Gson. I\'m converting the RSS\' XML into JSON, and then subsequently using Gson to deserialize the JSON into Java

相关标签:
4条回答
  • 2020-12-10 05:08

    Here is my sample code, hope you find it helpful

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

    Another one is parseRecursive in Streams.java (you can Google search) as below:

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

    UPDATE: you can also refer to parse(JsonReader reader) in Streams class (gson-2.3.1.jar)

    Like this

    JsonElement jsonElement = Streams.parse(jsonReader);
    
    0 讨论(0)
  • 2020-12-10 05:11

    You can use a TypeAdapter. The idea is to only choose between the different cases (string or object), and delegate the actual deserialization.

    Register the Factory :

    public class RSSFeedItem {
    
        @JsonAdapter(GuidAdapterFactory.class)
        private Guid guid;
    }
    

    which creates the adapter:

    public class GuidAdapterFactory implements TypeAdapterFactory {
    
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            return (TypeAdapter<T>) new GuidAdapter(gson);
        }
    }
    

    which makes the decision how to handle the 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());
            }
        }
    }
    

    A few remarks :

    • It only works because the adapter is registered with an attribute. Registering it globally triggers a recursive call when the actual deserialization is delegated.

    • The factory is only needed because we need a reference to the Gson object, otherwise we could directly register the adapter class.

    • I believe a TypeAdapter is more efficient than a Deserializer because it does not need a JsonElement tree to be build, although in this case the difference is probably negligible.

    0 讨论(0)
  • 2020-12-10 05:11

    Make it as Object Class instead of Other Class Type and Type cast according to the call

    // RSSFeedItem.java
    private Object guid;
    
    0 讨论(0)
  • 2020-12-10 05:25

    My answer is to make use of a class hierarchy.

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

    And register a deserializer for 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 + ".");
            }
        });
    

    This way you can potentially handle much more complex JSON objects, and dispatch based on whatever criteria you like.

    0 讨论(0)
提交回复
热议问题