Get nested JSON object with GSON using retrofit

前端 未结 13 760
挽巷
挽巷 2020-11-22 17:04

I\'m consuming an API from my android app, and all the JSON responses are like this:

{
    \'status\': \'OK\',
    \'reason\': \'Everything was fine\',
    \         


        
相关标签:
13条回答
  • 2020-11-22 17:28

    Another simple solution:

    JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
    Content content = gson.fromJson(parsed.get("content"), Content.class);
    
    0 讨论(0)
  • 2020-11-22 17:29

    Continuing Brian's idea, because we almost always have many REST resources each with it's own root, it could be useful to generalize deserialization:

     class RestDeserializer<T> implements JsonDeserializer<T> {
    
        private Class<T> mClass;
        private String mKey;
    
        public RestDeserializer(Class<T> targetClass, String key) {
            mClass = targetClass;
            mKey = key;
        }
    
        @Override
        public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                throws JsonParseException {
            JsonElement content = je.getAsJsonObject().get(mKey);
            return new Gson().fromJson(content, mClass);
    
        }
    }
    

    Then to parse sample payload from above, we can register GSON deserializer:

    Gson gson = new GsonBuilder()
        .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
        .build();
    
    0 讨论(0)
  • 2020-11-22 17:31

    @BrianRoach's solution is the correct solution. It is worth noting that in the special case where you have nested custom objects that both need a custom TypeAdapter, you must register the TypeAdapter with the new instance of GSON, otherwise the second TypeAdapter will never be called. This is because we are creating a new Gson instance inside our custom deserializer.

    For example, if you had the following json:

    {
        "status": "OK",
        "reason": "some reason",
        "content": {
            "foo": 123,
            "bar": "some value",
            "subcontent": {
                "useless": "field",
                "data": {
                    "baz": "values"
                }
            }
        }
    }
    

    And you wanted this JSON to be mapped to the following objects:

    class MainContent
    {
        public int foo;
        public String bar;
        public SubContent subcontent;
    }
    
    class SubContent
    {
        public String baz;
    }
    

    You would need to register the SubContent's TypeAdapter. To be more robust, you could do the following:

    public class MyDeserializer<T> implements JsonDeserializer<T> {
        private final Class mNestedClazz;
        private final Object mNestedDeserializer;
    
        public MyDeserializer(Class nestedClazz, Object nestedDeserializer) {
            mNestedClazz = nestedClazz;
            mNestedDeserializer = nestedDeserializer;
        }
    
        @Override
        public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
            // Get the "content" element from the parsed JSON
            JsonElement content = je.getAsJsonObject().get("content");
    
            // Deserialize it. You use a new instance of Gson to avoid infinite recursion
            // to this deserializer
            GsonBuilder builder = new GsonBuilder();
            if (mNestedClazz != null && mNestedDeserializer != null) {
                builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer);
            }
            return builder.create().fromJson(content, type);
    
        }
    }
    

    and then create it like so:

    MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class,
                        new SubContentDeserializer());
    Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();
    

    This could easily be used for the nested "content" case as well by simply passing in a new instance of MyDeserializer with null values.

    0 讨论(0)
  • 2020-11-22 17:34

    A better solution could be this..

    public class ApiResponse<T> {
        public T data;
        public String status;
        public String reason;
    }
    

    Then, define your service like this..

    Observable<ApiResponse<YourClass>> updateDevice(..);
    
    0 讨论(0)
  • 2020-11-22 17:37

    Don't forget @SerializedName and @Expose annotations for all Class members and Inner Class members that most deserialized from JSON by GSON.

    Look at https://stackoverflow.com/a/40239512/1676736

    0 讨论(0)
  • 2020-11-22 17:42

    There is a simpler way, just consider content sub object as another class:

    class Content {
        var foo = 0
        var bar: String? = null
    }
    
    class Response {
        var statis: String? = null
        var reason: String? = null
        var content: Content? = null
    } 
    

    and now you can use Response type to deserialize json.

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