Gson parse response to get generic class - java

I've a response class
public class ResponseModel<T> {
private boolean isRequestSuccessful;
public boolean getIsRequestSuccessful() {
return this.isRequestSuccessful;
}
public void setIsRequestSuccessful(boolean isRequestSuccessful) {
this.isRequestSuccessful = isRequestSuccessful;
}
private String message;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
private T object;
public T getObject() {
return this.object;
}
public void setObject(T object) {
this.object = object;
}
}
My API will return type T. I would like to parse the response from the API and create a object of type ResponseModel.
I am trying to achieve something like below which I can do it easily with c#. Please help on how to do this with Java
public static ResponseModel<T> Get(String requestUri) throws ClientProtocolException,IOException {
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet(requestUri);
httpGet.addHeader("TenantKey", TenantKey);
httpGet.addHeader("accept", "application/json");
HttpResponse response = client.execute(httpGet);
ResponseModel<T> responseModel = new ResponseModel<T>();
if (response.getStatusLine().getStatusCode() == 200) {
Gson gson = new GsonBuilder().create();
// parse the response as T and and assign to object of ResponseModel
responseModel.object = ...
}
else
{
responseModel.message = response.getEntity().getContent();
}
// return ResponseModel here
}

Generics in C# and Java are pretty different. Simply spoken, there is no sense in what you are doing here.
The java generic T you are using there is a compile time feature. It allows you to use more specific types at compile time, instead of using Object all over the place.
Therefore you can't use generics to determine a "T" at "runtime", as you probably intend to. That T in your method comes from the "outside", and the compiler determines that in occasion it should be a ResponseModel<Integer> and ResponseModel<Whatever> in another context.
You can't have gson read JSON data to return a specific ResponseModel<Whatever> for you. If at all, you might be able to use TypeAdapter magic that does some switching based on the actual value, to return this or that specific ResponseModel<Foo>.
Beyond that: when using such bean like classes as your ResponseModel, you simply want them to be specific, not generic.

Not sure, but I had a similar requirement, but its for Android though. Here is the reference link, where I had to write a generic class to load the different forms of JSON from Assets folder and parse to POJO class.
https://github.com/gokulnathperiasamy/Android-Helper/blob/master/JSONHelper.java
Code:
private static String convertJSONtoEntity(String jsonString, String typeString) {
String jsonObjectString = null;
try {
JSONObject jsonObject = new JSONObject(jsonString);
jsonObjectString = jsonObject.get(typeString).toString();
} catch (Exception e) {
Log.e(TAG, e.getLocalizedMessage());
}
return jsonObjectString;
}
private static <T> List<T> fromJsonList(String json, Class<T> clazz) {
Object[] array = (Object[]) java.lang.reflect.Array.newInstance(clazz, 0);
array = new Gson().fromJson(json, array.getClass());
List<T> list = new ArrayList<>();
if (array != null && array.length > 0) {
for (Object anArray : array) {
list.add(clazz.cast(anArray));
}
}
return list;
}
Usage:
Invoke convertJSONtoEntity() with your jsonString and typeString will be your root element of your JSON.
Invoke the fromJsonList() with value returned by convertJSONtoEntity() and Class. This gives list of objects from JSON.

With the help of other answers and further searching in Google, I ended up with the following code
public static <T> ResponseModel<T> Get(Class<?> classType, String requestUri) throws ClientProtocolException, IOException
{
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet(requestUri);
httpGet.addHeader("accept", "application/json");
HttpResponse response = client.execute(httpGet);
ResponseModel<T> responseModel = new ResponseModel<T>();
if (response.getStatusLine().getStatusCode() == 200)
{
responseModel.setObject((T) Utils.fromJson(EntityUtils.toString(response.getEntity()), classType));
responseModel.setIsRequestSuccessful(true);
}
else
{
responseModel.setMessage(response.getEntity().getContent().toString());
responseModel.setIsRequestSuccessful(false);
}
return responseModel;
}

Related

ResponseBuilder toString() returns object classes in string, not just the raw response string

