Custom GSON deserialiser - android

I've got multiple API responses in this format:
{
status: "OK",
error: null,
data: [
]
}
Where the "data" field is different (single item and list of items)... So I've written a custom deserializer:
public class CustomDeserializer<T> implements JsonDeserializer<ServerResponse<T>> {
private Type t;
public CustomDeserializer() {
}
public CustomDeserializer(Type t) {
this.t = t;
}
#Override
public ServerResponse<T> deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
Gson gson = new Gson();
ServerResponse<T> serverResponse = new ServerResponse<>();
serverResponse.setError(gson.fromJson(je.getAsJsonObject(), Error.class));
serverResponse.setStatus(gson.fromJson(je.getAsJsonObject(), ResponseStatus.class));
if (je.getAsJsonObject().get("data").isJsonArray()) {
JsonArray arr = je.getAsJsonObject().getAsJsonArray("data");
T[] a = (T[]) new Object[arr.size()];
for (int i = 0; i < arr.size(); i++) {
a[i] = gson.fromJson(arr.get(i), t);
}
((ServerResponse<List<T>>) serverResponse).setData(Arrays.asList(a));
return serverResponse;
} else {
T data = gson.fromJson(je.getAsJsonObject().get("data"), t);
serverResponse.setData(data);
return serverResponse;
}
}
}
Gson setup:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer<Category>(Category.class))
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer<City>(City.class))
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer<Business>(Business.class))
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer<BusinessListItem>(BusinessListItem.class))
.create();
But when the response come... The deserializer deserialize it in the wrong class...
Do you have any suggestions how I can fix that problem?

It depends on your data structure. If you have
public static class ServerResponse<T>{
private Error error;
private ResponseStatus status;
private List<T> data;
// getters, setters , tostring
}
You can use this generic deserializer:
public static class CustomDeserializer implements JsonDeserializer<ServerResponse> {
#Override
public ServerResponse deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
Type t = ((ParameterizedType) type).getActualTypeArguments()[0];
JsonObject jObject = (JsonObject) je;
ServerResponse serverResponse = new ServerResponse();
JsonElement dataElement = jObject.get("data");
JsonElement errorElement = jObject.get("error");
JsonElement statusElement = jObject.get("status");
Error error = jdc.deserialize(errorElement, Error.class);
ResponseStatus status = jdc.deserialize(statusElement, ResponseStatus.class);
ArrayList dataObj;
if (dataElement.isJsonArray()) {
JsonArray array = dataElement.getAsJsonArray();
Type listT = ParameterizedTypeImpl.make(List.class, new Type[]{t}, null);
dataObj = jdc.deserialize(array, listT);
} else {
dataObj = new ArrayList();
dataObj.add(jdc.deserialize(dataElement, t));
}
serverResponse.setError(error);
serverResponse.setStatus(status);
serverResponse.setData(dataObj);
return serverResponse;
}
}
Use case:
Gson gson = new GsonBuilder().registerTypeAdapter(ServerResponse.class,new CustomDeserializer()).create();
ServerResponse<String> resp = gson.fromJson(jsonString, new TypeToken<ServerResponse<String>>() {}.getType());
Edit post update after Deserializing JSON into a class with a generic argument using GSON or Jackson

Related

How to search a key in JSON Object if format is not constant?

