Is there way to associate arbitrary data structure with GSON parser? - java

First of all I've seen this question, but I did not see the full answer to my question and this question was asked 2 years ago.
Introduction:
For example we have an JSON with such structure:
{
"name": "some_name",
"description": "some_description",
"price": 123,
"location": {
"latitude": 456987,
"longitude": 963258
}
}
I can use GSON library for auto parsing this JSON to my object's class.
For this I must create class describing JSON structure, like below:
public class CustomClassDescribingJSON {
private String name;
private String description;
private double price;
private Location location;
// Some getters and setters and other methods, fields, etc
public class Location {
private long latitude;
private long longitude;
}
}
And next I can auto parse JSON to object:
String json; // This object was obtained earlier.
CustomClassDescribingJSON object = new Gson().fromJson(json, CustomClassDescribingJSON.class);
I have a few ways for changing names of fields in my class (for writing more readable code or to follow language guidelines). One of them below:
public class CustomClassDescribingJSON {
#SerializedName("name")
private String mName;
#SerializedName("description")
private String mDescription;
#SerializedName("price")
private double mPrice;
#SerializedName("location")
private Location mLocation;
// Some getters and setters and other methods, fields, etc
public class Location {
#SerializedName("latitude")
private long mLatitude;
#SerializedName("longitude")
private long mLongitude;
}
}
Using same code like above for parsing JSON:
String json; // This object was obtained earlier.
CustomClassDescribingJSON object = new Gson().fromJson(json, CustomClassDescribingJSON.class);
But I could not find a possibility to change the structure of the class. For example, I would like to use next class for parsing the same JSON:
public class CustomClassDescribingJSON {
private String mName;
private String mDescription;
private double mPrice;
private long mLatitude;
private long mLongitude;
}
Questions:
Same as in the header: Is there way to associate arbitrary data structure with GSON parser?
Maybe there are another libraries to do what I want?

Would a custom GSON (de-)serializer help?
See https://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserialization

Simply convert the JSON string into HashMap<String, Object> then populate any type of custom structure by simply iterating it or create a constructor in each custom object class as shown below to populate the fields.
class CustomClassDescribingJSON {
public CustomClassDescribingJSON(Map<String, Object> data) {
// initialize the instance member
}
}
Sample code:
Reader reader = new BufferedReader(new FileReader(new File("resources/json12.txt")));
Type type = new TypeToken<HashMap<String, Object>>() {}.getType();
HashMap<String, Object> data = new Gson().fromJson(reader, type);
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(data));
output:
{
"price": 123.0,
"location": {
"latitude": 456987.0,
"longitude": 963258.0
},
"description": "some_description",
"name": "some_name"
}

Related

Jackson Deserialization of dynamic data fails,

public class Baseproperties
{
#JsonProperty("id")
private String id ;
private Integer ccode;
//...set and geters
}
public class Person
{
#JsonProperty("name")
private String name ;
private Integer age;
#JsonProperty("props")
private Baseproperties bprop;
//...set and geters
}
public class Cars
{
#JsonProperty("model")
private String Model ;
private Integer yearOfMake;
#JsonProperty("props")
private Baseproperties bprop;
//...set and geters
}
public MessageWrapper
{
#JsonProperty("ct")
private String classType;
private Object data;
//...set and geters
}
I need to serialise MessageWrapper class to json, but the approach fails due to unable to desearialize the Object data;
here i am reading the classType and desearializing it to either Person or CarType
//Person
{
"name": "arnold",
"age": 21
}
//car
{
"model": "Moriz",
"yearOfMake": 1892
}
//example MessageWrapper
String s= "{
"ct": "<packagename>.car",
"data": {
"model": "Moriz",
"yearOfMake": 1892
"props":{
"id" : "12312",
"ccode" :33
}
}
}"
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
MessageWrapper mw = mapper.readValue(s, MessageWrapper.class);
if(mw.getclassType().toString().equals("<packagename>.car"))
Cars cw = mapper.readValue(mw.getData(), Cars.class);
but cw is wrong // serialise fails.
This is because there is no ObjectMapper::readValue method that takes Object as first argument.
By default with your approach Jackson will deserialize your data field to LinkedHashMap because you have given it Object type.
To then deserialize this value manually you will have to use ObjectMapper::convertValue and passing Cars.class as argument :
Cars cw = mapper.convertValue(mw.getData(), Cars.class);
And also get rid of :
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
as it does not seem to be needed here.
And just to add, I am not sure that approach with such dynamic data is good because, if you will be creating more and more types of objects you will end up with a tower of ifs or colosal switch statement.