I'm presently migrating from the Java ASK-SDK v1 to Java ASK SDK v2.
I'm trying to return a webhook call using the ResponseBuilder class that I built my response up and the data is correct, however when I try to populate the HTTP body with the JSON text, the ResponseBuilder.toString() value doesn't just populate the data with just the string, I get the following:
Optional[class Response {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>Some of the things you can say are What would you like to do?</speak>
}
card: null
reprompt: class Reprompt {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>You can say ..., is that what you want?</speak>
}
}
directives: []
shouldEndSession: false
canFulfillIntent: null
}]
Is there another way to get the string for the body of the response? The BaseSkillResponse has a getResponse() call, however, I cannot figure out how to use the class to generate the String response output.
I was able to get the string with the following in my class:
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
myFunction(){
try{
return toJsonString(responseBuilder.build().get());
} catch(IOException e) {
e.printStackTrace();
}
}
public String toJsonString(Response response)throws IOException {
return OBJECT_MAPPER.writeValueAsString(response);
}
Solve this by doing the following:
public String toJsonString(Response response)throws IOException
{
JacksonSerializer jacksonSerializer = new JacksonSerializer();
constructedResponse = jacksonSerializer.serialize(response);
JSONObject jsonObject = new JSONObject();
jsonObject.put("response",constructedResponse);
}

Retrofit2 Handle condition when status code 200 but json structure different than datamodel class

