Android: deserialize dynamic JSON with Retrofit and Jackson - java

I need to deserialize a dynamic JSON with unknown name of properties and I can't get it done.
The JSON looks like this:
{
Player: [
{
name: "name",
surname: "surname",
email: "email",
photo: "photo",
position: "position"
}
],
...
}
So basically, this would be a JSON object containing multiple arrays.
The name of the name of the JSON array -Player- is dynamic, and I have just included the first array, but in the JSON object there can be multiple arrays.
Otherwise, if the wasn't dynamic, then I would include it in the declaration of the fields of the model with #JsonProperty.
Thanks a lot in advance.

What I did here was creating my own deserializer and using it when creating the Gson:
Gson gson = new GsonBuilder().registerTypeAdapter(MyModel.class,
new MyJsonDeserializer())
.create();
And here's the deserializer:
public class MyJsonDeserializer implements JsonDeserializer<MyModel> {
#Override
public MyModel deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new GsonBuilder().create();
MyModel myModel = new MyModel();
Iterator it = ((JsonObject) json).entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
if (entry.getValue() instanceof JsonArray) {
JsonArray myModelJsonArray = (JsonArray) entry.getValue();
MyModel2 mymodel2 = new MyModel2();
if (entry.getKey() instanceof String) {
mymodel2.setName((String) entry.getKey());
}
for (int i = 0; i < myModelJsonArray.size(); i++) {
JsonElement jsonElementMember = myModelJsonArray.get(i);
mymodel2.getMembers().add(gson.fromJson(jsonElementMember, Member.class));
}
myModel.getMyModel2().add(myModel2);
}
}
return myModel;
}
}
It is basically creating the structure of the JSON manually, with all its hierarchies.
Thanks a lot for your help!

Related

How to lowercase the JsonElement value in the custom deserializer Gson?

I have a custom deserializer for my class as shown below:
private class HolderDeserializer implements JsonDeserializer<Holder> {
#Override
public Holder deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
// in the below data map, I want value to be stored in lowercase
// how can I do that?
Map<String, String> data = context.deserialize(json, mapType);
return new Holder(data);
}
}
And this is how I register my deserializer when creating the Gson object:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Holder.class, new HolderDeserializer());
Gson gson = gsonBuilder.create();
And finally, parsing my JSON like this:
Type responseType = new TypeToken<Map<String, Holder>>() {}.getType();
Map<String, Holder> response = gson.fromJson(jsonLine, responseType);
In my deserialize method, value of json is coming as like this {"linkedTo":"COUNT"} and then it get loaded into data map as {linkedTo=COUNT}. I wanted to see if there is any way by which all the value of data map can be lowercase so instead of this {linkedTo=COUNT}, it should get stored like this {linkedTo=count} in data map automatically?
Is there any way to do this in Gson itself automatically?
Update:
Below is my JSON content:
{
"abc": {
"linkedTo": "COUNT",
// possibly more data...
},
"plmtq": {
"linkedTo": "TITLE",
"decode": "TRUE",
// possibly more data...
}
}
Firstly, it is suggested to use Gson TypeAdapter instead of JsonDeserializer. So I'm going to answer your question with it:
New applications should prefer TypeAdapter, whose streaming API is
more efficient than this interface's tree API.
More information.
Question: How can we modify the json content before deserialization ?
One of the solutions: Preprocess the json content before deserialization and modify some of its contents.
How can we achive this with TypeAdapter: Define a custom TypeAdapter, get the json content at its read method (which is called just before the deserialization) and modify the content.
Code sample:
Define a TypeAdapterFactory and a TypeAdapter;
TypeAdapterFactory myCustomTypeAdapterFactory = new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); //
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
public T read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
/**
* Modify {#code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(T source, JsonElement toSerialize) {
}
/**
* Modify {#code deserialized} before it is parsed
*/
protected void afterRead(JsonElement deserialized) {
if(deserialized instanceof JsonObject) {
JsonObject jsonObject = ((JsonObject)deserialized);
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
for(Map.Entry<String,JsonElement> entry : entrySet){
if(entry.getValue() instanceof JsonPrimitive) {
if(entry.getKey().equalsIgnoreCase("linkedTo")) {
String val = jsonObject.get(entry.getKey()).toString();
jsonObject.addProperty(entry.getKey(), val.toLowerCase());
}
} else {
afterRead(entry.getValue());
}
}
}
}
};
}
};
We've added an extra process before deserialization. We get the entrySet from json content and updated linkedTo key's value.
Working sample:
String jsonContent = "{\"abc\":{\"linkedTo\":\"COUNT\"},\"plmtq\":{\"linkedTo\":\"TITLE\",\"decode\":\"TRUE\"}}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(myCustomTypeAdapterFactory);
Gson gson = gsonBuilder.create();
Map mapDeserialized = gson.fromJson(jsonContent, Map.class);
Output:
This is the similar answer for your question.

