I\'m consuming an API from my android app, and all the JSON responses are like this:
{
\'status\': \'OK\',
\'reason\': \'Everything was fine\',
\
Another simple solution:
JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);
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();
@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.
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(..);
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
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.