Parsing JSON Map of Maps with GSON - java

I've tried everything but the solution is so ugly, I really want a straight forward answer on if this can be improved (and that means if I need to use a different implementation).
The problem lies in Map of Maps with GSON:
Gives me this response according to Firebug:
{"id": 2, "result": {"FirstWorld": {"FirstValue": 5, ... }, "SecondWorld":{"FirstValue": 5, ....}}, "error":null }
There are around 200 "Values", but only two "Worlds". This is what I have so far to parse it in my ControlService class:
public void RegisterValues( String [] Names, AsyncCallback<Map<String,RegisterValues>> callback);
public class RegisterValues
{
int FirstValue;
int SecondValue;
... And so on 200 times !!!
So I access the data like so:
service_.RegisterValues( Names, new AsyncCallback<ControlService.RegisterValues>()
{
public void onSuccess( GlibControlService.RegisterValues result )
{
String test = "FirstValue";
String message="Result for channel 1 is ";
for( String Name : result.keySet() ) message+=Name+"="+result.get(Name);
But as you can see, this is going to be really long. The other problem is some of the "Values" have ampersands in them, which means I can't use them in this method e.g;
#SerializedName("One&Two") // Ampersand not allowed in name
int OneTwo; //gives Invalid JSON response apparently
Does anyone have a better method of doing this?
EDIT Code that works:
private ControlService service_;
service_.connectedNames( new String[0], new AsyncCallback<String[]>() {
public void onSuccess( String[] result)
{
List_.removeItem(0);
Names = result;
for( String Name : result ) {
List_.addItem(Name);
}
List_.setEnabled( true );
}
public void onFailure(Throwable why)
{
List_.removeItem(0);
List_.addItem( "Server error!" );
}
});
Then in my ControlService Class, I have this:
#RpcImpl(version=RpcImpl.Version.V2_0,transport=RpcImpl.Transport.HTTP_POST)
public interface ControlService extends RemoteJsonService
{
public void connectedNames( String [] Names, AsyncCallback<String[]> callback );
This works perfectly.
I tried doing it a very similar way by adding this in my ControlService:
public void RegisterValues( String [] Names, AsyncCallback<Map< String,Map<String, Integer>> callback);
And so on, making sure the Map<String, Map<String, Integer>> was also in the onSucess() part of the structure too. This however caused my program to crash. It seemed to me that it doesn’t like nested maps. However GSON will automatically parse in objects if the member name matches the JSON field. So I used that RegisterValues to automatically force GSON to parse this.
Stack trace:
[ERROR] Errors in 'generated://AF9BA58B045D92E7896CD657C9CC5FAF/example/client/ControlService_JsonProxy.java'
[ERROR] Line 18: INSTANCE cannot be resolved or is not a field
See snapshot: /var/folders/pf/56b3mznn35gg741rlsqq424m0000gp/T/example.client.ControlService_JsonProxy2948240672238331252.java
This is why I think GSON can't automatically parse Nested Maps using AsyncCallback. It may be better to do my HTTP call more in line with what you suggested below.

The best way is to wrap the response you posted into one class.
{"id": 2, "result": {"FirstWorld": {"FirstValue": 5, ... }, "SecondWorld":{"FirstValue": 5, ....}}, "error":null }
import java.util.Map;
public class Response {
private int id;
private Map<String, Map<String, Integer>> result;
private String error;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Map<String, Map<String, Integer>> getResult() {
return result;
}
public void setResult(Map<String, Map<String, Integer>> result) {
this.result = result;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
I've tested with this and it runs fine.
static String json = "{\"id\":2,\"result\":{\"FirstWorld\":{\"FirstValue\":5,\"SecondValue\":6},\"SecondWorld\":{\"FirstValue\":5,\"SecondValue\":6}},\"error\":null}";
public static void main(String[] args) {
Gson g= new Gson();
Response b=g.fromJson(json, Response.class );
System.out.println(g.toJson(b));
}
If you need to have the RegisterValues class declared,
you only need to replace the Map<String, Integer> of result with this class, probably extending Map.

Related

Expected BEGIN_ARRAY but was STRING at line 1 column 2 path $

I have an API, that for getting data I must send a Parameter of JSON. Here is the JSON that must need to be sent in the #Body to get the data.
{
"ViewName": "Members_HousholdAdmin",
"DataRequest":{
"filter":[{
"field":"",
"logic":"",
"operator":"",
"value":""
}],
"offset":0,"take":0,
"Sort":[{"field":"","dir":""}]
},
"parameters":[
{"key": "%FundId","value": "1" }
]
}
It works in postman, but in Android, I have the following error:
Expected BEGIN_ARRAY but was STRING at line 1 column 2 path $
I guess this is a problem in my models, but I can not fix it.
My Models :
public class SendParametersGetData {
#SerializedName("ViewName")
public String ViewName;
#SerializedName("DataRequest")
public DataRequest DataRequest = null;
#SerializedName("Parameters")
public List<Parameters> parameters = null;
}
public class DataRequest {
#SerializedName("take")
public int take=0;
#SerializedName("offset")
public int offset=0;
#SerializedName("filter")
public ArrayList<Filter> filter;
#SerializedName("Sort")
public ArrayList<Sort> Sort;
}
public class Parameters {
#SerializedName("value")
public String value;
#SerializedName("key")
public String key;
public Parameters(String key, String value) {
this.value = value;
this.key = key;
}
}
There is 2 more models for sort and filter. I use retrofit & RXJava.
#POST("test.php")
Single<ArrayList<Members>> getMembers(#Body SendParametersGetData sendParametersGetData);
I set SendParametersGetData with the constructor function (in this example only ViewName and Parameters are set) and I give as input to the getMembers method.
ArrayList<Parameters> parameters = new ArrayList<>();
parameters.add(new Parameters("%FundId",String.valueOf(fund.getFundId())));
SendParametersGetData sendParametersGetData = new SendParametersGetData("Members_HousholdAdmin",parameters);
dataSource.getMembers(sendParametersGetData).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<ArrayList<Members>>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onSuccess(ArrayList<Members> members) {
}
#Override
public void onError(Throwable e) {
view.hideProgress();
TastyToast.makeText(view.getContext(), e.toString(), 6000, TastyToast.ERROR);
}
});
I found the following error in your model class.
The Parameters needs to be renamed as parameters to match with the JSON key.
#SerializedName("parameters")
public List<Parameters> parameters = null;
This should solve your problem I guess. I think the other model classes are fine.

UnrecognizedPropertyException not making sense to me

I have a JSON map that contains a simple data type (String) and a complex data type. I have created a struct to handle this. Here it is:
public static class PayloadData {
String authkey;
PizzaPlaceTo pizzaPlace;
Map<String, List<String>> updates;
}
Now, I read the data in like this:
PayloadData payloadData = objectMapper.readValue(payload, PayloadData.class);
and it's giving me:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "authkey" (class pizzainthecloud.pizzaplace.controller.PizzaPlaceController$PayloadData), not marked as ignorable (0 known properties: ])
at [Source: {"authkey":"Don't Panic!","pizzaPlace":{"contactName":null,"customerId":null}; line: 1, column: 13] (through reference chain: pizzainthecloud.pizzaplace.controller.PizzaPlaceController$PayloadData["authkey"])
So if I'm reading this correctly, it's telling me there's no authkey in my receiving class to receive the authkey in the JSON data, right? Only there is.
Help, please.
So, it looks like that Jackson only works with beans and not with struct style classes. I changed my class to:
public static class PayloadData {
private String authkey;
private PizzaPlaceTo pizzaPlace;
private Map<String, List<String>> updates;
public String getAuthkey() {
return authkey;
}
public void setAuthkey(String authkey) {
this.authkey = authkey;
}
public PizzaPlaceTo getPizzaPlace() {
return pizzaPlace;
}
public void setPizzaPlace(PizzaPlaceTo pizzaPlace) {
this.pizzaPlace = pizzaPlace;
}
public Map<String, List<String>> getUpdates() {
return updates;
}
public void setUpdates(Map<String, List<String>> updates) {
this.updates = updates;
}
}
And that resolved the problem.

deserialize Json into POJO

I am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON

Parse json to necessary object

In my Android app I have json, which looks like :
{
"Records": [
{
"RowIndex": "0",
"NameValue": {
"Name": "PropertyName1",
"Value": "PropertyValue1"
}
}{
"RowIndex": "1",
"NameValue": {
"Name": "PropertyName2",
"Value": "PropertyValue2"
}
}
]
}
I need to parce this json to object, which looks like:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
And result after parsing should be:
public String PropertyName1 = "PropertyValue1";
public String PropertyName2 = "PropertyValue2";
Basically, the first json is equivalent of:
{
"PropertyName1" : "PropertyValue1",
"PropertyName2" : "PropertyValue2"
}
Question: How can I parce first json without usage swith/case to search for the necessary Property?
You'll have to go down the dark path of reflection I'm afraid.
you can parse the json into an intermediary object which has a map for namevalue.
then you use the below code (ofcourse just copy paste the bits you need) to loop over the map of key/value pairs. for each key look up the field you want, and set it. If you're guaranteed only to need to set public variables then you can use getFields and can skip the setAccessible.
public class Test {
public static void main(String[] argv) {
MyClass myClass = new MyClass();
Class<?> classObject = myClass.getClass();
// Field fields[] = classObject.getFields(); // if you want to get only public fields.
Field fields[] = classObject.getDeclaredFields(); // any field
for(Field f : fields) {
System.out.println(f.getName());
try {
// if member is private: security managers may object but the default java allows it
f.setAccessible(true);
f.set(myClass, "abc");
} catch (IllegalAccessException e) {
// handle access exception:
e.printStackTrace();
}
}
System.out.println("prop 1: " + myClass.PropertyName1);
System.out.println("prop 2: " + myClass.PropertyName2);
}
public static class MyClass {
public String PropertyName1;
private String PropertyName2;
}
}
Actually.. there is a non-reflect way but that will replace your implementation of the object you have.
If you change your class:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
to
public class MyClass {
private Map<String, String> properties = new HashMap<String, String>();
public void setProperties(Map<String, String> props) { this.properties = props; }
public String getPropertyName1() {
return lookupProperty("PropertyName1");
}
public String getPropertyName2() {
return lookupProperty("PropertyName2");
}
private String lookupProperty(String property) {
if (properties.containsKey(property) {
return properties.get(property);
} else {
return null;
}
}
}
then you could parse the name value map into a map, and construct a myclass with it.
just listing it for completeness, though changing your domain model to fit a json input is not ideal.
I would recommend either way to do the input parsing, and then copy over the model into your actual domain object rather than using the json-model in your application. that way if the json model ever changes, your domain model will not change.
One method I can think of (which doesn't sound too great) is to actually make an object that matches the JSON response you get back. Then, map THAT NameValue object to MyClass
So, something like
public class NameValue {
public string Name;
public String Value;
public MyClass getMyClass(){
MyClass myClass = new MyClass();
myClass.PropertyName2 = Value;
return myClass;
}
}
You can come up with a better way to map it, obviously. But this is just an example of something I might do if I was given a response JSON I didn't particularly care for. You can similarly reverse it (have MyClass be able to create a NameValue object) so you can send data back in the correct format.

Json Data assign to java class

I'm having problem to assign json data into java class.Please do help anyone,
My java class is like,
public class ListofGridRecords<T> {
public int Totalrecords;
public List<T> GridRecords;//using TraderTransaction class.
}
and TraderTransaction class is,
public class TraderTransaction {
public Date AddedTime;
public String TransactId;
public TransactStatus Status;
public String OtherPartyAccountNo;
public Double AmountPaid;
public Double AmountRecieved;
public Double ClosingBalance;
public TransactionTypes TransType;
public String Narration;
public TraderTransaction() {
super();
}
}
and my json conversion function look like,
JsonObject returndata = JsonObject.parse(responseString);
String operationresult = returndata.get("OperationResult").toString();
if (Result.values()[Integer.parseInt(operationresult)] == Result.Success) {
Gson gson = new Gson();
#SuppressWarnings("unchecked")
ListofGridRecords<TraderTransaction> traderlist =
gson.fromJson(returndata.get("ResultData").toString(), ListofGridRecords.class);
Log.i("LIST DATA:", "" + traderlist);
for (TraderTransaction trader: traderlist.GridRecords) {
HashMap<String, String> map = new HashMap<String, String>();
map.put(TRANS_FIRST_COLUMN, currentformatter.format(trader.AddedTime));
map.put(TRANS_SECOND_COLUMN, trader.TransactId);
map.put(TRANS_THIRD_COLUMN, trader.OtherPartyAccountNo);
map.put(TRANS_FOURTH_COLUMN, trader.AmountPaid.toString());
map.put(TRANS_FIFTH_COLUMN, trader.AmountRecieved.toString());
map.put(TRANS_SIXTH_COLUMN, OpenOrClosed.values()[Integer.parseInt(trader.TransType.toString())].toString());
list.add(map);
}
}
I'm getting conversion error at for (TraderTransaction trader : traderlist.GridRecords).
My Json data look like,
{
"Messages":"RESULTS_RETRIEVAL_SUCCESSFULL",
"OperationResult":0,
"ResultData":{
"GridRecords":[
{
"AddedBy":"Distributor-9787457361-Rathinavel",
"AddedTime":"2013-04-12T16:26:24.0140117",
"AmountPaid":0.0,
"AmountRecieved":10000.0,
"ClosingBalance":10000.0,
"Narration":null,
"OtherPartyAccountNo":"0102849015327675",
"Status":2,
"TransType":2,
"TransactId":"TDRF483679051236"
},
{
"AddedBy":"Distributor-9787457361-Rathinavel",
"AddedTime":"2013-04-12T16:20:54.8681857",
"AmountPaid":0.0,
"AmountRecieved":0.0,
"ClosingBalance":0.0,
"Narration":null,
"OtherPartyAccountNo":"0102849015327675",
"Status":0,
"TransType":2,
"TransactId":"TDRF706925413802"
}
],
"Totalrecords":2
},
"UpdateAvailable":"0"
}
In order to parse your JSON, I'd use a slightly different strategy. As you seem to be interested in parsing only the "ResultData", I'd create classes to wrap the response, very similar to those you have already created, namely:
public class Response {
#SerializedName("ResultData")
public ResultData resultData;
}
and,
public class ResultData {
#SerializedName("GridRecords")
public List<GridRecord> gridRecords;
#SerializedName("Totalrecords")
public int totalrecords;
}
and,
public class GridRecord {
#SerializedName("AddedTime")
public String addedTime;
#SerializedName("TransactId")
public String transactId;
//other fields...
}
and other classes if necessary...
Then, in order to parse your JSON reponse, you just have to do:
Gson gson = new Gson();
Response data = gson.fromJson(responseString, Response.class);
and you'll be able to access any field, for example:
data.resultData.gridRecords.transactId;
Note 1: If you are interested in more fields of the JSON response, you just have to add more fields to your wrap classes, according to the JSON response...
Note 2: I've changed the type of addedTime to String, instead of Date because it throws an exception for unparseable date. Anyway I usually leave the types in the Response objects as simple String and then in the class from where I retrieve the response, I do the correct formatting while creating my objects, for example, when you put the values in your Map...
Note 3: The use of the annotation #SerializedName is interesting to separate the name of a field in the JSON response and in your app, in order to follow Java naming conventions, which your attributes are not following...
Note 4: You shouldn't use public attributes in your classes. It's more recommendable to use private/protected attributes and their correspondent getters and setters...

Categories

Resources