In my android application, I have saved my form's data in a json, the parent of this json is a json object that may contains simple key value pairs, or a JSON Array, or sub json object.
The structure of Parent JSON Object is basically changing on run time and i have to add a function that can find value saved against any key.
For example, if I have the following JSON and I want to get value of key5. Is there any way to get the value?
A solution that i want to try is loop through the json but i am unable to check what is stored on index of json object mean is it a simple string, a json array or a sub json object.
{
"key1": "value of key 1",
"key2": "value of key 2",
"key3": "value of key 3",
"subjson": {
"key4": "value of key 4",
"key5": "value of key 5",
"key6": "value of key 6"
},
"subjsonArray": [
{
"key7": "value of key 7",
"key8": "value of key 8"
},
{
"key9": "value of key 9",
"key10": "value of key 10"
}
]
}
Can someone please help me out with this. Any help will be appreciated.
Here is an example to handle json
JsonElement jsonElement = new Gson().fromJson("YOUR JSON", JsonElement.class);
if(jsonElement.isJsonObject()){
jsonElement.getAsJsonObject("key5");
}else if(jsonElement.isJsonArray()){
jsonElement.getAsJsonArray("key5");
}
If you need to iterate over json you can use
Set<Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
for(Map.Entry<String,JsonElement> entry : entrySet){
String key = entry.getKey();
JsonElement value = jsonObject.get(entry.getKey()));
}
You need to import Gson to your project
dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
}
More information how to use Gson:
https://github.com/google/gson
Let me start by saying this - you should try to fix the non-constant Json issue before finding a solution for this.
But if it's out of your control, here's what might work.
This handles nested JsonObject and/or JsonArray.
You can just copy/paste those functions.
All you have to do is call this function - getValueFromNonConstantJson(...)
private void getValueFromNonConstantJson(String fullJson, String key) {
Gson gson = new Gson();
final Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> objectMap = gson.fromJson(fullJson, mapType);
String value = "";
value = getValueFromMap(objectMap, key);
}
private String getValueFromMap(Map<String, Object> objectMap, String key) {
String result = "";
// let's quickly check is the root object contains the key you want
// if it does, you don't have to start the for loop at all
if(objectMap.containsKey("key5")) {
Object key5Value = objectMap.get("key5");
if(key5Value instanceof JsonObject) {
JsonObject jsonObject = (JsonObject) key5Value;
result = getValueFromJsonObject(jsonObject, key);
} else if (key5Value instanceof JsonArray) {
JsonArray jsonArray = (JsonArray) key5Value;
result = getValueFromJsonArray(jsonArray, key);
}
} else {
for(Map.Entry<String, Object> entry : objectMap.entrySet()) {
if(entry.getValue() instanceof JsonObject) {
JsonObject jsonObject = (JsonObject) entry.getValue();
result = getValueFromJsonObject(jsonObject, key);
if(!result.isEmpty()) {
// you found it.
break;
}
} else if (entry.getValue() instanceof JsonArray) {
JsonArray jsonArray = (JsonArray) entry.getValue();
result = getValueFromJsonArray(jsonArray, key);
if(!result.isEmpty()) {
// you found it.
break;
}
}
}
}
return result;
}
private String getValueFromJsonObject(JsonObject subJsonObject, String key) {
String value = "";
Gson gson = new Gson();
final Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> objectMap = gson.fromJson(subJsonObject, mapType);
value = getValueFromMap(objectMap, key);
return value;
}
private String getValueFromJsonArray(JsonArray subJsonArray, String key) {
String value = "";
Gson gson = new Gson();
Object[] objectArr = gson.fromJson(subJsonArray, Object[].class);
for(Object object : objectArr) {
if(object instanceof Map) {
Map<String, Object> objectMap = (Map<String, Object>) object;
value = getValueFromMap(objectMap, key);
if(!value.isEmpty()) {
break;
}
}
}
return value;
}
Using Retrofit, you can achieve it like this :
Below is the Model class which extends JsonElement as the JsonResponse structure is undefined
public class JsonResponse extends JsonElement {
//Key will be of type JsonElement
#SerializedName("key1")
public JsonElement key1;
//Return type is JsonElement since it is decided at Runtime
public JsonElement getKey1() {
if (key1 instanceof JsonArray) {
List<JsonObject> objectList = getArray(key1);
return (JsonElement) objectList; //If key is of type JsonArray then return JsonArray
} else {
return getObject(key1); //If key is of type JsonObject then return JsonObject
}
}
public void setKey1(JsonElement key1) {
this.key1 = key1;
}
private JsonObject getObject(JsonElement data) {
Type type = new TypeToken<JsonObject>() {}.getType();
return new Gson().fromJson(data, type);
}
private List<JsonObject> getArray(JsonElement data) {
Type type = new TypeToken<List<JsonObject>>() {}.getType();
return new Gson().fromJson(((JsonObject) data).get("key1"), type);
}
//Method from super class
#Override
public JsonElement deepCopy() {
return null;
}
}
Same process can be repeated for the keys whose return-type is not defined.
Then in your ClassA you can fetch data by directly accessing key1 element from JsonResponse class.
In your ClassA, check if this element key1 is of type JsonArray or not and fetch data accordingly.
Using JSONObject a possible implementation can be
private Object interateNestedJson(JSONObject jsonObject, String keySearch) {
for (Iterator<String> it = jsonObject.keys(); it.hasNext(); ) {
try {
String jsonKey = it.next();
Object jsonValue = jsonObject.get(jsonKey);
if (jsonValue instanceof JSONArray) {
Object foundInArray = iterateArray((JSONArray) jsonValue, keySearch);
if(foundInArray != null) {
return foundInArray;
}
} else if (jsonValue instanceof JSONObject) {
Object foundInObject = interateNestedJson(jsonObject, keySearch);
if(foundInObject != null) {
return foundInObject;
}
} else {
return jsonValue; // return primitive value
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
private Object iterateArray(JSONArray jsonArray, String keySearch) throws JSONException {
for (int i = 0; i < jsonArray.length(); i++) {
Object jsonObject = jsonArray.get(i);
if (jsonObject instanceof JSONObject) {
Object inNested = interateNestedJson((JSONObject) jsonObject, keySearch);
if (inNested != null) {
return inNested;
}
} else if (jsonObject instanceof JSONArray) {
Object inArray = iterateArray((JSONArray) jsonObject, keySearch);
if (inArray != null) {
return inArray;
}
}
}
return null;
}
And you start the search like this
JSONObject json = new JSONObject(jsonString);
Object value = interateNestedJson(jsonObject, "key5");
I have no time to test this now, if it has an error I'll try to fix it later

Avoid adding null POJO objects to parsed array from retrofit response

I need to avoid returning an array with null items after parsing a response from a Web Service, but I cannot figure out how to configure Gson to do so. I intended to do it this way as I don't like having to process the parsed response (having to loop the array again).
Let me show you a basic example of what I mean.
Given this POJO:
public class Item {
#SerializedName("_id")
private String id; // Required field
private String name;
#SerializedName("accept_ads")
private boolean acceptAds;
// Getters and setters
}
And given a JSON response from a webservice:
[
{"_id": "a01",
"name": "Test1",
"accept_ads": true},
{"name": "Test2"}
]
I created this deserializer:
public class ItemDeserializer implements JsonDeserializer<Item> {
#Override
public Item deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Item item = null;
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("_id")) {
item = new Item();
item.setId(jsonObject.get("_id").getAsString());
if (jsonObject.has("name")) {
item.setName(jsonObject.get("name").getAsString());
}
if (jsonObject.has("accept_ads")) {
item.setAcceptAds(jsonObject.get("accept_ads").getAsBoolean());
}
}
return item;
}
}
Then, I create my Gson and Retrofit instances like this:
Gson gson = new GsonBuilder().registerTypeAdapter(Item.class, new ItemDeserializer()).create();
mRetrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
Unfortunately, the returned ArrayList<Item> contains both an Item and a null value.
UPATE
Thanks to #deluxe1 in the comments below, I read the linked SO thread, which by itself isn't what I was looking for, as it's about serializers instead of deseralizers. However, another answer in that thread specifically focuses on deserializers.
Unfortunatedly, I'm unable to make it work. Please notice that, in that case, is about null JsonElements inside the JSON HTTP body response, which isn't exactly what I'm trying to achieve. In my case, a JsonElement might not be null, but lacking required fields which, in the end, makes it invalid (therefore, null).
Given the example classes above, I created the following deserializer:
public class NonNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationcontext context) throws JsonParseException {
Gson gson = new GsonBuilder().create();
List<T> items = new ArrayList<>();
for (final JsonElement jsonElement : json.getAsJsonArray() {
T item = gson.fromJson(jsonElement, typeOfT);
if (item != null) {
items.add(item);
}
}
return items;
}
}
Of course, I remembered to register it as a TypeAdapter in Gson.
The thing is, typeOfT is java.util.ArrayList<com.example.Item> when that method gets invoked (But why??). As a result, the line T item = gson.fromJson(jsonElement, typeOfT) doesn't get called with the inner class (Item, in this case), causing this exception:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $
AFAIK, type erasure makes it impossible to do the following:
T item = gson.fromJson(jsonElement, T.class)
NonNullListDeserializer requires small modifications:
class NonNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json instanceof JsonArray) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return Collections.emptyList();
}
final List<T> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// get element type
Type elementType = $Gson$Types.getCollectionElementType(typeOfT, List.class);
T value = context.deserialize(array.get(i), elementType);
if (value != null) {
list.add(value);
}
}
return list;
}
return Collections.emptyList();
}
}
Now, you need to deserialise your JSON payload as below:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Item.class, new ItemDeserializer())
.registerTypeAdapter(List.class, new NonNullListDeserializer<>())
.setPrettyPrinting()
.create();
Type itemListType = new TypeToken<List<Item>>() {}.getType();
List<Item> response = gson.fromJson(json, itemListType);

