How to parse JSONArray with variable key (GSON)? - android

My json is :
{"array":[{"US":"id_123"},{"UK":"id_112"},{"EN":"id_1112"}...]}
My classes are:
class LocaleResponce implements Serializable{
#SerializedName("array")
List<Locale> array;
}
class Locale implements Serializable{
#SerializedName("title")
String title;
#SerializedName("id")
String id;
}
I'm tried to make this:
Gson gson = new Gson();
Type type = new TypeToken<LocaleResponce >(){}.getType();
LocaleResponce response = gson.fromJson(cacheJsonObject.toString(), type);
it doesn't work or is it an issue of server?

It can be achieved by creating custom JsonDeserializer.
Your deserializer class will look something like
public class CityListDeserializer implements JsonDeserializer<List<City>>{
#Override
public List<City> deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<City> cityList = new ArrayList<>();
JsonObject parentJsonObject = element.getAsJsonObject();
Map.Entry<String, JsonElement> entry = parentJsonObject.entrySet().iterator().next();
Iterator<JsonElement> iterator = entry.getValue().getAsJsonArray().iterator();
City city;
while (iterator.hasNext()){
JsonObject cityJsonObject = iterator.next().getAsJsonObject();
for(Map.Entry<String, JsonElement> entry1 : cityJsonObject.entrySet()){
city = new City();
city.cityName = entry1.getKey();
city.id = entry1.getValue().toString();
cityList.add(city);
}
}
return cityList;
}
}
You can use it with
try {
JSONObject object = new JSONObject("{\"array\":[{\"US\":\"id_123\"},{\"UK\":\"id_112\"},{\"EN\":\"id_1112\"}]}");
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<ArrayList<City>>() {}.getType(), new CityListDeserializer());
Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
List<City> cityList = gson.fromJson(String.valueOf(object), new TypeToken<ArrayList<City>>() {}.getType());
} catch (JSONException e) {
e.printStackTrace();
}
Your City class will be
public class City {
String cityName;
String id;
}

You can generate classes for Json using websites like http://www.jsonschema2pojo.org/ etc in your case the array array variable, does not have an array of homogeneous objects {"US":"id_123"},{"UK":"id_112"},{"EN":"id_1112"} these are all objects of different DataTypes because, the parameter keys are different, so for parsing this you cannot use a Pojo. The parameters vary from UK US EN etc, the solution here is either to ask the person who is developing the api to send Json that is Consistent, the array that you have received is not type safe, if you want to use this in Java you have to write lots of lines of code. For example you can get the value of the parameter "UK" like this
cacheJsonObject.get("array").getAsJsonArray().get(1).get("UK").getAsString();
This would return the value id_112 for instance.

Related

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

Gson - Same field name, different types

I asked this in a different question today but I'm afraid that won't get any solution because of how it was phrased.
I have a json input that has the following data:
As you can see, the option_value item is an Array in one object and a simple string in another object.
How can I make Gson handle this properly? My class has this described as a List object, so it works for the first few items where option_value is an array, but when it becomes a string, the app crashes and I get a json parse exception.
Is there a workaround for this?
UPDATE
Adding the relevant part of my class as requested:
public class Options
{
String product_option_id;
String option_id;
String name;
String type;
String required;
List<OptionValue> option_value;
// get set stuff here
public class OptionValue
{
String product_option_value_id;
String option_value_id;
String name;
String image;
String price;
String price_prefix;
// get set stuff here
}
}
I have a solution for you :) For this purpose, we should use a custom deserializer. Remake your class like this:
public class Options{
#SerializedName ("product_option_id");
String mProductOptionId;
#SerializedName ("option_id");
String mOptionId;
#SerializedName ("name");
String mName;
#SerializedName ("type");
String mType;
#SerializedName ("required");
String mRequired;
//don't assign any serialized name, this field will be parsed manually
List<OptionValue> mOptionValue;
//setter
public void setOptionValues(List<OptionValue> optionValues){
mOptionValue = optionValues;
}
// get set stuff here
public class OptionValue
{
String product_option_value_id;
String option_value_id;
String name;
String image;
String price;
String price_prefix;
// get set stuff here
}
public static class OptionsDeserializer implements JsonDeserializer<Options> {
#Override
public Offer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Options options = new Gson().fromJson(json, Options.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("option_value")) {
JsonElement elem = jsonObject.get("option_value");
if (elem != null && !elem.isJsonNull()) {
String valuesString = elem.getAsString();
if (!TextUtils.isEmpty(valuesString)){
List<OptionValue> values = new Gson().fromJson(valuesString, new TypeToken<ArrayList<OptionValue>>() {}.getType());
options.setOptionValues(values);
}
}
}
return options ;
}
}
}
Before we can let Gson parse json, we should register our custom deserializer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Options.class, new Options.OptionsDeserilizer())
.create();
And now - just call:
Options options = gson.fromJson(json, Options.class);
In my situation, the field with same name is "data":{} or "data":[array_with_real_data]. So the code from accepted answer need to be modified slightly, like this:
#Override
public MyClass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
MyClass bean = new Gson().fromJson(json, MyClass.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("data")) {
JsonArray array = jsonObject.getAsJsonArray("data");
if (array != null && !array.isJsonNull()) {
List<Data> data = new Gson().fromJson(array, new TypeToken<ArrayList<Data>>() {}.getType());
bean.realData = data;
}
}
return bean ;
}
hope that helps.