I'm using Retrofit2 and RxJava2CallAdapterFactory.
The API I consume returns status code always as 200 and for success and response JSON string the JSON structure is entirely different. Since the status code is always 200 the onResponse() method is called always. Hence, I'm not able to extract error msgs from the JSON in the error condition.
Solution 1:
I use ScalarsConverterFactory to get response String and manually use Gson to parse the response .
How to get response as String using retrofit without using GSON or any other library in android
Problem with this solution: I'm planning to use RxJava2CallAdapterFactory for that the retrofit method should return DataModel Class.
I need to find the best solution for this problem, in way I can keep returning the data model classes from Retrofit method & somehow I identify the error condition from response (identify the response JSON does not match the data model) and then parse the error JSON into a data model.
Retrofit Client
public static Retrofit getClient(String url) {
if (apiClient == null) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor).build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
Method
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(UiUtils.getContext(), SPConstants.BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
Call<LoginBean> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginBean>() {
#Override
public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
if (response.body().isObjectNull()) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, null);
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body());
}
#Override
public void onFailure(Call<LoginBean> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}
Interface
#GET("App/login")
Call<LoginBean> getLoginResponse(#QueryMap Map<String, String> queryMap);
PS :
The API cannot change for now, as some other applications are consuming it.
Gson parser does not return a null object instance for me to understand that there is json structure and datamodel mismatch.
RestAdapter is deprecated in Retrofit 2
I'm looking for the best approach to resolve this , preferably avoid manually json parsing and take most advantage of retrofit and RX adapters.
EDIT
Response code 200 hence
response.isSuccessful() == true
response.body() != null is also true as Gson never creates a null instance or throws any exception if there is mismatch of JSON structure
response.errorBody() == null at all times as response sent as input stream from the server.
if (response.isSuccessful() && response.body() != null) {
//control always here as status code 200 for error condition also
}else if(response.errorBody()!=null){
//control never reaches here
}
EDIT 2
SOLUTION
The solution is based on anstaendig answer
I have created a base generic class to further this answer.
Since I have multiple apis and data models I have to create deserilizers for each
BASE API BEAN
public class BaseApiBean<T> {
#Nullable
private T responseBean;
#Nullable
private ErrorBean errorBean;
public BaseApiBean(T responseBean, ErrorBean errorBean) {
this.responseBean = responseBean;
this.errorBean = errorBean;
}
public T getResponseBean() {
return responseBean;
}
public void setResponseBean(T responseBean) {
this.responseBean = responseBean;
}
public ErrorBean getErrorBean() {
return errorBean;
}
public void setErrorBean(ErrorBean errorBean) {
this.errorBean = errorBean;
}
}
BASE DESERIALIZER
public abstract class BaseDeserializer implements JsonDeserializer<BaseApiBean> {
#Override
public BaseApiBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("result")) {
/* {"result":"404"}*/
ErrorBean errorMessage = new Gson().fromJson(jsonObject, ErrorBean.class);
return getResponseBean(errorMessage);
} else {
return getResponseBean(jsonObject);
}
}
public abstract BaseApiBean getResponseBean(ErrorBean errorBean);
public abstract BaseApiBean getResponseBean(JsonObject jsonObject);
}
Custom Deserializer for each API
public class LoginDeserializer extends BaseDeserializer {
#Override
public BaseApiBean getResponseBean(ErrorBean errorBean) {
return new LoginResponse(null, errorBean);
}
#Override
public BaseApiBean getResponseBean(JsonObject jsonObject) {
LoginBean loginBean = (new Gson().fromJson(jsonObject, LoginBean.class));
return new LoginResponse(loginBean, null);
}
}
CUSTOM RESPONSE BEAN
public class LoginResponse extends BaseApiBean<LoginBean> {
public LoginResponse(LoginBean responseBean, ErrorBean errorBean) {
super(responseBean, errorBean);
}
}
CLIENT
public class ApiClient {
private static Retrofit apiClient = null;
private static Retrofit apiClientForFeedBack = null;
private static LoginDeserializer loginDeserializer = new LoginDeserializer();
private static AppVerificationDeserializer appVerificationDeserializer = new AppVerificationDeserializer();
public static Retrofit getClient(String url) {
if (apiClient == null) {
GsonBuilder gsonBuilder=new GsonBuilder();
gsonBuilder.registerTypeAdapter(LoginResponse.class,
loginDeserializer);
gsonBuilder.registerTypeAdapter(AppVerificationResponse.class,
appVerificationDeserializer);
Gson gson= gsonBuilder.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
HANDLE RESPONSE
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(getContext(), SPConstants.MT4_BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
HashMap<String, String> queryParams = new HashMap<>();
queryParams.put(APIConstants.KEY_EMAIL, sourceId + username.toLowerCase());
queryParams.put(APIConstants.KEY_PASSWORD, Utils.encodePwd(password));
Call<LoginResponse> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginResponse>() {
#Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.body().getResponseBean()==null) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, response.body().getErrorBean());
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body().getResponseBean());
}
#Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}
So you have two different successful (status code 200) responses from the same endpoint. One being the actual data model and one being an error (both as a json structure like this?:
Valid LoginBean response:
{
"id": 1234,
"something": "something"
}
Error response
{
"error": "error message"
}
What you can do is have an entity that wraps both cases and use a custom deserializer.
class LoginBeanResponse {
#Nullable private final LoginBean loginBean;
#Nullable private final ErrorMessage errorMessage;
LoginBeanResponse(#Nullable LoginBean loginBean, #Nullable ErrorMessage errorMessage) {
this.loginBean = loginBean;
this.errorMessage = errorMessage;
}
// Add getters and whatever you need
}
A wrapper for the error:
class ErrorMessage {
String errorMessage;
// And whatever else you need
// ...
}
Then you need a JsonDeserializer:
public class LoginBeanResponseDeserializer implements JsonDeserializer<LoginBeanResponse> {
#Override
public LoginBeanResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Based on the structure you check if the data is valid or not
// Example for the above defined structures:
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("error") {
ErrorMessage errorMessage = new Gson().fromJson(jsonObject, ErrorMessage.class);
return new LoginBeanResponse(null, errorMessage)
} else {
LoginBean loginBean = new Gson().fromJson(jsonObject, LoginBean.class):
return new LoginBeanResponse(loginBean, null);
}
}
}
Then add this deserializer to the GsonConverterFactory:
GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(LoginBeanResponse.class, new LoginBeanResponseDeserializer()).create():
apiClient = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder))
.client(httpClient)
.build();
This is the only way I can think of making this work. But as already mentioned this kind of API design is just wrong because status codes are there for a reason. I still hope this helps.
EDIT: What you can then do inside the class where you make the call to that Retrofit (if you already converted from Call<LoginBeanResponse> to Single<LoginBeanResponse> with RxJava) is actually return a proper error. Something like:
Single<LoginBean> getLoginResponse(Map<String, String> queryMap) {
restApi.getLoginResponse(queryMap)
.map(loginBeanResponse -> { if(loginBeanResponse.isError()) {
Single.error(new Throwable(loginBeanResponse.getError().getErrorMessage()))
} else {
Single.just(loginBeanReponse.getLoginBean())
}})
}
You can simply do that by doing this
try
{
String error = response.errorBody().string();
error = error.replace("\"", "");
Toast.makeText(getContext(), error, Toast.LENGTH_LONG).show();
}
catch (IOException e)
{
e.printStackTrace();
}
One possible solution is to make Gson fail on unknown properties. There seems to be an issue raised already(https://github.com/google/gson/issues/188). You can use the workaround provided in the issue page. So the steps are as follows:
Add the workaround ValidatorAdapterFactory to the code base:
public class ValidatorAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// If the type adapter is a reflective type adapter, we want to modify the implementation using reflection. The
// trick is to replace the Map object used to lookup the property name. Instead of returning null if the
// property is not found, we throw a Json exception to terminate the deserialization.
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
// Check if the type adapter is a reflective, cause this solution only work for reflection.
if (delegate instanceof ReflectiveTypeAdapterFactory.Adapter) {
try {
// Get reference to the existing boundFields.
Field f = delegate.getClass().getDeclaredField("boundFields");
f.setAccessible(true);
Map boundFields = (Map) f.get(delegate);
// Then replace it with our implementation throwing exception if the value is null.
boundFields = new LinkedHashMap(boundFields) {
#Override
public Object get(Object key) {
Object value = super.get(key);
if (value == null) {
throw new JsonParseException("invalid property name: " + key);
}
return value;
}
};
// Finally, push our custom map back using reflection.
f.set(delegate, boundFields);
} catch (Exception e) {
// Should never happen if the implementation doesn't change.
throw new IllegalStateException(e);
}
}
return delegate;
}
}
Build a Gson object with this TypeAdaptorFactory:
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ValidatorAdapterFactory()).create()
And then use this gson instance in GsonConverterFactory like below:
apiClient = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson)) //Made change here
.client(httpClient)
.build();
This should throw an error if the unmarshalling step finds an unknown property, in this case the error response structure.
Here is another attempt. General idea: create a custom Converter.Factory based on GsonConverterFactory and a custom Converter<ResponseBody, T> converter based on GsonRequestBodyConverter to parse whole body 2 times: first time as error and second time as actual expected response type. In this way we can parse error in a single place and still preserve friendly external API. This is actually similar to #anstaendig answer but with much less boilerplate: no need for additional wrapper bean class for each response and other similar stuff.
First class ServerError that is a model for your "error JSON" and custom exception ServerErrorException so you can get all the details
public class ServerError
{
// add here actual format of your error JSON
public String errorMsg;
}
public class ServerErrorException extends RuntimeException
{
private final ServerError serverError;
public ServerErrorException(ServerError serverError)
{
super(serverError.errorMsg);
this.serverError = serverError;
}
public ServerError getServerError()
{
return serverError;
}
}
Obviously you should change the ServerError class to match your actual data format.
And here is the main class GsonBodyWithErrorConverterFactory:
public class GsonBodyWithErrorConverterFactory extends Converter.Factory
{
private final Gson gson;
private final GsonConverterFactory delegate;
private final TypeAdapter<ServerError> errorTypeAdapter;
public GsonBodyWithErrorConverterFactory()
{
this.gson = new Gson();
this.delegate = GsonConverterFactory.create(gson);
this.errorTypeAdapter = gson.getAdapter(TypeToken.get(ServerError.class));
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
return new GsonBodyWithErrorConverter<>(gson.getAdapter(TypeToken.get(type)));
}
#Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
{
return delegate.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
#Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit)
{
return delegate.stringConverter(type, annotations, retrofit);
}
class GsonBodyWithErrorConverter<T> implements Converter<ResponseBody, T>
{
private final TypeAdapter<T> adapter;
GsonBodyWithErrorConverter(TypeAdapter<T> adapter)
{
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException
{
// buffer whole response so we can safely read it twice
String contents = value.string();
try
{
// first parse response as an error
ServerError serverError = null;
try
{
JsonReader jsonErrorReader = gson.newJsonReader(new StringReader(contents));
serverError = errorTypeAdapter.read(jsonErrorReader);
}
catch (Exception e)
{
// ignore and try to read as actually required type
}
// checked that error object was parsed and contains some data
if ((serverError != null) && (serverError.errorMsg != null))
throw new ServerErrorException(serverError);
JsonReader jsonReader = gson.newJsonReader(new StringReader(contents));
return adapter.read(jsonReader);
}
finally
{
value.close();
}
}
}
}
The basic idea is that the factory delegates other calls to the standard GsonConverterFactory but intercepts responseBodyConverter to create a custom GsonBodyWithErrorConverter. The GsonBodyWithErrorConverter is doing the main trick:
First it reads whole response as String. This is required to ensure response body is buffered so we can safely re-read it 2 times. If your response actually might contain some binary you should read and buffer the response as binary and unfortunately retrofit2.Utils.buffer is not a public method but you can create a similar one yourself. I just read the body as a String as it should work in simple cases.
Create a jsonErrorReader from the buffered body and try to read the body as a ServerError. If we can do it, we've got an error so throw our custom ServerErrorException. If we can't read it in that format - just ignore exception as it is probably just normal successful response
Actually try to read the buffered body (second time) as the requested type and return it.
Note that if your actual error format is not JSON you still can do all the same stuff. You just need to change the error parsing logic inside GsonBodyWithErrorConverter.convert to anything custom you need.
So now in your code you can use it as following
.addConverterFactory(new GsonBodyWithErrorConverterFactory()) // use custom factory
//.addConverterFactory(GsonConverterFactory.create()) //old, remove
Note: I haven't actually tried this code so there might be bugs but I hope you get the idea.

Passing method generic type to internal call

I am trying to create a method with generics to unmarshal JSON lists into lists containing POJOs. The snippet below compiles and runs, but at runtime I am getting my List<CustomPojo> filled with HashMap instances, as the type T is not passed along to the TypeReference constructor which then falls back to HashMap I guess.
public static <T> List<T> getList(String endpoint) throws IOException {
HttpGet request = new HttpGet(SERVER_ADDRESS + endpoint);
CloseableHttpResponse response = httpClient.execute(request);
try {
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() == 200) {
return mapper.readValue(response.getEntity().getContent(), new TypeReference<List<T>>() { });
}
} finally {
response.close();
}
return null;
}
Am I on the right track or is this something not achievable using generics?
Try to use something like:
mapper.readValue(response.getEntity().getContent(),
mapper.getTypeFactory().contructCollectionType(List.class, cls);
where cls is Class<T> it should works.

How to ask gson to avoid escaping json in a json response?

I have a response object like this:
public class TestResponse {
private final String response;
private final ErrorCodeEnum error;
private final StatusCodeEnum status;
// .. constructors and getters here
}
I am serializing above class using Gson library as shown below:
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
System.out.println(gson.toJson(testResponseOutput));
And the response I am getting back is shown below:
{
"response": "{\"hello\":0,\"world\":\"0\"}",
"error": "OK",
"status": "SUCCESS"
}
As you can see, my json string in "response" field is getting escaped. Is there any way I can ask gson not to do that and instead return a full response like this:
{
"response": {"hello":0,"world":"0"},
"error": "OK",
"status": "SUCCESS"
}
And also - Is there any problem if I do it above way?
NOTE: My "response" string will always be JSON string or it will be null so only these two values will be there in my "response" string. In "response" field, I can have any json string since this library is calling a rest service which can return back any json string so I am storing that in a string "response" field.
If your response field can be arbitrary JSON, then you need to:
Define it as an arbitrary JSON field (leveraging the JSON type system already built into GSON by defining it as the root of the JSON hierarchy - JsonElement)
public class TestResponse {
private final JsonElement response;
}
Convert the String field to an appropriate JSON object representation. For this, you can use GSON's JsonParser class:
final JsonParser parser = new JsonParser();
String responseJson = "{\"hello\":0,\"world\":\"0\"}";
JsonElement json = parser.parse(responseJson); // Omits error checking, what if responseJson is invalid JSON?
System.out.println(gson.toJson(new TestResponse(json)));
This should print:
{
"response": {
"hello": 0,
"world": "0"
}
}
It should also work for any valid JSON:
String responseJson = "{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}";
JsonElement json = parser.parse(responseJson);
System.out.println(gson.toJson(new TestResponse(json)));
Output:
{
"response": {
"arbitrary": "fields",
"can-be": {
"in": [
"here",
"!"
]
}
}
}
I know this is old but just adding an potential answer in case it is needed.
Sounds like you just want to return the response without escaping. Escaping is a good thing, it will help to prevent security issues and prevent your JS application from crashing with errors.
However, if you still want to ignore escaping, try:
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create();
add simple TypeAdapter and use jsonValue(value)
gson 2.8.0
version 1:
#Test
public void correctlyShow() {
TestResponse2 src = new TestResponse2("{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}");
Gson create = new GsonBuilder().registerTypeAdapter(String.class, ADAPTER).create();
Stopwatch createStarted = Stopwatch.createStarted();
String json2 = create.toJson(src);
System.out.println(json2 + " correctlyShow4 " + createStarted.stop());
}
public class TestResponse2 {
private final String response;
public TestResponse2(String response) {
this.response = response;
}
public String getResponse() {
return response;
}
}
private static final TypeAdapter<String> ADAPTER = new TypeAdapter<String>() {
#Override
public String read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Unsupported Operation !!!");
}
#Override
public void write(JsonWriter out, String value) throws IOException {
out.jsonValue(value);
}
};
...
vesrion 2
#Test
public void correctlyShow() {
TestResponse2 src = new TestResponse2("{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}");
String json2 = new Gson().toJson(src);
System.out.println(json2 + " correctlyShow4 ");
}
public class TestResponse2 {
#JsonAdapter(value = AdapterStringJson.class)
private final String response;
public TestResponse2(String response) {
this.response = response;
}
public String getResponse() {
return response;
}
}
private class AdapterStringJson extends TypeAdapter<String> {
#Override
public String read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Unsupported Operation !!!");
}
#Override
public void write(JsonWriter out, String value) throws IOException {
out.jsonValue(value);
}
}
You should have a nested object.
public class Response {
private final Integer hello;
private final String world;
}
public class TestResponse {
private final Response response;
private final ErrorCodeEnum error;
private final StatusCodeEnum status;
// .. constructors and getters here
}
Instead of a String, depending on your needs, you could use a Map (or similar) or a nested Object. There should not be a problem representing it this way but in your example, if it were a String, there would be a problem if you didn't escape characters such as the double-quote.