GSON parsing multiple keys of the same type

I'm working on a personal project in Android and I want to use GSON to parse a JSON file containing the data I need.
I have a local JSON file with the following structure:
{
"Object1": {
"foo": "value1",
"bar": "value2",
"baz": "value3",
...
},
"Object2": {
"foo": "value4",
"bar": "value5",
"baz": "value6",
...
},
...
}
I have already made an Object class of the following structure:
Class Object {
String data;
...
}
How would I parse this JSON file with this structure?
EDIT: The JSON file I use is very large, it contains about 400+ of these objects of type Object. I would have to iterate over each object to create a new JSONObject, but I do not know how to do this.
In the solution below, we convert the JSON you've provided in your link as a JSONOject. Then we get the list of names contained in the JSON ("Abaddon", "Archeri", ...). Once we have the list we iterate through it. For each name we get the JSON object associated with it.
Then we use GSON to convert each object into a Demon object. The Demon class has been generated using http://www.jsonschema2pojo.org/ as suggested above.
As all the objects in the JSON have the same structure we need only one class to deserialize every single one of them.
Deserializer
public List<Demon> deserialize(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
final JSONArray names = jsonObject.names();
final List<Demon> demons = new ArrayList<>();
final Gson gson = new Gson();
Demon demon;
for (int i = 0; i < names.length(); i++) {
demon = gson.fromJson(jsonObject.get(names.getString(i)).toString(), Demon.class);
demons.add(demon);
}
return demons;
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
Demon class
public class Demon {
#SerializedName("ailments")
#Expose
public String ailments;
#SerializedName("align")
#Expose
public String align;
#SerializedName("code")
#Expose
public Integer code;
#SerializedName("inherits")
#Expose
public String inherits;
#SerializedName("lvl")
#Expose
public Integer lvl;
#SerializedName("pcoeff")
#Expose
public Integer pcoeff;
#SerializedName("race")
#Expose
public String race;
#SerializedName("resists")
#Expose
public String resists;
#SerializedName("skills")
#Expose
public List<String> skills = null;
#SerializedName("source")
#Expose
public List<String> source = null;
#SerializedName("stats")
#Expose
public List<Integer> stats = null;
public Demon(){
// Default constructor
}
}

Retrofit/Android - dynamic response support

I'm having trouble with mapping resulting json data to pojo class with Retrofit. I need to determine Firebase topics by token. This can be eaisly done with Google's json api, as described here: https://developers.google.com/instance-id/reference/server#get_information_about_app_instances
In my case, server response is simlar to this:
{
"applicationVersion": "36",
"connectDate": "2018-02-04",
"attestStatus": "ROOTED",
"application": "<my application id>",
"scope": "*",
"authorizedEntity": "205414012839",
"rel": {
"topics": {
"topic1": {
"addDate": "2018-02-04"
},
"topic2": {
"addDate": "2018-01-31"
}
}
},
"connectionType": "WIFI",
"appSigner": "<hash>",
"platform": "ANDROID"
}
The problem is basically rel and topics structure, because topics is dynamic and field list can by anything and it's unknown. So I can't generate simple POJO to get it mapped by Retrfofit automatically.
Can I force Retrofit to treat topics as single String field, I will able to parse it later to retrieve topics list? Or is there any other soulution?
Any ideas?
If you use gson, you can define rel as a JsonElement. If you use moshi, you can define it as a Map.
for gson:
public class Response{
private String applicationVersion;
private String connectDate;
private String attestStatus;
private String application;
private String scope;
private String authorizedEntity;
private String connectionType;
private String appSigner;
private String platform;
private JsonElement rel;
}
for moshi:
public class Response{
private String applicationVersion;
private String connectDate;
private String attestStatus;
private String application;
private String scope;
private String authorizedEntity;
private String connectionType;
private String appSigner;
private String platform;
private Map<String, Map<String, Map<String, String>>> rel;
}

Parsing JSON with Retrofit and GSON, error when trying to parse and obtain callback.

I am using to Retrofit to handle Calls to my API for an Android Application. I am trying to get Retrofit to handle the parsing of the JSON, and creating a list of Objects in accordance with the POJO i have created.
The error i receive is "com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 176".
I used JsonSchema2Pojo to generate my java classes. The classes and associated JSON are as follows.
{"status":"success","data":[{"sort_key":1,"event_id":1947357,"title":"2014 US Open Tennis Session 15 (Mens\/Womens Round of 16)","datetime_utc":"2014-09-01T15:00:00","venue":{"city":"Flushing","name":"Louis Armstrong Stadium","extended_address":"Flushing, NY 11368","url":"https:\/\/seatgeek.com\/venues\/louis-armstrong-stadium\/tickets\/?aid=10918","country":"US","display_location":"Flushing, NY","links":[],"slug":"louis-armstrong-stadium","state":"NY","score":0.73523,"postal_code":"11368","location":{"lat":40.7636,"lon":-73.83},"address":"1 Flushing Meadows Corona Park Road","timezone":"America\/New_York","id":2979},"images":["https:\/\/chairnerd.global.ssl.fastly.net\/images\/performers-landscape\/us-open-tennis-45e2d9\/5702\/huge.jpg","https:\/\/chairnerd.global.ssl.fastly.net\/images\/performers\/5702\/us-open-tennis-c1ccf7\/medium.jpg","https:\/\/chairnerd.global.ssl.fastly.net\/images\/performers\/5702\/us-open-tennis-01f513\/large.jpg","https:\/\/chairnerd.global.ssl.fastly.net\/images\/performers\/5702\/us-open-tennis-4e07f2\/small.jpg"]}
From this i believe i need to generate 3 POJO's, my higher level "EventObject" Class, A Location Class, and a Venue Class. These classes and their variables follow:
EventObject Class:
public class EventObject {
private Integer sortKey;
private Integer eventId;
private String title;
private String datetimeUtc;
private Venue venue;
private List<String> images = new ArrayList<String>();
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
Location Class:
public class Location {
private Float lat;
private Float lon;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
Venue Class:
public class Venue {
private String city;
private String name;
private String extendedAddress;
private String url;
private String country;
private String displayLocation;
private List<Object> links = new ArrayList<Object>();
private String slug;
private String state;
private Float score;
private String postalCode;
private Location location;
private String address;
private String timezone;
private Integer id;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
My interface for the Api Call is as follows:
public interface UserEvents {
#GET("/user/get_events")
void getEvents(#Header("Authorization")String token_id,
#Query("event_type")String event_type,
#Query("postal_code")int postalCode,
#Query("per_page") int perPage ,
#Query("lat") int lat,
#Query("lon") int lon,
#Query("month")int month,
#Query("page")int page,
Callback<List<EventObject>>callback) ;
}
Here is its implementation in my code :
UserEvents mUserEvents = mRestAdapter.create(UserEvents.class);
mUserEvents.getEvents(token_Id, "sports",11209,25,0, 0, 9, 2, new Callback <List<EventObject>>() {
#Override
public void success(List<EventObject> eventObjects, retrofit.client.Response response) {
Log.d(TAG,"Success");
}
There is alot going on here, but i believe that i am probably going wrong with how i am handling the JSON. When i copied and pasted in my JSON to the Pojo generator, i did not include "status:success, " data:{
I essentially just used the entire entry of an element in the Array ( everything from {sort_key onward until the next sort key ) and pushed that through the converter.
This is my first try at Retrofit and API work, and parsing anything this complicated.
I am hoping its something that someone else will be able to point out. I have googled as well i could to sort this out with no luck.
Thanks for looking.
The main problem is that you are not getting the root element of the response. You need to create an entity "response" that gets the items status and data. It would look something like this:
public class RootObject {
#Expose
private String status;
#Expose
private EventObject data;
//getters and setters here
}
Then when you make the callback you should point to your RootObject, mUserEvents.getEvents(token_Id, "sports",11209,25,0, 0, 9, 2, new Callback <RootObject>()
One more thing, Retrofit uses GSON to parse your json reponse. It means that when you create the entities, the variables need to match the name of the objects coming in the response. If it doesn't you need to tell GSON how to map the variables, like this:
#SerializedName("extended_address")
#Expose
private String extendedAddress;
In that case the value coming in the json is "extended_address" and will be mapped to the String extendedAddress. If you don't put that #SerializedName line the parsing will fail. If you want to skip that line then you can call your variable "extended_address" so it matches the response.
The #Expose is needed by GSON to parse the variable below it. If a variable doesn't have it then GSON will ignore that parsing. So you need to fix both the #Expose and #SerializedName on your entities so GSON works correctly.
Hope it helps.

GSON custom object deserialisation setting all fields to null

I am using GSON for the first time. I am trying to deserialise a JSON string into a custom object, but every property of my object is set to null. There are no parsing errors so I think the JSON properties are not mapping to the object?
Here is my code, if anyone could point out where I have gone wrong it would be much appreciated. I have checked everything against tutorial and cannot see the problem. The only thing is that there are more properties in the JSON string than in my object but I hope that does not matter.
JSON string:
{
"_id": "D7D4A7D8219CA25848257C63000A1B50",
"ReportingPerson": "TRAIN2 Ifap",
"InjuredPerson": "TRAIN3 Ifap",
"DateReported": {
"$date": "2014-01-17T00:00:00.000Z"
},
"Company": "test",
"Division": "Learning & Development",
"Site_id": "3CA9AD4E6066388648257B7500047D90",
"Department_id": "724BC4B509E7B61648257363002FD645",
"Area": "Training Room",
"DocNo": "002223",
"CreatedBy": "Ifap TRAIN2",
"DateComposed": {
"$date": "2014-01-17T01:50:23.000Z"
},
"OccurTime": "12:00:00",
"Affiliation": "Employee",
"BriefDescription": "Employee tripped over power lead in computer lab.",
"ThirdPartyInvolvedYN": "No",
"ThirdPartyName": "",
"ThirdPartyAddress": [
""
],
"ThirdPartyTel": "",
"Classification": "Minor Injury",
"Confidential": "",
"ConfidentialMonitors": [
""
],
"IncidentCategory": "2",
"IncidentCategoryPotential": "3",
"ReportableYN": "No",
"ExternalBody": [
""
],
"Authorisor": "",
"WorkSafeConfirmedYN": "No",
"Details": "Fell over cord in computer lab when walking through. Held hand out to brace fall and fell on pinkie finger.",
"Controls": [
"Tape over cord."
],
"Witnesses": [
"No"
],
"Supervisor": "TRAIN1 Ifap",
"IntAuthorisor": "TRAIN3 Ifap",
"IntAuthorisorNext": "",
"AssociatedRisks": {},
"OpenActions": {},
"ClosedActions": {}
}
POJO:
public class Incident {
#SerializedName("_id")
private String _id;
private String docNo;
private String site_id;
private String company;
private String division;
private String department_id;
private Date dateReported;
private String briefDescription;
private String thirdPartyInvolvedYN;
private String supervisor;
private String classification;
private String status;
private String injuredPerson;
private String reportingPerson;
private Date occurDate;
private String occurTime;
//Getters & Setters...
}
Main method:
public Incident convertJSONToBean(String json) {
Incident i = new Incident();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Type type = new TypeToken<Incident>(){}.getType();
try {
i = gson.fromJson(json, type);
} catch (JsonSyntaxException e) {
e.printStackTrace();
} catch (JsonIOException e) {
e.printStackTrace();
}
return i;
}
Type is set correctly to Incident.class. But any properties of the resulting Incident object are all null.
I tried commenting out all properties except _id to see if I could get just one to populate but it was still set to null.
In Json format DateComposed & DateReported properties are the Object, You need to create the
either custom model classes for them or write CustomDeserializer class for them.
"DateComposed": { "$date": "2014-01-17T01:50:23.000Z" }
"DateReported": {"$date": "2014-01-17T00:00:00.000Z"}
public class Incident {
#SerializedName("_id")
private String _id;
#SerializedName(value = "ReportingPerson")
// other properties, you need to put #SerializedName on each property
............
// No need to put SerializedName annotation on dateReported & dateComposed
private Date dateReported;
private Date dateComposed;
#SerializedName(value = "ThirdPartyAddress")
private List<String> thirdPartyAddress;
#SerializedName(value = "ConfidentialMonitors")
private List<String> confidentialMonitors;
#SerializedName(value = "ExternalBody")
private List<String> externalBody;
#SerializedName(value = "Controls")
private List<String> controls;
#SerializedName(value = "Witnesses")
private List<String> witnesses;
// getter/setter
....
}
Here is the CustomDeserializer class for Deserializing date properties
public class CustomDeserializer implements JsonDeserializer<Incident> {
#Override
public Incident deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final Gson gson = new Gson();
// Parse the JsonElement tree here
final Incident incident = gson.fromJson(json, Incident.class);
// getting date properties as string from JsonElement and parse them into date object.
String dateReportedStr = jsonObject.get("DateReported").getAsJsonObject().get("$date").getAsString();
String dateComposedStr = jsonObject.get("DateComposed").getAsJsonObject().get("$date").getAsString();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
try {
// setting date properties in incident object
incident.setDateReported(df.parse(dateReportedStr));
incident.setDateComposed(df.parse(dateComposedStr));
} catch (ParseException e) {
e.printStackTrace();
}
return incident;
}
}
Finally Parse it
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Incident.class, new CustomDeserializer());
Gson gson = gsonBuilder.create();
Incident incident = gson.fromJson(Your_JSON_STR, Incident.class);
You have interesting date objects in your json string. Such as;
"DateReported": {
"$date": "2014-01-17T00:00:00.000Z"
}
Which causes a JsonParseException because of your Incident class:
com.google.gson.JsonParseException: The date should be a string value
For your Incident class, Dates at json value should be something like;
"DateReported": "2014-01-17T00:00:00.000Z"
If you don't have an option to change dates at json value, then you should define its custom date holder class:
public class CustomDateHolder {
#SerializedName("$date")
private Date date;
// Getters & Setters...
}
And change those date fields' type to CustomDateHolder;
public class Incident {
#SerializedName("_id")
private String _id;
private String docNo;
private String site_id;
private String company;
private String division;
private String department_id;
private CustomDateHolder dateReported;
private String briefDescription;
private String thirdPartyInvolvedYN;
private String supervisor;
private String classification;
private String status;
private String injuredPerson;
private String reportingPerson;
private CustomDateHolder occurDate;
private String occurTime;
// Getters & Setters...
}
Also modify your GsonBuilder a little bit:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Gson gson = gsonBuilder.create();

Categories