Android gson AbstractAdapter.serialize is not called (polymorphic serialization)

Good day my fellow developers,
I'm struggling with gson lib from google in my Android app. I'm trying to serialize a list of objects to Json string, however without luck. My inheritance hierarchy looks like this:
interface IFloorPlanPrimitive
abstract class FloorPlanPrimitiveBase implements IFloorPlanPrimitive
class Wall extends FloorPlanPrimitiveBase
class Mark extends FloorPlanPrimitiveBase
Pretty simple. There are some of fields in each class. I searched for the matter on the web and added this adapter class to facilitate with serializing/deserializing. Currently I'm unable to serialize, so let's focus on that.
public class FloorPlanPrimitiveAdapter implements
JsonSerializer<FloorPlanPrimitiveBase>, JsonDeserializer<FloorPlanPrimitiveBase> {
#Override
public JsonElement serialize(FloorPlanPrimitiveBase src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("type", new JsonPrimitive(src.getClass().getSimpleName()));
result.add("properties", context.serialize(src, src.getClass()));
return result;
}
#Override
public FloorPlanPrimitiveBase deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String type = jsonObject.get("type").getAsString();
JsonElement element = jsonObject.get("properties");
try {
final String packageName = IFloorPlanPrimitive.class.getPackage().getName();
return context.deserialize(element, Class.forName(packageName + '.' + type));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("Unknown element type: " + type, cnfe);
}
}
}
And this is how I use it:
public String getFloorPlanAsJSon() {
GsonBuilder gsonBilder = new GsonBuilder();
gsonBilder.registerTypeAdapter(FloorPlanPrimitiveBase.class, new FloorPlanPrimitiveAdapter());
Gson gson = gsonBilder.create();
List<IFloorPlanPrimitive> floorPlan = mRenderer.getFloorPlan();
String jsonString = gson.toJson(floorPlan);
return jsonString;
}
From a simple debug I see that serialize method of FloorPlanPrimitiveAdapter is not being called when serializing and thus I don't get those "type" and "properties" fields in Json. Instead I get straight-forward Json string. I suppose this is due to mismatch in types. I'm asking to serialize IFloorPlanPrimitive, but instead pass FloorPlanPrimitiveBase which implements this interface. My expectation was that it should work :)
Can anyone point on how to deal with serialization and deserialization in this situation? How to overcome that "mismatch"?
Thank you in advance,
Kind regards, Greg.
Good day,
I want to share my solution to my own problem. I hope this will be helpful for others. Also, I would like to know if this solution has any flaws.
So, first the usage. I looked into another answer on SO (linked below). Also this issue on Gson github was also helpful (in particular I learnt there to pass a type parameter into toJson() method:
public String getFloorPlanAsJSon() {
GsonBuilder builder = new GsonBuilder();
final Type type = (new TypeToken<List<FloorPlanPrimitiveBase>>() {}).getType();
builder.registerTypeAdapter(type, new FloorPlanAdapter());
Gson gson = builder.create();
List<IFloorPlanPrimitive> floorPlan = mRenderer.getFloorPlan();
String jsonString = gson.toJson(floorPlan, type);
return jsonString;
}
And now the Adapter I actually took from this SO answer (beware it has bug in it - the name of class put into Json is ArrayList instead of class of the element):
public class FloorPlanAdapter implements JsonSerializer<List<FloorPlanPrimitiveBase>> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(List<FloorPlanPrimitiveBase> src, Type typeOfSrc,
JsonSerializationContext context) {
JsonArray array = new JsonArray();
for (FloorPlanPrimitiveBase primitiveBase : src) {
JsonObject primitiveJson = new JsonObject();
String className = primitiveBase.getClass().getCanonicalName();
primitiveJson.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(primitiveBase);
primitiveJson.add(INSTANCE, elem);
array.add(primitiveJson);
}
return array;
}
}
As you can see it loops over all objects in List<> and processes it just like FloorPlanPrimitiveBaseAdapter in my original question.
This is how I deserialize it:
#Override
public List<FloorPlanPrimitiveBase> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final String packageName = IFloorPlanPrimitive.class.getPackage().getName();
List<FloorPlanPrimitiveBase> result = new ArrayList<>();
JsonArray jsonArray = json.getAsJsonArray();
for (JsonElement element : jsonArray) {
final JsonObject asJsonObject = element.getAsJsonObject();
String className = asJsonObject.get(CLASSNAME).getAsString();
JsonElement serializedInstance = asJsonObject.get(INSTANCE);
Class<?> klass;
try {
klass = Class.forName(packageName + '.' + className);
final FloorPlanPrimitiveBase deserializedInstance = context.deserialize(serializedInstance, klass);
result.add(deserializedInstance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return result;
}

Dynamically tags parsing Json Data using gson

I have a JSoN data like this:
{
"data": {
"noofCity": "1",
"City 1": [
{
"id": "12",
"title": "Delhi"
}
]
},
"success": true
}
Now based on noofCity next tag City 1 will be generated. If noofCity will be 2 then there are two tag City 1 and City 2. Then how can I parse it using Json? Please tell me how can I generate my POJO class structure.
Your POJOs should look like below:
Main POJO for Response:
public class Response {
Data data;
boolean success;
}
For Data
public class Data {
int noofCity;
Map<String, List<City>> cityMap;
void put(String key, List<City> city){
if(cityMap == null){
cityMap = new HashMap<>();
}
cityMap.put(key, city);
}
public void setNoofCity(int noofCity) {
this.noofCity = noofCity;
}
public int getNoofCity() {
return noofCity;
}
}
For City
public class City {
int id;
String title;
}
But one of the most important think is a way how to deserialise Data. You have to prepare your own deserialiser for this, and define way how to fill HashMap as is shown in the code below:
public class DataDeserializer implements JsonDeserializer<Data> {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Data result = new Data();
Gson gson = new Gson();
JsonObject jsonObject= json.getAsJsonObject();
result.setNoofCity(jsonObject.get("noofCity").getAsInt());
for(int i =1; i<=result.getNoofCity() ; i++ ){
List<City> cities= gson.fromJson(jsonObject.getAsJsonArray("City "+ i), List.class);
result.put("City "+ i, cities);
}
return result;
}
}
And now you can deserialise you json
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class, new DataDeserializer())
.create();
Response test = gson.fromJson(json, Response.class);

