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.. :)
Related
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);
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;
}
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
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.
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.