Dealing with Randomly Generated and Inconsistent JSON Field/Key Names Using Gson

Dealing with randomly generated and inconsistent JSON field/key names using GSON

Code dump solution:

import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;

public class Foo
{
public static void main(String[] args) throws Exception
{
Type mapStringObjectType = new TypeToken<Map<String, Object>>() {}.getType();

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(mapStringObjectType, new RandomMapKeysAdapter());
Gson gson = gsonBuilder.create();

Map<String, Object> map = gson.fromJson(new FileReader("input.json"), mapStringObjectType);
System.out.println(map);
}
}

class RandomMapKeysAdapter implements JsonDeserializer<Map<String, Object>>
{
@Override
public Map<String, Object> deserialize(JsonElement json, Type unused, JsonDeserializationContext context)
throws JsonParseException
{
// if not handling primitives, nulls and arrays, then just
if (!json.isJsonObject()) throw new JsonParseException("some meaningful message");

Map<String, Object> result = new HashMap<String, Object> ();
JsonObject jsonObject = json.getAsJsonObject();
for (Entry<String, JsonElement> entry : jsonObject.entrySet())
{
String key = entry.getKey();
JsonElement element = entry.getValue();
if (element.isJsonPrimitive())
{
result.put(key, element.getAsString());
}
else if (element.isJsonObject())
{
result.put(key, context.deserialize(element, unused));
}
// if not handling nulls and arrays
else
{
throw new JsonParseException("some meaningful message");
}
}
return result;
}
}

GSON: Deserializing Json with random class names

You can convert a JSON string to an equivalent Java object using custom Gson JsonDeserializer

Assuming you have mapping classes

public class Data {
private int this_number;
private int that_number;
private String some_string;
private List<DataInfo> objects;
}

public class DataInfo {
private double random_double;
private int magic;
private int health;
private int price;
}

public class Point {
int x ;
int y;
}

CustomDeserializer

public class CustomDeserializer implements JsonDeserializer<Data> {

@Override
public Data deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();

final int this_number = jsonObject.get("this_number").getAsInt();
final int that_number = jsonObject.get("that_number").getAsInt();
final String some_string = jsonObject.get("some_string").getAsString();

JsonObject list_of_objects =jsonObject.get("list_of_objects").getAsJsonObject();

Set<Entry<String, JsonElement>> objects = list_of_objects.entrySet();

final Data data = new Data();
List<DataInfo> list = new ArrayList<>();

Gson gson = new Gson();

for (Entry<String, JsonElement> entry : objects) {
JsonElement jsonElement = entry.getValue();
DataInfo info = gson.fromJson(jsonElement,DataInfo.class);
list.add(info);
}

data.setObjects(list);
data.setSome_string(some_string);
data.setThat_number(that_number);
data.setThis_number(this_number);

return data;
}
}

GSON 2.0+ - Deserialize and parse arbitrary fields

You can use your custom object that contains the mapped fields in the JSON, and have them converted to a List for you:

List<MyObject> items =
gson.fromJson(json, new TypeToken<List<MyObject>>() { }.getType());

JSON has unique keys for all objects, how to convert to POJO using GSON?

As the names are not the same, you can't use an neither an annotation or have a field with the same name in your Java class.

What I would suggest is to implement a custom deserializer so that you shouldn't have to care about these key-values.

Assuming proper constructors here's how you could implement a deserializer for the Objects class (by the way it's a very bad name as there is already a class named Objects in the JDK).

class ObjectsDeserializer implements JsonDeserializer<Objects> {
@Override
public Objects deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject().getAsJsonObject("Objects");
List<NestedObject1> list = new ArrayList<>();
for(Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
List<NestedObject2> nestedObject2List = new ArrayList<>();
//here deserialize each NestedObject2 and put it in the list
list.add(new NestedObject1(entry.getKey(), nestedObject2List));
}
return new Objects(list);
}
}

Then you register your deserializers in the Gson parser:

Gson gson = new GsonBuilder()
.registerTypeAdapter(Objects.class, new ObjectsDeserializer())
.registerTypeAdapter(NestedObject2.class, new NestedObject2Deserializer())
.create();

Objects o = gson.fromJson(new FileReader(new File("file.json")), Objects.class);

which outputs:

Objects{mNestedObjects1=
NestedObject1{mId='-JrEvneZHQV73jxKu0-U', mNestedObjects2=[]}
NestedObject1{mId='-JrF0_yWy_NmrMROB3qp', mNestedObjects2=[]}
NestedObject1{mId='-JrF3XSkpvo2WMzF1osb', mNestedObjects2=[]}
}

You'd need to define a custom deserializer for the NestedObject2 class, here I just fill it with an empty list, but I guess you will be able to do it as it's the same principle.

Hope it helps! :-)

GSON Deserialization

It seems like there is another level. You didn't take in account that there is the key "7148bc5e5065d61bd3a4b00318824db0" which maps your IncMessages object.

So something like this work:

class ReceiveMessageResponceModel <T>  {
private Boolean success;
private String message;
private Data<T> data;

public T getValue() {
return data.t;
}

public void setValue(Data<T> data) {
this.data = data;
}

public Boolean getSuccess() {
return success;
}

public String getMessage() {
return message;
}
}

class Data<T> {
@SerializedName("7148bc5e5065d61bd3a4b00318824db0")
T t;
}

class IncMessages {

String names;
List<Messages> messages;

public String getName() {
return names;
}

public List<Messages> getMessages() {
return messages;
}
}

Also I suppose that the key "7148bc5e5065d61bd3a4b00318824db0" is not constant. If you want to skip the key (or at least ignoring its real value), it can certainly be done with a custom deserializer. See also Dealing with randomly generated and inconsistent JSON field/key names using GSON

How to decode JSON with unknown field using Gson?

(After OP commented that in fact the JSON looks like this, I completely updated the answer.)

Solution for Gson 2.0+

I just learned that with newer Gson versions this is extremely simple:

GsonBuilder builder = new GsonBuilder();
Object o = builder.create().fromJson(json, Object.class);

The created object is a Map (com.google.gson.internal.LinkedTreeMap), and if you print it, it looks like this:

{1145={cities_id=1145, city=Nawanshahr, city_path=nawanshahr, region_id=53, region_district_id=381, country_id=0, million=0, population=null, region_name=Punjab}, 
1148={cities_id=1148, city=Nimbahera, city_path=nimbahera, region_id=54, region_district_id=528, country_id=0, million=0, population=null, region_name=Rajasthan}
...

Solution using a custom deserialiser

(NB: Turns out you don't really a custom deserialiser unless you're stuck with pre-2.0 versions of Gson. But still it is useful to know how to do custom deserialisation (and serialisation) in Gson, and it may often be the best approach, depending on how you want to use the parsed data.)

So we're indeed dealing with random / varying field names. (Of course, this JSON format is not very good; this kind of data should be inside a JSON array, in which case it could be very easily read into a List. Oh well, we can still parse this.)

First, this is how I would model the JSON data in Java objects:

// info for individual city
public class City {
String citiesId;
String city;
String regionName;
// and so on
}

// top-level object, containing info for lots of cities
public class CityList {
List<City> cities;

public CityList(List<City> cities) {
this.cities = cities;
}
}

Then, the parsing. One way to deal with this kind of JSON is to create a custom deserialiser for the top level object (CityList).

Something like this:

public class CityListDeserializer implements JsonDeserializer<CityList> {

@Override
public CityList deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = element.getAsJsonObject();
List<City> cities = new ArrayList<City>();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
// For individual City objects, we can use default deserialisation:
City city = context.deserialize(entry.getValue(), City.class);
cities.add(city);
}
return new CityList(cities);
}

}

A key point to notice is the call to jsonObject.entrySet() which retuns all the top-level fields (with names like "1145", "1148", etc). This Stack Overflow answer helped me solve this.

Complete parsing code below. Note that you need to use registerTypeAdapter() to register the custom serialiser.

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(CityList.class, new CityListDeserializer());
Gson gson = builder.setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
CityList list = gson.fromJson(json, CityList.class);

(Here's a full, executable example that I used for testing. Besides Gson, it uses Guava library.)

GSON parsing dynamic JSON field

I tried it in this form:

The Json

{
"id": 6,
"error": "0",
"dates": {
"34234" : "2011-01-01"
"87474" : "2011-08-09"
"74857" : "2011-09-22"
}
}

And the Response.java

public class Response {
public Integer id;
public String error;
public Map<Integer, String> dates;
}

At least that seemed to work out of the box.

Parsing object with dynamic key through gson

Maybe you can try this:
Episode class

    public class Episode {

@SerializedName("season")
@Expose
private Integer season;
@SerializedName("name")
@Expose
private String name;
@SerializedName("number")
@Expose
private Integer number;

public Integer getSeason() {
return season;
}

public void setSeason(Integer season) {
this.season = season;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getNumber() {
return number;
}

public void setNumber(Integer number) {
this.number = number;
}

}

Then Serial class:

    public class Serial {

@SerializedName("episodes")
@Expose
private List<Episode> episodes = new ArrayList<Episode>();
@SerializedName("year")
@Expose
private Integer year;

public List<Episode> getEpisodes() {
return episodes;
}

public void setEpisodes(List<Episode> episodes) {
this.episodes = episodes;
}

public Integer getYear() {
return year;
}

public void setYear(Integer year) {
this.year = year;
}

}

Then to generate JAVA objects with dynamic keys from JSON just run this:

Type type = new TypeToken<Map<String, Serial>>() {
}.getType();
Map<String, Serial> result = new Gson()
.fromJson(new InputStreamReader(new ByteArrayInputStream(jsonString.getBytes())), type);

To get for example Narcos serial information you need to write this:

Serial narcosSerial = result.get("Narcos");


Related Topics



Leave a reply



Submit