Deserialize nested object with GSON

I'm trying to deserialize the following structure
{ meta: { keywords: [a, b, c, d]} ... }
other valid structures are
{ meta: { keywords: "a,b,c,d"} ... }
and
{ meta: {keywords: "a"} ...}
I have this classes
public class Data {
#PropertyName("meta")
MetaData meta;
...
}
public class MetaData {
List<String> keywords;
...
}
and a custom deserializer
public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
#Override
public MetaData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<String> keywords = null;
Gson gson = new Gson();
MetaData metaData = gson.fromJson(json, AppMetaData.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("keywords")) {
JsonElement elem = jsonObject.get("keywords");
if (elem != null && !elem.isJsonNull()) {
if (jsonObject.get("keywords").isJsonArray()) {
keywords = gson.fromJson(jsonObject.get("keywords"), new TypeToken<List<String>>() {
}.getType());
} else {
String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
keywords = new ArrayList<String>();
list.addAll(Arrays.asList(keywordString.split(",")));
}
}
}
metaData.setKeywords(keywords);
}
Then I try to apply the deserilizer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new CustomDeserilizer())
.create();
But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?
I solved it creating a deserializer for my class Data.
public static class DataDeserilizer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("meta")) {
JsonElement elem = jsonObject.get("meta");
if (elem != null && !elem.isJsonNull()) {
Gson gsonDeserializer = new GsonBuilder()
.registerTypeAdapter(MetaData.class, new CustomDeserilizer())
.create();
gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
}
}
return data;
}
}
And
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new DataDeserilizer())
.create();
Pretty obvious, but is there a more elegant solution?
Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.
Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);
In order to get keywords only, you need this.
JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");
keywords is a JsonObject not an JsonArray so you can't directly map it
onto List. You can split the string to get keywords in an array.
String keywords[] = data.split(",");
Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.
Step 1: For readability, create an empty object to represent the nested mapping
public class StateRegionCitiesMap extends HashMap<String, List<String>> {
}
Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage
protected void loadContent(JsonObject stateRegionsJsonObject) {
HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
mGson.fromJson(
stateRegionsJsonObject,
new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
}.getType()
);
}
Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.
Example JSON:
The class StateRegionCitiesMap represents a multi-tier map structure for say:
[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]
"CA": {
"Central CA": {
"Central Valley": [
"FRESNO",
"VISALIA"
],
"Sacramento Area": [
"SACRAMENTO",
"EL DORADO HILLS"
]
},
This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e.
in your inner class have private Keywords keywords;
In your Main class:
Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);
In a class called Data:
public class Data {
private Meta meta;
static class Meta{
private String[] keywords;
}
}

De/Serialize Gson an array of arrays [duplicate]