How to skip null entries with Gson

When deserializing JSON with Gson, is there a way to skip null entries in a JSON array?
[
{
text: "adsfsd...",
title: "asdfsd..."
},
null,
{
text: "adsfsd...",
title: "asdfsd..."
}
]
The resulting List has 3 entries and the second one is null. I would like to configure Gson to skip the nulls but I could not find a way to do it.
You can exclude null values by writing your own custom gson JsonDeserializer
Assuming that you have your model class
class GetData {
private String title;
private String text;
}
class CustomDeserializer implements JsonDeserializer<List<GetData>> {
#Override
public List<GetData> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray jsonArray = jsonElement.getAsJsonArray();
List<GetData> list=new ArrayList<>(30);
Gson gson = new Gson();
for (JsonElement element : jsonArray) {
// skipping the null here, if not null then parse json element and add in collection
if(!(element instanceof JsonNull))
{
list.add(gson.fromJson(element, GetData.class));
}
}
return list;
}
Finally you can parse it
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Collection.class, new CustomDeserializer()).create();
gson.fromJson(builder.toString(), Collection.class);
Null values are excluded by default as long as you don't set serializeNulls() to your GsonBuilder. Once I found this. and worked for me:
class CollectionAdapter implements JsonSerializer<Collection<?>> {
#Override
public JsonElement serialize(Collection<?> src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null || src.isEmpty()) // exclusion is made here
return null;
JsonArray array = new JsonArray();
for (Object child : src) {
JsonElement element = context.serialize(child);
array.add(element);
}
return array;
}
}
Then register this by this code:
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Collection.class, new CollectionAdapter()).create();
Hope this helps.. :)

Categories

Resources