Suppose we have this simple json:
"Book":
{
"title": "Hello world",
"metadata":
{
"author": "Jeff"
}
}
Is there an easy way to ignore the metadata object and put author directly into a books object using gson? Should i use a deserializer for that or is there an easier way?
I need a class like this:
public class Book
{
private String title;
private String author;
//...
}
Use Deserializer. Something like this :
public class BookDeserializer implements JsonDeserializer<Book> {
#Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final String title = jsonObject.get("title").getAsString();
JsonElement metaElement = jsonObject.get("metadata");
final String author = metaElement.get("author").getAsString();
final Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
return book;
}
}
How to use it:
sGson = new GsonBuilder()
.registerTypeAdapter(Book.class, new BookDeserializer())
.create();
Note : I prefer to use Constant instead of hardcoded strings but it's just for the example here ;)
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;
}
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.
I have a Json data to be pulled from a server. This data contains several objects and arrays.
The first model is as follows:
{
"results": [
{
"id": "17",
"name": "Accessories",
"child": [
{
"id": "371",
"name": "Belt"
},
{
"id": "55",
"name": "Derp"
},
...
]
}
]
}
However, some of the results array doesn't have child array. Instead, it have a String with an empty value.
{
"results": [
{
"id": "19",
"name": "Stuff",
"child": ""
}
]
}
When the code is executed, it returns this line:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING
This is how the Model looks like:
public class CategoryModel {
#SerializedName("id")
private String category_id;
private String name;
private ArrayList<CategoryChildModel> child;
...
}
And this is how I implement the GsonRequest (which using Volley as background asynctask):
private void loadCategory() {
mRequestQueue = Volley.newRequestQueue(getActivity());
String url = Constants.CATEGORIES_LIST;
GsonRequest<CategoryContainer> myReq = new GsonRequest<CategoryContainer>(
Request.Method.GET, url, CategoryContainer.class,
createMyReqSuccessListener(), createMyReqErrorListener());
mRequestQueue.add(myReq);
}
So, anyone knows how to make null object pass through GsonRequest?
Actually your json response should return an empty array not a string for null cases. But if you don't have an option to change server's response then you may try to write a custom json deserializer:
class ChildDeserializer implements JsonDeserializer<ChildHolder> {
#Override
public ChildHolder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String currentValueOfChild = json.toString();
Log.d("ChildDeserializer", "ChildDeserializer: child=" + currentValueOfChild);
ChildHolder childHolder = null;
if (json instanceof JsonArray) {
Log.d("ChildDeserializer", "ChildDeserializer: We have an array for 'child'");
Type listType = new TypeToken<List<Child>>() {}.getType();
JsonArray jsonArray= json.getAsJsonArray();
childHolder = new ChildHolder();
childHolder.childList = context.deserialize(jsonArray, listType);
}
return childHolder;
}
}
Your response java model should look like below:
class Response {
List<Result> results;
}
class Result {
private String id, name;
private ChildHolder child;
}
class ChildHolder {
private List<Child> childList;
}
class Child {
private String id, name;
}
Apply deserializer while parsing json to java model:
String jsonTest1 = "{\"results\":[{\"id\":\"17\",\"name\":\"Accessories\",\"child\":[{\"id\":\"371\",\"name\":\"Belt\"},{\"id\":\"55\",\"name\":\"Derp\"}]}]}";
String jsonTest2 = "{\"results\":[{\"id\":\"19\",\"name\":\"Stuff\",\"child\":\"\"}]}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ChildHolder.class, new ChildDeserializer());
Gson gson = gsonBuilder.create();
Response response1 = gson.fromJson(jsonTest1, Response.class);
Response response2 = gson.fromJson(jsonTest2, Response.class);
Also please read this link for further information.
Another option for writing a custom serializer is to simply convert the empty string to an empty array in the JSON. Then your class can remain the way it is:
class ChildDeserializer implements JsonDeserializer<CategoryModel>
{
#Override
public ChildHolder deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = json.getAsObject();
JsonElement e = obj.get("child");
if (e.isJsonPrimitive()) // it's a String
{
obj.remove("child");
obj.add("child", new JsonArray());
}
return new Gson().fromJson(obj, CategoryModel.class);
}
}