No-args constructor for class XXX does not exist

I am new to Android development. Here, I am making a GET call like this -
protected String doInBackground(String... params) {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("email", "guest#example.com"));
JSONHttpClient jsonHttpClient = new JSONHttpClient();
ProductDetail[] products = jsonHttpClient.Get(ServiceUrl.PRODUCT, nameValuePairs, ProductDetail[].class);
return null;
}
This is the GET call in JSONHttpClient file -
public <T> T Get(String url, List<NameValuePair> params, final Class<T> objectClass) {
DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
String paramString = URLEncodedUtils.format(params, "utf-8");
url += "?" + paramString;
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
httpGet.setHeader("Accept-Encoding", "gzip");
httpGet.setHeader("Authorization", "Bearer <code>");
HttpResponse httpResponse = defaultHttpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
if (httpEntity != null) {
InputStream inputStream = httpEntity.getContent();
Header contentEncoding = httpResponse.getFirstHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
inputStream = new GZIPInputStream(inputStream);
}
String resultString = convertStreamToString(inputStream);
inputStream.close();
return new GsonBuilder().create().fromJson(resultString, objectClass);
}
return null;
}
And this is my ProductDetail class -
public class ProductDetail {
public int Id;
public String Name;
}
On running this, I am getting below error -
No-args constructor for class com.compa.ProductDetail does not exist. Register an InstanceCreator with Gson for this type to fix this problem.
This is thrown on this line in JSONHttpClient file -
return new GsonBuilder().create().fromJson(resultString, objectClass);
Can anyone help on this?
In my web api, I am creating json like this (proddetails is a C# IEnumerable object) -
json = JsonConvert.SerializeObject(proddetails);
var response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(json, Encoding.UTF8, "application/json");
return response;
The structure of response json is -
[
{
"Id": 1,
"Name": "First"
},
{
"Id": 2,
"Name": "Second"
}
]
The Gson user guide (https://sites.google.com/site/gson/gson-user-guide) tells you that a well behaved class (meant for serialization and deserialization) should have a no argument constructor. If this is not there, it advises you to use InstanceCreator.
Even if you do not have a constructor, Gson will create an ObjectConstructor for your class. But this is not safe always and has it's own limitations. This question on SO goes more into the details: Is default no-args constructor mandatory for Gson?
NOTE: Please see that if this is an inner class, then it MUST have a constructor as explained in the documentation.
EDIT: Your json is an array. So you need to have the specified number of array objects in the containing class. So you can do the following and then cast:
public class ProductDetailArray {
public ProductDetailArray[] array;
public static ProductDetail {
public ProductDetail() {} // You can also make the constructor private if you don't want anyone to instantiate this
public int Id;
public String Name;
}
}
Once you cast your json similarly as before:
ProductDetailArray obj = GsonBuilder.create().fromJson(response, ProductDetailArray.class);
ProductDetail one = obj.array[0];
ProductDetail two = obj.array[1];
And then you can do your manipulation.. also you should probably be using Gson.fromJson() rather than the GsonBuilder

Categories