I'm trying to parse some JSON data using gson in Java that has the following structure but by looking at examples online, I cannot find anything that does the job.
Would anyone be able to assist?
{
"data":{
"id":[
{
"stuff":{
},
"values":[
[
123,
456
],
[
123,
456
],
[
123,
456
],
],
"otherStuff":"blah"
}
]
}
}
You just need to create a Java class structure that represents the data in your JSON. In order to do that, I suggest you to copy your JSON into this online JSON Viewer and you'll see the structure of your JSON much clearer...
Basically you need these classes (pseudo-code):
class Response
Data data
class Data
List<ID> id
class ID
Stuff stuff
List<List<Integer>> values
String otherStuff
Note that attribute names in your classes must match the names of your JSON fields! You may add more attributes and classes according to your actual JSON structure... Also note that you need getters and setters for all your attributes!
Finally, you just need to parse the JSON into your Java class structure with:
Gson gson = new Gson();
Response response = gson.fromJson(yourJsonString, Response.class);
And that's it! Now you can access all your data within the response object using the getters and setters...
For example, in order to access the first value 456, you'll need to do:
int value = response.getData().getId().get(0).getValues().get(0).get(1);
Depending on what you are trying to do. You could just setup a POJO heirarchy that matches your json as seen here (Preferred method). Or, you could provide a custom deserializer. I only dealt with the id data as I assumed it was the tricky implementation in question. Just step through the json using the gson types, and build up the data you are trying to represent. The Data and Id classes are just pojos composed of and reflecting the properties in the original json string.
public class MyDeserializer implements JsonDeserializer<Data>
{
#Override
public Data deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
final Gson gson = new Gson();
final JsonObject obj = je.getAsJsonObject(); //our original full json string
final JsonElement dataElement = obj.get("data");
final JsonElement idElement = dataElement.getAsJsonObject().get("id");
final JsonArray idArray = idElement.getAsJsonArray();
final List<Id> parsedData = new ArrayList<>();
for (Object object : idArray)
{
final JsonObject jsonObject = (JsonObject) object;
//can pass this into constructor of Id or through a setter
final JsonObject stuff = jsonObject.get("stuff").getAsJsonObject();
final JsonArray valuesArray = jsonObject.getAsJsonArray("values");
final Id id = new Id();
for (Object value : valuesArray)
{
final JsonArray nestedArray = (JsonArray)value;
final Integer[] nest = gson.fromJson(nestedArray, Integer[].class);
id.addNestedValues(nest);
}
parsedData.add(id);
}
return new Data(parsedData);
}
}
Test:
#Test
public void testMethod1()
{
final String values = "[[123, 456], [987, 654]]";
final String id = "[ {stuff: { }, values: " + values + ", otherstuff: 'stuff2' }]";
final String jsonString = "{data: {id:" + id + "}}";
System.out.println(jsonString);
final Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, new MyDeserializer()).create();
System.out.println(gson.fromJson(jsonString, Data.class));
}
Result:
Data{ids=[Id {nestedList=[[123, 456], [987, 654]]}]}
POJO:
public class Data
{
private List<Id> ids;
public Data(List<Id> ids)
{
this.ids = ids;
}
#Override
public String toString()
{
return "Data{" + "ids=" + ids + '}';
}
}
public class Id
{
private List<Integer[]> nestedList;
public Id()
{
nestedList = new ArrayList<>();
}
public void addNestedValues(final Integer[] values)
{
nestedList.add(values);
}
#Override
public String toString()
{
final List<String> formattedOutput = new ArrayList();
for (Integer[] integers : nestedList)
{
formattedOutput.add(Arrays.asList(integers).toString());
}
return "Id {" + "nestedList=" + formattedOutput + '}';
}
}

Exclude null objects from JSON array with Gson