Deserializing an object that contains a single list with gson

I have the following json I want to deserialize:
{
"locations": [{
"id": 17,
"account_id": 11,
"name": "The Haunted Lexington",
"radius": 100
}]
}
(In this particular instance, there's only one Location, but there can be many).
I deserialize this using Gson with the following code:
Gson gson = new GsonBuilder().create();
LocationList ll = gson.fromJson(jsonString, LocationList.class);
I have the following classes defined:
public class Location {
#SerializedName("id")
private long mId;
#SerializedName("account_id")
private long mAccountId;
#SerializedName("name")
private String mName;
#SerializedName("radius")
private int mRadius;
public long getId() {
return mId;
}
public String getName() {
return mName;
}
}
and:
public class LocationList {
#SerializedName("locations")
private List<Location> mLocations;
}
The thing is, I have a bunch of these "dummy" classes that contain a single object that's a list of other objects (e.g. UserList, MessageList, etc...)
What I'd like to do is have the above json parsed somehow so I can skip defining the intermediate class definition of LocationList, like so:
Gson gson = new GsonBuilder().create();
// Use the same json as above, but skip defining the superfluous "LocationList" class
List<Location> ll = gson.fromJson(jsonString, "locations", ArrayList<Location>.class);
Is there a way I can do this, perhaps by providing a custom deserializer?
I faced a similar problem not long ago, and solved it like this
// Parse the JSON response.
JSONArray jsonArray = new JSONArray(response);
List<Location> locations = new ArrayList<Location>();
/*
* Create a Location object for every JSONObject in the response,
* and add it to the list.
*/
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Location location = new Gson().fromJson(jsonObject.toString(),
Location.class);
locations.add(location);
The approach here is to loop over every Location in the locations array in the JSON, extracting them one by one and then adding them to a list.
The JSON I worked with had a list as root object, so you probably can't use this JSONArray jsonArray = new JSONArray(response);. Something like this might better suit your situation
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray locationsJsonArray = jsonObject.get("locations");
I have not tested these last 2 lines, but I think you get the idea.
I hope you can use this to solve your problem.
I'm currently using a simple method to achive your goal:
private static <T> List<T> getList(final String jsonVal, final String listTag, T t) {
try {
JsonObject jsonObject = (JsonObject)(new JsonParser()).parse(jsonVal); // root JsonObject. i.e. "locations"
JsonArray jsonArray = (JsonArray)jsonObject.get(listTag);
Gson gson = new GsonBuilder().create();
List<T> list = gson.fromJson(jsonArray, new TypeToken<List<T>>() {}.getType());
return list;
} catch (Exception e) {
throw new IllegalArgumentException("Unexpected json structure!", e);
}
}
Example usage:
final GsonBuilder gsonBuilder = new GsonBuilder();
final Gson gson = gsonBuilder.create();
String jsonString = "{\"locations\":[{\"id\":17,\"account_id\":11,\"name\":\"The Haunted Lexington\",\"radius\":100}]}";
List<Location> list = getList(jsonString, "locations", new Location());
This method should be used for your orther classes, such as:
List<User> userList = getList(jsonString, "users", new User());
List<Message> messageList = getList(jsonString, "messages", new Message());

Deserializing multi-leveled JSON

I am completely new to the concept of JSON, and am having trouble figuring out how to deserialize a multi-leveled JSON statement using Gson.
This is what I'm trying to deserialize: {"stat":"ok","pkey":{"id":"1234567890"}}
First I tried doing using a hashmap:
HashMap<String, Object> results = gson.fromJson(response, HashMap.class);
The result looked reasonable enough, but the second entry in the hashmap (the one that contained the actual id number) was a gson.internal.LinkedTreeMap, which I couldn't access.
Next I tried making a custom class to deserialize it into, but I can't seem to get it to work right...
Neither of these worked:
class Results
{
String stat;
String[][] pkey;
}
class Results
{
String stat;
String[] pkey;
}
The only examples I can find online have to do with deserializing simple, one level JSON, which looks easy. I just can't seem to figure this out.
Use JSONObject can easy deserializing multi-leveled JSON
This is Log "ok" and "1234567890" from your example
String json = "{\"stat\":\"ok\",\"pkey\":{\"id\":\"1234567890\"}}";
try {
JSONObject jsonObj = new JSONObject(json);
Log.i("chauster", jsonObj.getString("stat"));
Log.i("chauster", jsonObj.getJSONObject("pkey").getString("id"));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
This is code for gson, you need to custom your class
public class Custom {
private String stat;
private IdClass pkey;
public String getStat() {
return stat;
}
public IdClass getPkey() {
return pkey;
}
public Custom(String _stat, IdClass _pkey) {
stat = _stat;
pkey = _pkey;
}
public class IdClass {
private String id;
public String getId() {
return id;
}
public IdClass(String _id){
id = _id;
}
}
}
String json = "{\"stat\":\"ok\",\"pkey\":{\"id\":\"1234567890\"}}";
Gson gson = new Gson();
Custom custom = gson.fromJson(json, Custom.class);
System.out.println(custom.getStat());
System.out.println(custom.getPkey().getId());

Categories

Resources