How to parse dynamic JSON with Retrofit 2 (+Nested objects) - java

I'm trying to parse JSON response that looks like this.
{
"Cryptsy": {
"AMC": [
"BTC"
],
"CIRC": [
"BTC"
],
"SYNC": [
"BTC"
]
},
"Bitstamp": {
"EUR": [
"USD"
],
"ETH": [
"USD",
"EUR"
],
"XRP": [
"USD",
"EUR",
"BTC"
]
},
// ...
// More objects...
// ...
}
As you can see, this one has the dynamic keys and each values are also objects with dynamic keys. I tried parse this using retrofit2 and GsonConverter but it causes the exception
W/System.err: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $
I think it's because the JSON is nested and all objects don't have any fixed keys.
Here's my code.
PairListResponse.java
// This is the GSON model class
class PairListResponse {
private Map<String, Map<String, String[]>> exchangePairs;
PairListResponse() {
}
Map<String, Map<String, String[]>> getExchangePairs() {
return exchangePairs;
}
void setExchangePairs(Map<String, Map<String, String[]>> exchangePairs) {
this.exchangePairs = exchangePairs;
}
Map<String, String[]> getTradingPairs(String fromSymbol) {
return exchangePairs.get(fromSymbol);
}
}
PairListDeserializer.java
public class PairListDeserializer implements JsonDeserializer<PairListResponse> {
private static final String TAG = PairListDeserializer.class.getSimpleName();
#Override
public PairListResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final Map<String, Map<String, String[]>> exchangePairs = readPairMap(jsonObject);
PairListResponse result = new PairListResponse();
result.setExchangePairs(exchangePairs);
return result;
}
#Nullable
private Map<String, Map<String, String[]>> readPairMap(#NonNull final JsonObject jsonObject) {
// Initializing Hashmap for the outer object
final Map<String, Map<String, String[]>> result = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
String exchange = entry.getKey();
String fromSymbol;
String[] toSymbols;
JsonObject fsymbolObj = entry.getValue().getAsJsonObject();
// Initializing Hashmap for inner objects
final Map<String, String[]> pairsPerCoin = new HashMap<>();
for (Map.Entry<String, JsonElement> inner_entry : fsymbolObj.entrySet()) {
fromSymbol = inner_entry.getKey();
toSymbols = toStringArray(inner_entry.getValue().getAsJsonArray());
pairsPerCoin.put(fromSymbol, toSymbols);
}
result.put(exchange, pairsPerCoin);
}
return result;
}
private static String[] toStringArray(JsonArray array) {
if (array == null) return null;
String[] arr = new String[array.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = array.get(i).toString();
}
return arr;
}
}
Thanks in advance!

Sorry, I made a dumbest mistake!
In my retrofit API call, I forgot to set the correct model class name.
public interface TradingPairAPICall {
#GET("exchanges")
Call<String> getAllPairList();
}
In fact, it needs to be
Call<PairListResponse> getAllPairList();
I changed it and it successfully worked.

Related

Gson- Parsing a JSON array of JSON objects to ArrayList<org.json.JSONObject>