I'm using Gson to parse my REST API calls to Java objects.
I want to filter out null objects in an array, e.g.
{
list: [
{"key":"value1"},
null,
{"key":"value2"}
]
}
should result in a List<SomeObject> with 2 items.
How can you do this with Gson?
Answer: The Custom Serializer
You can add a custom serializer for List.class which would look like:
package com.dominikangerer.q27637811;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class RemoveNullListSerializer<T> implements JsonSerializer<List<T>> {
#Override
public JsonElement serialize(List<T> src, Type typeOfSrc,
JsonSerializationContext context) {
// remove all null values
src.removeAll(Collections.singleton(null));
// create json Result
JsonArray result = new JsonArray();
for(T item : src){
result.add(context.serialize(item));
}
return result;
}
}
This will remove the null values from the list using Collections.singleton(null) and removeAll().
Register your Custom Serializer
Now all you have to do is register it to your Gson instance like:
g = new GsonBuilder().registerTypeAdapter(List.class, new RemoveNullListSerializer()).create();
Downloadable & executable Example
You can find this answer and the exact example in my github stackoverflow answers repo:
Gson CustomSerializer to remove Null from List by DominikAngerer
See also
Gson User Guide - Custom serializers and deserializers
To remove all the null values from a list regardless of their structure, first we need to register a de-serializer with the gson like this
Gson gson = new GsonBuilder().registerTypeAdapter(List.class, new RemoveNullListDeserializer()).create();
Then the custom de-serializer would remove the null like this
/**
* <p>
* Deserializer that helps remove all <code>null</code> values form the <code>JsonArray</code> .
*/
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>>
{
/**
* {#inheritDoc}
*/
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonArray jsonArray = new JsonArray();
for(final JsonElement jsonElement : json.getAsJsonArray())
{
if(jsonElement.isJsonNull())
{
continue;
}
jsonArray.add(jsonElement);
}
Gson gson = new GsonBuilder().create();
List<?> list = gson.fromJson(jsonArray, typeOfT);
return (List<T>) list;
}
}
Hope this help others, who want to remove null values from a json array, regardless of the incoming json structure
My answer maybe late but I expect it works. This question can be solved by removing all the null elements in the Java object when deserialize the json string. So first, we define the custom JsonDeserializer for type List
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
//TODO
}
}
And then, remove all null elements in the JsonElement. Here we use recursive to handle these potential null elements.
private void removeNullEleInArray(JsonElement json) {
if (json.isJsonArray()) {
JsonArray jsonArray = json.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); i++) {
JsonElement ele = jsonArray.get(i);
if (ele.isJsonNull()) {
jsonArray.remove(i);
i--;
continue;
}
removeNullEleInArray(ele);
}
} else if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
for (String key : jsonObject.keySet()) {
JsonElement jsonElement = jsonObject.get(key);
if (jsonElement.isJsonArray() || jsonElement.isJsonObject()) {
removeNullEleInArray(jsonElement);
}
}
}
}
It's worth noting that only remove the null elements in top class is not enough.
And next step, transfer this method when deserialize.
public class RemoveNullListDeserializer<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
removeNullEleInArray(json)
return Gson().fromJson(json, typeOfT)
}
}
Finally, register the adapter for creating your Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(List.class, new RemoveNullListDeserializer())
.create();
Just Over!

Java with GSON - deserialize only the values into an ArrayList of a JSON string

I have this structure of my JSON response string:
{
"1":{
"data1":"1","data2":"test1", ...
},
"2":{
"data1":"6","data2":"test2", ...
},
...
}
And I want to get the values to put into an ArrayList<MyItem>. I use GSON and normally I can do it in this way:
ArrayList<MyItem> items =
gson.fromJson(jsonString, new TypeToken<ArrayList<MyItem>>() {}.getType());
The problem is, that it does not work, because my JSON String has numbers as keys, but I only want to get the values to put into the ArrayList (unfortunately, the JSON string can not be changed by myself). How can I do this efficiently?
I'd probably deserialize the JSON into a java.util.Map, get the values from the Map as a Collection using the Map.values() method, and then create a new ArrayList using the constructor that takes a Collection.
Write a custom deserializer.
class MyItem
{
String data1;
String data2;
// ...
}
class MyJSONList extends ArrayList<MyItem> {}
class MyDeserializer implements JsonDeserializer<MyJSONList>
{
public MyJSONList deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
MyJSONList list = new MyJSONList();
for (Entry<String, JsonElement> e : je.getAsJsonObject().entrySet())
{
list.add((MyItem)jdc.deserialize(e.getValue(), MyItem.class));
}
return list;
}
}
Example:
String json = "{\"1\":{\"data1\":\"1\",\"data2\":\"test1\"},\"2\":{\"data1\":\"6\",\"data2\":\"test2\"}}";
Gson g = new GsonBuilder()
.registerTypeAdapter(MyJSONList.class, new MyDeserializer())
.create();
MyJSONList l = g.fromJson(json, MyJSONList.class);
for (MyItem i : l)
{
System.out.println(i.data2);
}
Output:
test1test2

Categories