So I'm using GSON to parse JSON from an API and am stuck as to how to have it parse the dynamic fields in the data.
Here is an example of the JSON data returned on a query:
{
-
30655845: {
id: "30655845"
name: "testdata
description: ""
latitude: "38"
longitude: "-122"
altitude: "0"
thumbnailURL: http://someimage.com/url.jpg
distance: 9566.6344386665
}
-
28688744: {
id: "28688744"
name: "testdata2"
description: ""
latitude: "38"
longitude: "-122"
altitude: "0"
thumbnailURL: http://someimage.com/url.jpg
distance: 9563.8328713012
}
}
The way I am currently handling the single static values is with a class:
import com.google.gson.annotations.SerializedName;
public class Result
{
#SerializedName("id")
public int id;
#SerializedName("name")
public String name;
#SerializedName("description")
public String description;
#SerializedName("latitude")
public Double latitude;
#SerializedName("longitude")
public Double longitude;
#SerializedName("altitude")
public Double altitude;
#SerializedName("thumbnailURL")
public String thumbnailURL;
#SerializedName("distance")
public Double distance;
}
And then I can simply use GSON to parse that:
Gson gson = new Gson();
Reader reader = new InputStreamReader(source);
Result response= gson.fromJson(reader, Result.class);
I know this works on the sub-data as I can query and get a single entry and parse that quite easily, but what about the random integer values given for each value in the array? (ie the 30655845 and 2868874)
Any help?
According to GSON documentation you can do things like:
Type mapType = new TypeToken<Map<Integer, Result> >() {}.getType(); // define generic type
Map<Integer, Result> result= gson.fromJson(new InputStreamReader(source), mapType);
Or you can try to write custom serializer for your class.
Disclaimer: I too, have no experience with GSon but with other frameworks like Jackson.
Related
I have the answer to my own question, so I post both the answer and solution, as explicitly encouraged by Jeff Atwood. My question was originally for Kotlin, but while trying to find a solution, I also tried Java, so I provide the question and solution in both Java and Kotlin.)
Question in Kotlin
Given this deserializable Product class:
data class Product(val name: String, val prices: List<Int>)
and this json string that lacks the prices field:
{"name": "Computer"}
how can I deserialize the json string to a Product object using Jackson?
What I have tried in Kotlin
I tried this:
data class Product(val name: String, val prices: List<Int>)
// Missing "prices" field
val json = """{"name": "Computer"}"""
// "prices" field included works fine
// val json = """{"name": "Computer", "prices": [1,2,3]}"""
val mapper = ObjectMapper().registerKotlinModule()
val product = mapper.readValue<Product>(json)
println(product)
but it results in this exception:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of
[simple type, class MainKt$main$Product] value failed for JSON property prices due to
missing (therefore NULL) value for creator parameter prices which is a non-nullable type
at [Source: (String)"{"name": "Computer"}"; line: 1, column: 20]
(through reference chain: MainKt$main$Product["prices"])
When using Java
For Java the Product class would be:
class Product {
private String name;
private List<Integer> prices;
public Product(String name, List<Integer> prices) {
this.name = name;
this.prices = prices;
}
#Override
public String toString() {
return "Product{name='" + name + "\", prices=" + prices + '}';
}
}
with this Jackson code:
String json = "{\"name\": \"Computer\"}";
// String json = "{\"name\": \"Computer\", \"prices\": [1,2,3]}";
ObjectMapper mapper = new ObjectMapper();
// ParameterNamesModule is needed for non-arg constructor when not using Jackson annotations
mapper.registerModule(new ParameterNamesModule());
Product product = mapper.readValue(json, Product.class);
// Shows "prices=null", while "prices=[]" is required
System.out.println(product);
But this sets prices to null instead of an empty list.
Solution in Kotlin
This solution is for Jackson 2.11 and higher. It uses the jackson-module-kotlin Maven artifact.
val kotlinModule = KotlinModule.Builder()
.configure(KotlinFeature.NullToEmptyCollection, true)
.build()
val mapper = ObjectMapper().registerModule(kotlinModule)
val product = mapper.readValue(json, Product::class.java)
println(product)
So the solution uses KotlinFeature.NullToEmptyCollection, which has the following documentation:
Default: false. Whether to deserialize null values for collection
properties as empty collections.
There is also a map version: KotlinFeature.NullToEmptyMap.
For version 2.9 and 2.10 you can use the nullToEmptyCollection default parameter of the KotlinModule constructor.
Solution in Java using annotations
Annotated Product class:
class Product {
private String name;
private List<Integer> prices;
public Product(#JsonProperty("name") String name,
#JsonProperty("prices")
#JsonSetter(nulls = Nulls.AS_EMPTY) List<Integer> prices
) {
this.name = name;
this.prices = prices;
}
#Override
public String toString() {
return "Product{name='" + name + "\', prices=" + prices + '}';
}
}
Jackson code:
String json = "{\"name\": \"Computer\"}";
ObjectMapper mapper = new ObjectMapper();
Product product = mapper.readValue(json, Product.class);
System.out.println(product); // Product{name='Computer', prices=[]}
The key part in this solution is #JsonSetter(nulls = Nulls.AS_EMPTY), which sets the missing or null json field to an empty list in Java.
The number of verbose annotations, such as #JsonProperty("prices") can be reduced by using the jackson-module-parameter-names Maven artifact. Then only #JsonSetter(nulls = Nulls.AS_EMPTY) is needed.
Solution in Java without annotations
This solution requires the jackson-module-parameter-names Maven artifact. When using this module/artifact, don't forget to add the -parameters compiler argument.
Product class Jackson without annotations:
class Product {
private String name;
private List<Integer> prices;
public Product(String name, List<Integer> prices) {
this.name = name;
this.prices = prices;
}
#Override
public String toString() {
return "Product{name='" + name + "\", prices=" + prices + '}';
}
}
Jackson code:
String json = "{\"name\": \"Computer\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule());
mapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
Product product = mapper.readValue(json, Product.class);
System.out.println(product);
The ParameterNamesModule model is required to allow Jackson to reflect the Product constructor parameters by name, so that #JsonProperty("prices") isn't required anymore.
And JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY) is used to convert missing or null json fields to a list.
Your Product class need to implement Serializable class. It can make consistency data.
class Product implements Serializable {
..............
}
I'm trying to fetch JSON data from my website throw REST API with retrofit2.
But when I run the app this error message show:
Can not find a (Map) Key deserializer for type [simple type, class com.example.app.ReferralApiModel]
I'm using retrofit library.
This is my code for the retrofit call:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(AppConfig.URL)
.addConverterFactory(JacksonConverterFactory.create())
.client(defaultHttpClient)
.build();
ReferralsPlaceHolderApi placeHolderApi = retrofit.create(ReferralsPlaceHolderApi.class);
Call<List<Map<ReferralApiModel, String>>> call = placeHolderApi.getReferrals();
And this is my ReferralsPlaceHolderApi class:
public interface ReferralsPlaceHolderApi {
#JsonDeserialize(keyAs = ReferralsCustomDeserializer.class)
#GET(AppConfig.ENDPOINT_REFERRALS)
Call<List<Map<ReferralApiModel, String>>> getReferrals();
}
Also this is my ReferralApiModel class:
public class ReferralApiModel {
private String date;
private String amount;
private String currency;
private String status;
public ReferralApiModel() {}
public ReferralApiModel(String date, String amount, String currency, String status) {
this.date = date;
this.amount = amount;
this.currency = currency;
this.status = status;
}
public String getDate() {
return date;
}
public String getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
public String getStatus() {
return status;
}
}
This is the json data that I'm trying to get:
"[{\"id\":\"1\",\"refferal_wp_uid\":\"0\",\"campaign\":\"\",\"affiliate_id\":\"5\",\"visit_id\":\"1\",\"description\":\"\",\"source\":\"woo\",\"reference\":\"302\",\"reference_details\":\"68\",\"parent_referral_id\":\"0\",\"child_referral_id\":\"0\",\"amount\":\"1500.00\",\"currency\":\"\د\ج\",\"date\":\"2022-01-31 12:53:29\",\"status\":\"0\",\"payment\":\"0\",\"username\":\"aaa\"},{\"id\":\"2\",\"refferal_wp_uid\":\"0\",\"campaign\":\"\",\"affiliate_id\":\"5\",\"visit_id\":\"2\",\"description\":\"\",\"source\":\"woo\",\"reference\":\"303\",\"reference_details\":\"68\",\"parent_referral_id\":\"0\",\"child_referral_id\":\"0\",\"amount\":\"1500.00\",\"currency\":\"\د\ج\",\"date\":\"2022-01-31 13:03:43\",\"status\":\"1\",\"payment\":\"0\",\"username\":\"aaa\"},{\"id\":\"3\",\"refferal_wp_uid\":\"0\",\"campaign\":\"\",\"affiliate_id\":\"5\",\"visit_id\":\"2\",\"description\":\"\",\"source\":\"woo\",\"reference\":\"304\",\"reference_details\":\"68\",\"parent_referral_id\":\"0\",\"child_referral_id\":\"0\",\"amount\":\"1500.00\",\"currency\":\"\د\ج\",\"date\":\"2022-01-31 13:04:33\",\"status\":\"2\",\"payment\":\"0\",\"username\":\"aaa\"}]"
Can anyone help me with this?.
Also I've found that this problem may be a class mapping problem, from this answer :
https://stackoverflow.com/a/16383752/8055951
If it's ?!, Can someone tell me how to map the ReferralsPlaceHolderApi class.
Thanks.
Jackson cannot deserialize custom classes as map keys. The key of your deserialized map is ReferralApiModel. I order to achieve it, you need to write your own KeyDeserializer and register it for your class with Jackson. You can see here or here how to do that.
Also the json string in the question makes it look as if you don't need to deserialize into List<Map<ReferralApiModel, String>>, but into List<ReferralApiModel> instead. Which would make writing custom key deseriaslizers redundant.
Edit: Ok, receiving json array, which has been json sting-ified is just strange. It would be best, if someone on your team is responsible for this API and can fix it. If not, you have workarounds:
Parse twice with object mapper - first parse it to normal string, which would be json array, then parse this string into List<YourObject>
ObjectMapper mapper = new ObjectMapper();
String string = mapper.readValue(initialJson, String.class);
List<ReferralApiModel> list = mapper.readValue(string, TypeFactory.defaultInstance().constructCollectionType(List.class, ReferralApiModel.class));
list.forEach(System.out::println);
Turn it manually into proper json array. That means remove first and last char - double quote, and remove all those escapes - \. Something like this:
String jsonString = "the string";
jsonString = jsonString.substring(1, jsonString.length() - 1).replace("\\", "");
ObjectMapper mapper = new ObjectMapper();
List<ReferralApiModel> list = mapper.readValue(jsonString, TypeFactory.defaultInstance().constructCollectionType(List.class, ReferralApiModel.class));
list.forEach(System.out::println);
I am facing with a IMHO a strange behaviour of GSON. Let's take the following example:
{
"Name": "emaborsa",
"Surname": null
}
and my POJO is:
public class User {
#SerializedName("Name")
private String name;
#SerializedName("Surname")
private String surname;
// getter and setter
}
I deserialize it using the following code:
Gson g = new Gson();
User user = g.fromJson(json, User.class);
The variable name is set with "emaborsa", the variable surname I expected it were set to null but there is a string "null " instead.
Is it the correct behaviour or am I missing something? I tried to google it but it is hard to find something related to String and null...
This worked fine for me, using your code as the basis:
package gsonexample3;
import com.google.gson.Gson;
import com.google.gson.annotations.*;
public class User {
public static void main(String[] args) {
Gson g = new Gson();
User user = g.fromJson(json, User.class);
}
#SerializedName("Name")
private String name;
#SerializedName("Surname")
private String surname;
private static String json = "{\"Name\": \"emaborsa\", \"Surname\": null}";
}
Looks like you have to specify that you want to serialize nulls.
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.serializeNulls()
Coming from - https://howtodoinjava.com/gson/serialize-null-values/
SoapObject data=(SoapObject) envelope.bodyIn;
String result = String.valueOf(((SoapObject) envelope.bodyIn).getProperty(0));
JSONObject _jobjec = new JSONObject(result);
UserId = _jobjec.get("UserId").toString();
UserParentId = _jobjec.get("UserParentId").toString();
UserName = _jobjec.get("UserName").toString();
UserPassword = _jobjec.get("UserPassword").toString();
UserMobile = _jobjec.get("UserMobile").toString();
UserEmail = _jobjec.get("UserEmail").toString();
UserMpin = _jobjec.get("UserMpin").toString();
This is my COde i am trying to JOSon Parse and get value but what happen { ,} remove in result when i make json Object and trying and get value then i get Excepion i am getting
String.valueOf(((SoapObject) envelope.bodyIn).getProperty(0) :
{"UserId":"2","UserParentId":"1","UserName":"Anilkumar","UserPassword":"12546",
"UserMobile":"8130513899","UserEmail":"anilaat87#gmail.com","UserMpin":"7890",
"UserBalance":"20.0000","UserResponseMessage":"Is Valid"}
but unable to parse it
Directly using JSONObject to parse POJO is tedious and error prone, recommended using one of the below libraries:
GSON
JACKSON
MOSHI
JSON.simple
First, define your POJO class, you can use some online service, e.g. this one or this,
Just paste your example json string, then you can get below pojo class (you don't need to write it on your own) in 2 seconds:
package com.example;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Example {
#SerializedName("UserId")
#Expose
public String userId;
#SerializedName("UserParentId")
#Expose
public String userParentId;
#SerializedName("UserName")
#Expose
public String userName;
#SerializedName("UserPassword")
#Expose
public String userPassword;
#SerializedName("UserMobile")
#Expose
public String userMobile;
#SerializedName("UserEmail")
#Expose
public String userEmail;
#SerializedName("UserMpin")
#Expose
public String userMpin;
#SerializedName("UserBalance")
#Expose
public String userBalance;
#SerializedName("UserResponseMessage")
#Expose
public String userResponseMessage;
}
Then to get the java object, use below gson calls:
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Example instance = gson.fromJson(jsonString, Example.class);
If you use Jackson or other libraries, the same process applies, and they differ only in the detailed function calls.
you have to write code like this
SoapObject data=(SoapObject) envelope.bodyIn;
String result = String.valueOf(((SoapObject)envelope.bodyIn).getProperty(0));
JSONObject _jobjec = new JSONObject(result);
UserId = _jobjec.getString("UserId");
UserParentId = _jobjec.getString("UserParentId");
UserName = _jobjec.getString("UserName");
UserPassword = _jobjec.getString("UserPassword");
UserMobile = _jobjec.getString("UserMobile");
UserEmail = _jobjec.getString("UserEmail");
UserMpin = _jobjec.getString("UserMpin");
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"
}