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.
Related
Right now I am using the JSON path function to get the JSON response values like placeid, profileid and then am declaring that as static, because I need to use those variables in clickProfile() and clickPlace() functions. Going forward I will have a lot of values like placeid, profileid, contentid etc and I want to avoid those declaring as static String at the beginning. Instead I want to use the Maps concept here going forward and want to use it from framework Utility.
I am new to HashMaps. Can someone help me here in implementing the Maps concept here?
public static String placeid;
public static String profileid;
public void extractplaceId()
{
placeid = getJsonPath("results.place_id");
System.out.println(placeid);
}
public void extractpleId()
{
profileid = getJsonPath("results.profile_id");
System.out.println(profileid);
}
public Response clickProfile() throws IOException
{
config.setProperty("APIendPoints.properties");
PROFILE_URL = config.getConfig().getProperty("CLICK_PROFILE") + profileid + ".json";
response = requestSpec.when().get(PROFILE_URL);
return response;
}
public Response clickPlace() throws IOException
{
config.setProperty("APIendPoints.properties");
PLACE_URL = config.getConfig().getProperty("CLICK_PLACE") + placeid + ".json";
response = requestSpec.when().get(PLACE_URL);
return response;
}
Framework Utility:
public String getJsonPath(String key)
{
String resp = response.asString();
JsonPath js = new JsonPath(resp);
return js.get(key).toString();
}
I assume that you want something like a pack of all variables that you want to save and retrieve later. My POC below just work correctly for single thread, not sure about multiple threads.
A class to add/get/edit/remove variables. It wraps a Map to do the trick.
public class EnvironmentVariable {
private static final Map<String, Object> variables = new HashMap<>();
public static void add(String key, Object value) {
variables.put(key, value);
}
public static Object get(String key) {
return variables.get(key);
}
public static void edit(String key, Object value) {
variables.put(key, value);
}
public static Object remove(String key) {
return variables.remove(key);
}
}
ExtractUtil class to provide functions extract or extractAndSave
public class ExtractUtils {
public static Object extractFrom(Response res, String query){
return res.jsonPath().get(query);
}
public static void extractAndSave(Response res, String query, String key) {
Object value = res.jsonPath().get(query);
EnvironmentVariable.add(key, value);
}
}
Test client
public class TestClient {
public Response test() {
return given().get("https://auto-test-challenges.herokuapp.com/challenge3restassured");
}
#Test
void name() {
Response res = test();
ExtractUtils.extractAndSave(res, "data.key1.number", "key1_number");
given().log().all().get("https://postman-echo.com/get?a=" + EnvironmentVariable.get("key1_number"));
}
#Test
void name2() {
Response res = test();
int key2_number = (Integer) ExtractUtils.extractFrom(res, "data.key2.number");
EnvironmentVariable.add("key2_number", key2_number);
given().log().all().get("https://postman-echo.com/get?a=" + EnvironmentVariable.get("key2_number"));
}
}
Because I want my map can save any object, so that I use Map<String,Object>. It requires casting to get value from the map, like int key2_number = (Integer) ExtractUtils.extractFrom(res, "data.key2.number");
I have a problem with parsing my custom response because the I have a response with Localization properties.
I am recieving a response that looks something like this:
[
{
"id": "dummyID1",
"name.en_US": "dummyNameEn1",
"name.fi_FI": "dummyNameFi1"
},
{
"id": "dummyID2",
"name.en_US": "dummyNameEn2",
"name.fi_FI": "dummyNameFi2"
},
{
"id": "dummyID3",
"name.en_US": "dummyNameEn3",
"name.fi_FI": "dummyNameFi3"
}...
]
And to parse that I have created a custom class Device.java:
public class Device {
public String id;
public LocalizedString name;
public Device(String id, LocalizedString name) {
this.id = id;
this.name = name;
}
//Getters and setters
}
Now here we have a custom object named LocalizedString.java:
public class LocalizedString implements Parcelable {
public static final Creator<LocalizedString> CREATOR = new Creator<LocalizedString>() {
#Override
public LocalizedString createFromParcel(Parcel in) {
return new LocalizedString(in);
}
#Override
public LocalizedString[] newArray(int size) {
return new LocalizedString[size];
}
};
private String en_US;
private String fi_FI;
public LocalizedString(String en, String fi) {
this.en_US = en;
this.fi_FI = fi;
}
protected LocalizedString(Parcel in) {
en_US = in.readString();
fi_FI = in.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(en_US);
dest.writeString(fi_FI);
}
//Getters, setters
}
Now in my response I want to create a list of Device's but it does not seem to understand how the ´LocalizedString´ works. Since my request is returning a <List<Device>> I cannot really customly parse it either.
Here is how I try to parse it:
Call<List<Device>> call = getMainActivity().getRestClient().getDevices();
call.enqueue(new Callback<List<Device>>() {
#Override
public void onResponse(Call<List<Device>> call, Response<List<Device>> response) {
if (isAttached()) {
if (response.isSuccessful()) {
// get data
List<Device> items = response.body();
}
}
}
#Override
public void onFailure(Call<List<Device>> call, Throwable t) {
if (isAttached()) {
Logger.debug(getClass().getName(), "Could not fetch installation document devices past orders", t);
getMainActivity().showError(R.string.error_network);
}
}
});
And:
#GET("document/devices")
Call<List<Device>> gettDevices();
What am I supposed to do in this situation to bind the name to the Device and later be able to either get en_US or fi_FI.
Better you can write it like this
public class Device {
#SerializedName("id")
public String id;
#SerializedName("name.en_US")
public String en;
#SerializedName("name.fi_FI")
public String fi;
public Device(String id, String english, String fi) {
this.id = id;
this.en = english;
this.fi = fi;
}
//Getters and setters
}
If you can control the source of the JSON, then a modification of that JSON structure is easy to solve your problem.
If you can not, the one way we can use to solve your problem is to use Jackson and custom deserializer:
public class DeviceDeserializer extends StdDeserializer<Device> {
public DeviceDeserializer() {
this(null);
}
public DeviceDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Device deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String id = getStringValue(node, "id");
String en = getStringValue(node, "name.en_EN");
String fi = getStringValue(node, "name.fi_FI");
LocalizedString localized = new LocalizedString(en, fi);
return new Device(id, localizedString);
}
private String getStringValue(JsonNode node, String key) {
// Throws exception or use null is up to you to decide
return Optional.ofNullable(node.get("id"))
.map(JsonNode::asText)
.orElse(null);
}
}
Manually register the deserializer yourself or using the annotation:
#JsonDeserialize(using = DeviceDeserializer.class)
public class Device {
...
Note that you must enable retrofit jackson converter plugin: (see the Retrofit Configuration part)
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(JacksonConverterFactory.create())
.build();
Read this: Get nested JSON object with GSON using retrofit
I'm trying to deserialize a JSON which containes a String and a list of objects in my Spring web application.
JSON
[
{
"jsonrpc":"2.0",
"result":[
{
"event":{
"id":"27809810",
"name":"Spezia v Trapani",
"countryCode":"IT",
"timezone":"Europe/London",
"openDate":"2016-05-28T16:30:00.000Z"
},
"marketCount":13
},
{
"event":{
"id":"27811083",
"name":"Torino U19 v Atalanta U19",
"countryCode":"IT",
"timezone":"Europe/London",
"openDate":"2016-05-29T16:15:00.000Z"
},
"marketCount":18
},
...
]
My classes are:
ListEventsResponse class
public class ListEventsResponse {
private String jsonrpc;
private List<ListEventsResult> result;
public ListEventsResponse() { }
public String getJsonrpc() {
return jsonrpc;
}
public void setJsonrpc(String jsonrpc) {
this.jsonrpc = jsonrpc;
}
public List<ListEventsResult> getResult() {
return result;
}
public void setResult(List<ListEventsResult> result) {
this.result = result;
}
}
ListEventsResult class
public class ListEventsResult {
private Event event;
private int marketCount;
public ListEventsResult() { }
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public int getMarketCount() {
return marketCount;
}
public void setMarketCount(int marketCount) {
this.marketCount = marketCount;
}
}
I have also Event class, composed by 5 String (id, name, etc.).
Controller
[...]
ListEventsResponse listEvents = new Gson().fromJson(response.toString(), ListEventsResponse.class);
List<ListEventsResult> eventsList = listEvents.getResult();
return new ModelAndView("page", "eventsList", eventsList);
My .jsp page
[...]
<c:forEach items="${eventsList}" var="listEventsResult">
Match: <c:out value="${listEventsResult.name}"/>
</c:forEach>
[...]
My code runs and doesn't give any error, but no match is shown on my page, in fact listEvents doesn't contains any object.
I can't understand how to deserialize properly the list of objects, so my question is: which logic is behind the deserialization of a json which contains a list of objects?
I post my code just to explain better my problem.
As you have a Json Array as response , you need to deserialize like below
Gson gson = new Gson();
Type type = new TypeToken<List<ListEventsResponse>>(){}.getType();
List<ListEventsResponse> events = (List<ListEventsResponse>) gson.fromJson(response.toString(), type);
I have a JSON that looks like this:
{
"results": {
"exchange": [
"site.com",
{
"currency": "usd",
"last_traded": "2015.24"
}
]
}
}
How do I get the last_traded value?
I wrote some POJO for this, but I can't seem to find a way to get the key-value inside exchange array.
public class ExchangeContainer {
#Expose
private Results results;
public Results getResults() {
return results;
}
public void setResults(Results results) {
this.results = results;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
public class Results {
#Expose
private List<String> exchange = new ArrayList<String>();
public List<String> getExchange() {
return exchange;
}
public void setExchange(List<String> exchange) {
this.exchange = exchange;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
I'm using GSON to deserialize this JSON.
So in order for me to traverse through the model is:
ExchangeContainer response;
String rate = response.getResults().getExchange().get(1); // how to continue?
and I'm stuck.
Implement interface using implements JsonDeserializer in your class
and override deserialize method.
Example link - http://www.javacreed.com/gson-deserialiser-example/
As you have created list of Strings by
private List<String> exchange = new ArrayList<String>();
& setting another list in created list i.e., in exchange
public void setExchange(List<String> exchange)
When you get value by invoking line
String rate = response.getResults().getExchange().get(1);
it contains "last_traded": "2015.24" is it right ?
Now to get 2015.24, you have following choices :-
String[] split(":")
String substring(int beginIndex)
I hope this will solve your problem.
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.