I have a JSON string like this:
{
"r": [
{
"pic": "1.jpg",
"name": "Name1"
},
{
"pic": "2.jpg",
"name": "Name2"
},
{
"pic": "3.jpg",
"name": "Name3"
}
]
}
I want to parse to this POJO model:
public class Catalog {
#SerializedName("r")
#Expose
private List<JSONObject> r = new ArrayList<JSONObject>();
public List<JSONObject> getR() {
return r;
}
public void setR(List<JSONObject> r) {
this.r = r;
}
}
I am parsing this way:
Catalog cat = new Gson().fromJson(jsonString,Catalog.class);
But finally am getting this json
{
"r": [
{
"nameValuePairs": {}
},
{
"nameValuePairs": {}
},
{
"nameValuePairs": {}
}
]
}
Please note that I don't want to use com.google.gson.JsonObject.
I want to use org.json.JSONObject. How to achieve this because almost all of my code uses it?
As it was already mentioned in other answer and comments, you probably might not really want to use org.json.JSONObject for several reasons. But if it's a must for you, you just have to create your org.json.JSONObject-aware Gson instance.
final class JSONObjectJsonDeserializer
implements JsonDeserializer<JSONObject> {
// The implementation is fully thread-safe and can be instantiated once
private static final JsonDeserializer<JSONObject> jsonObjectJsonDeserializer = new JSONObjectJsonDeserializer();
// Type tokens are immutable values and therefore can be considered constants (and final) and thread-safe as well
private static final TypeToken<Map<String, Object>> mapStringToObjectTypeToken = new TypeToken<Map<String, Object>>() {
};
private JSONObjectJsonDeserializer() {
}
static JsonDeserializer<JSONObject> getJsonObjectJsonDeserializer() {
return jsonObjectJsonDeserializer;
}
#Override
public JSONObject deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
// Convert the input jsonElement as if it were a Map<String, Object> (a generic representation for JSON objectS)
final Map<String, Object> map = context.deserialize(jsonElement, mapStringToObjectTypeToken.getType());
// And forward the map to the JSONObject constructor - it seems to accept it nice
return new JSONObject(map);
}
}
Gson is designed thread-safe and does not need to be instantiated every time serialization or deserialization is necessary:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(JSONObject.class, getJsonObjectJsonDeserializer())
.create();
And finally:
final Catalog catalog = gson.fromJson(jsonString, Catalog.class);
out.println(catalog.getR());
with the following result:
[{"name":"Name1","pic":"1.jpg"}, {"name":"Name2","pic":"2.jpg"}, {"name":"Name3","pic":"3.jpg"}]
Anyway, I would suggest you to redesign your mappings model.
I think you don't need JSONObject.
Try this
// is wrapped class for serialized json.
public class JsonExample
{
List<Catalog> r;
}
public class Catalog {
private String pic;
private String name;
public String getPic() {
return pic;
}
public String getName() {
return name;
}
}
JsonExample example = new Gson().fromJson(json, JsonExample.class);
Additional - using JSONObject
JSONObject obj = new JSONObject(json);
JSONArray arr = obj.getJSONArray("r");
List<Catalog> cataList = new ArrayList<>();
for(int i = 0 ; i < arr.length() ; ++i)
{
cataList.add(new Catalog(arr.getJSONObject(i)));
}
public class Catalog {
private String pic;
private String name;
public Catalog(JSONObject obj) throws JSONException
{
pic = obj.getString("pic");
name = obj.getString("name");
}
public String getPic() {
return pic;
}
public String getName() {
return name;
}
}
I think in your case, usage of gson library is not required at all.
Only org.json can solve the entire problem.
E.g.:
JSONObject json = new JSONObject(jsonString);
JSONArray jsonArray = json.getJSONArray("r");
List<JSONObject> jsonList = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
jsonList.add(jsonArray.getJSONObject(i));
}
Catalog catalog = new Catalog();
catalog.setR(jsonList);

Deserializing arrays using GSON

So, I'm quite new to this Json world and well I'm trying to parse this Json below into a class in java using Gson, but I'm not sure if this is the correct way, because I want this to be a list of maps where the nomeArquivo would be the key in this map, can you guys help me to achive this? Or this way I posted is fine?
Test class
public class JsonTeste {
public static void main(String[] args) {
Gson gson = new Gson();
try (Reader reader = new FileReader("foobar.json")) {
List<FastqcJson[]> list = gson.fromJson(reader, new TypeToken<List<FastqcJson[]>>(){}.getType());
for (FastqcJson[] fastqcJsons : list) {
for (FastqcJson fastqcJson : fastqcJsons) {
System.out.println(fastqcJson);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Bean class
public class FastqcJson {
#SerializedName("name")
private String nomeArquivo;
#SerializedName("data")
private HashMap<Integer, Double> mediaBaseNumeros;
....
}
Printed Objects
FastqcJson [nomeArquivo=SRR3192396, mediaBaseNumeros={1=31.939449600540865, 2=32.05829640249262}]
FastqcJson [nomeArquivo=SRR3192397, mediaBaseNumeros={1=32.01549563582736, 2=32.13918804626231}]
Json File
[ [
{
"color": "#5cb85c",
"data": [
[
1,
31.939449600540865
],
[
2,
32.05829640249262
]
],
"name": "SRR3192396"
},
{
"color": "#5cb85c",
"data": [
[
1,
32.01549563582736
],
[
2,
32.13918804626231
]
],
"name": "SRR3192397"
}
]
]
There is no built-in way to do this since "data" is an array of arrays in its native JSON representation.
To do what you want to do you will need to create a wrapper type and write a custom deserializer:
public class MediaBase {
private HashMap<Integer, Double> numeros;
public MediaBase(HashMap<Integer, Double> numeros) {
this.numeros = numeros;
}
}
public class FastqcJson {
#SerializedName("name")
private String nomeArquivo;
#SerializedName("data")
private MediaBase mediaBaseNumeros;
....
}
public class MediaBaseAdapter extends TypeAdapter<MediaBase> {
#Override
public MediaBase read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
HashMap<Integer, Double> output = new HashMap<>();
reader.beginArray(); //Read "data" as array
while (reader.hasNext()) {
reader.beginArray(); //Read data array
int key = reader.nextInt();
double value = reader.nextDouble();
output.put(key, value);
reader.endArray();
}
reader.endArray();
return new MediaBase(output);
}
#Override
public void write(JsonWriter writer, MediaBase value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
// Inverse of reader
HashMap<Integer, Double> output = value.numeros;
writer.beginArray();
for (Map.Entry<Integer, Double> e : output.entries()) {
writer.beginArray();
writer.value(e.getKey());
writer.value(e.getValue());
writer.endArray();
}
writer.endArray();
}
}
Add this adapter during the creation of your GSON instance with GsonBuilder.registerTypeAdapter(MediaBase.class, new MediaBaseAdapter()) and the adaptor will correctly pack and unpack your datatype.
Do note that this is written from memory and I've not been able to verify that it compiles.

json to java hashmap using complex key

I am currently working on an app in which I need to serialize a
HashMap<Object1, Object2> into JSON and then deserialize from JSON to the same `HashMap'.
I am able to serialize it using the usual mapper and overriding the toString() method for Object1.
public String toString(){
String res = Object1.elem1 + ";" + Object1.elem2;
return res
}
I am then able to serialize and get the expected json (where res is the String I defined before easier not to write it all back).*
{res : Object2JsonRepresentation}
Then I want to deserialize, so I use a custom keyDeserializer :
#XmlElement(name="myMap")
#JsonDeserialize(keyUsing = Object1KeyDeserializer.class)
public HashMap <Object1,Object2> myMap = new HashMap <>();
And the Object1KeyDeserializer:
public class Object1KeyDeserializer extends KeyDeserializer{
#Override
public Object1 deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String[] parts = key.split(";");
System.out.println(key);
Elem elem1 = new Elem(parts[1]);
Elem elem2 = new Elem(parts[2]);
Object1 obj = new Object1(elem1,elem2);
return obj;
}
}
Nonetheless, the keyDeserializer never seems to be called, can you explain me the reason. I'm quite new to JSON and would be glad if answers could be detailed.
Instead of using toString() you can create your own serialization format. If you have non primitive key in Map then you can serialize Map as
[
{
"key": <serialized key>,
"value: <serialized value>
},
....
]
In this case your Serializer and Deserializer will be following:
public class CustomSerializer extends StdSerializer<Map<Object1, Object2>> {
protected CustomSerializer() {
super(Map.class, true);
}
#Override
public void serialize(Map<Object1, Object2> map,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException{
jsonGenerator.writeStartArray();
for (Map.Entry<Object1,Object2> element: map.entrySet()) {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField("key", element.getKey());
jsonGenerator.writeObjectField("value", element.getValue());
jsonGenerator.writeEndObject();
}
jsonGenerator.writeEndArray();
}
}
and
public class CustomDeserializer extends StdDeserializer<Map<Object1, Object2>> {
protected CustomDeserializer() {
super(Map.class);
}
#Override
public Map<Object1, Object2> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
Map<Object1, Object2> result = new HashMap<>();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
for (JsonNode element : node) {
result.put(
jsonParser.getCodec().treeToValue(element.get("key"), Object1.class),
jsonParser.getCodec().treeToValue(element.get("value"), Object2.class)
);
}
return result;
}
}
So you can create class with your field and another Map (for checking that maps with different types works as usual):
public class MapWrapper {
#JsonSerialize(using = CustomSerializer.class)
#JsonDeserialize(using = CustomDeserializer.class)
private Map<Object1, Object2> map = new HashMap<>();
private Map<String, String> someMap = new HashMap<>();
// default constructor, getters, setters
}
Serialized value can be following:
{
"map": [
{
"key": {
"elem1": "qqq",
"elem2": "rrr"
},
"value": {
"fieldFromValue": "xxx"
}
},
{
"key": {
"elem1": "qqq_two",
"elem2": "rrr_two"
},
"value": {
"fieldFromValue": "yyy"
}
}
],
"someMap": {
"key1": "value1"
}
}

jackson serialization for Java object with Map?

I have a Java class like this and want to convert to JSON using Jackson. Thanks for your help.
Java Class
public class myClass {
String Id;
Map<String, Object> optionalData = new LinkedHashMap<String, Object>();
}
How to serialization it to JSON using Jackson ObjectMapper ?
For example, suppose the optionalData is a Map saving two entries <"type", "book"> and <"year", "2014">
I want the output to be as follow. Please note that the key/value of optionalData could be changed on the fly (so, I cannot create a "static" Java object for this without using Map)
[
{
id: "book-id1",
type: "book",
year: "2014"
},
{
id: "book-id2",
type: "book",
year: "2013"
}
]
You can use the #JsonAnyGetter annotation on a getter method that returns the map of optional values. Please refer to this blog plost that explains that in details.
Here is an example:
public class JacksonAnyGetter {
public static class myClass {
final String Id;
private final Map<String, Object> optionalData = new LinkedHashMap<>();
public myClass(String id, String key1, Object value1, String key2, Object value2) {
Id = id;
optionalData.put(key1, value1);
optionalData.put(key2, value2);
}
public String getid() {
return Id;
}
#JsonAnyGetter
public Map<String, Object> getOptionalData() {
return optionalData;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
List<myClass> objects = Arrays.asList(
new myClass("book-id1", "type", "book", "year", 2013),
new myClass("book-id2", "type", "book", "year", 2014)
);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(objects));
}
}
Output:
[ {
"id" : "book-id1",
"type" : "book",
"year" : 2013
}, {
"id" : "book-id2",
"type" : "book",
"year" : 2014
} ]
You need to write your own Jackson JsonSerializer to create custom JSON string from Java object as per the need.
Here are the nice posts along with example
JSON Serializer & DeserializerHow do I use a custom Serializer with Jackson?
How Do I Write a Jackson JSON Serializer & Deserializer?
The same thing you can achieve using GSON JsonSerializer
Here are some examples
Serialize java object with GSON
GSON Serialiser Example - javacreed
Here is the code using GSON Serialiser
List<MyClass> list = new ArrayList<MyClass>();
MyClass myClass1 = new MyClass();
myClass1.setId("book-id1");
myClass1.getOptionalData().put("type", "book");
myClass1.getOptionalData().put("year", "2014");
list.add(myClass1);
MyClass myClass2 = new MyClass();
myClass2.setId("book-id2");
myClass2.getOptionalData().put("type", "book");
myClass2.getOptionalData().put("year", "2013");
list.add(myClass2);
class MyClassSerialiser implements JsonSerializer<MyClass> {
#Override
public JsonElement serialize(final MyClass obj, final Type typeOfSrc,
final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", obj.getId());
Map<String, String> optioanlData = obj.getOptionalData();
if (optioanlData.size() > 0) {
for (Map.Entry<String, String> entry : optioanlData.entrySet()) {
jsonObject.addProperty(entry.getKey(), entry.getValue());
}
}
return jsonObject;
}
}
String jsonString = new GsonBuilder().setPrettyPrinting().
.registerTypeAdapter(MyClass.class, new MyClassSerialiser()).create()
.toJson(list);
System.out.println(jsonString);
output:
[
{
"id": "book-id1",
"type": "book",
"year": "2014"
},
{
"id": "book-id2",
"type": "book",
"year": "2013"
}
]

NullPointerException : JSON Parsing in JAVA using GSON

I want to parse a JSON File through java using the Api GSON to get the last fields of the JSON file :
descriptor.json :
{
"Teleservice_1" : {
"Record_1" : {
"method_name" : "mehdi",
"method_params": ["param1",2,"param3"]
},
"Record_2" : {
"method_name" : "mkyong",
"method_params": [3,"param2"]
},
"Record_3" : {
"method_name" : "amine",
"method_params": [3,"param1","param2"]
}
},
"Teleservice_2" : {
"Record_11" : {
"method_name" : "mehdi1",
"method_params": ["param11",22,"param33"]
},
"Record_22" : {
"method_name" : "mkyong1",
"method_params": [33,"param22"]
},
"Record_33" : {
"method_name" : "amine1",
"method_params": [33,"param11","param22"]
}
},
"Teleservice_3" : {
"Record_111" : {
"method_name" : "mehdi2",
"method_params": ["param111",222,"param333"]
},
"Record_222" : {
"method_name" : "mkyong2",
"method_params": [333,"param222"]
},
"Record_333" : {
"method_name" : "amine2",
"method_params": [333,"param111","param222"]
}
}
}
ListTeleServices.java :
import java.util.HashMap;
public class ListTeleServices {
private HashMap<String, TeleService> listTeleServices;
public ListTeleServices() {
}
public TeleService getTeleService(String teleserviceName) {
if(this.listTeleServices.get(teleserviceName) != null)
return this.listTeleServices.get(teleserviceName);
else
return null;
}
}
TeleService.java :
import java.util.HashMap;
public class TeleService {
private HashMap<String, Record> listRecords;
public TeleService() {
}
public Record getRecord(String recordName) {
if(this.listRecords.get(recordName) != null)
return this.listRecords.get(recordName);
else
return null;
}
}
Record.java :
public class Record {
private String method_name;
private Object[] method_parameters;
public Record(String methodName, Object[] methodParameters) {
this.method_name = new String(methodName);
this.method_parameters = methodParameters;
}
public String getMethodName() {
return this.method_name;
}
public Object[] getMethodParameters() {
return this.method_parameters;
}
public void setMethodName(String methodName) {
this.method_name = methodName;
}
public void setMethodParameters(Object[] methodParameters) {
this.method_parameters = methodParameters;
}
}
And finally my parser class, JSONMainParse.java :
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import com.google.gson.Gson;
public class JSONMainParse {
public static void main(String[] args) throws FileNotFoundException {
BufferedReader br = new BufferedReader(new FileReader("/Users/Mehdi/Desktop/descriptor.json"));
Gson gson = new Gson();
ListTeleServices teleservices = gson.fromJson(br, ListTeleServices.class);
String methodName = teleservices.getTeleService("Teleservice_2").getRecord("Record_33").getMethodName();
System.out.println(methodName);
}
}
It seems correct to me, and it should display : "amine1" but it gives me a nullPointerException at :
ListTeleServices.getTeleService(ListTeleServices.java:12) which is :
if(this.listTeleServices.get(teleserviceName) != null)
and at JSONMainParse.main(JSONMainParse.java:15) which is :
String methodName = teleservices.getTeleService("Teleservice_2").getRecord("Record_33").getMethodName();
Do you have any idea about this ? Thank you :)
SOLUTION:
You are using more classes than necessary to parse the JSON response! You can delete your classes ListTeleServices and TeleService and keep only your Record class.
Gson gson = new Gson();
Type mapOfMapsType = new TypeToken<Map<String, Map<String, Record>>>() {}.getType();
Map<String, Map<String, Record>> map = gson.fromJson(br, mapOfMapsType);
Finally, in order to get the method name, you have to use:
String methodName = map.get("Teleservice_2").get("Record_33").getMethodName();
EXPLANATION:
When you use your class ListTeleServices to parse the JSON here:
ListTeleServices teleservices = gson.fromJson(br, ListTeleServices.class);
What Gson does is to analise the class ListTeleServices and compare it with the JSON response, so it says:
You passed a class ListTeleServices.class, and the JSON response starts with an object {}... so far everything is OK!
Then it continues parse the JSON, and:
In the class ListTeleServices it finds an attribute listTeleServices which is some object (doesn't mind the type for the moment).
However, in the JSON response it finds three elements "Teleservice_1", "Teleservice_2" and "Teleservice_3", but none of them has the same name listTeleServices, so Gson skip all these values and assigns null to the attribute listTeleServices...
Remember that Gson needs the names in the JSON response to be the same that those in the class you are using to parse the response.
On the other hand, if you use directly a Map<String, Map<String, Record>>, Gson see:
You passed the type of Map<String, Map<String, Record>>, and the JSON response starts with an object {}... so far everything is OK! (Remember a Map is just an object)
Then it continues parse the JSON, and:
In Map<String, Map<String, Record>> it see that there must be some pairs of key (string) and value (some object).
And in the JSON response it finds exactly that, some pairs of strings "Teleservice_1", "Teleservice_2" and "Teleservice_3", and some objects {}, so it can keep parsing happily...
P.S: To go further, note that you could have in your class ListTeleServices these attributes:
private HashMap<String, Record> Teleservice_1;
private HashMap<String, Record> Teleservice_2;
private HashMap<String, Record> Teleservice_3;
And it would work well, but this way you can't have an arbitrary number of teleservice ojects...
And by the way, I've also realised other error: in your Response class, the attribute name method_parameters doesn't match the name of the field in the JSON response, which is method_params. You can either change the attribute name or use the annotation:
#SerializedName("method_params")
private Object[] method_parameters;
change this:
private HashMap<String, TeleService> listTeleServices;
to this
private HashMap<String, TeleService> listTeleServices = new HashMap<String,TeleService>();
Initialize the
private HashMap<String, TeleService> listTeleServices;
as
private HashMap<String, TeleService> listTeleServices = new HashMap<>();
In your current code , you are trying to invoke get() on the null object reference, which throws NullPointerException , as you are trying to invoke instance method on a null object.

Categories

Resources