如何处理改造中的动态 JSON?

我使用的是改造后的高效网络库,但是我无法处理动态 JSON,它包含一个前缀 responseMessage,随机地改为 object,同样的前缀(responseMessage)在某些情况下(动态地)改为 String。

Json format 响应消息对象:

{
"applicationType":"1",
"responseMessage":{
"surname":"Jhon",
"forename":" taylor",
"dob":"17081990",
"refNo":"3394909238490F",
"result":"Received"
}


}

Json 格式动态更改为类型 string:

 {
"applicationType":"4",
"responseMessage":"Success"
}

我的问题是,因为改造有内置的 JSON解析,我必须分配单个 POJO 每个请求!但不幸的是,REST-API 是基于动态 JSON响应构建的。在 成功失败(...)方法中,前缀将从字符串随机更改为对象!

void doTrackRef(Map<String, String> paramsref2) {
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("http://192.168.100.44/RestDemo").build();






TrackerRefRequest userref = restAdapter.create(TrackerRefRequest.class);
userref.login(paramsref2,
new Callback<TrackerRefResponse>() {
@Override
public void success(
TrackerRefResponse trackdetailresponse,
Response response) {


Toast.makeText(TrackerActivity.this, "Success",
Toast.LENGTH_SHORT).show();


}


@Override
public void failure(RetrofitError retrofitError) {




Toast.makeText(TrackerActivity.this, "No internet",
Toast.LENGTH_SHORT).show();
}




});
}

波乔:

public class TrackerRefResponse {




private String applicationType;


private String responseMessage;          //String type


//private ResponseMessage responseMessage;  //Object of type ResponseMessage


//Setters and Getters




}

在上面的代码中,POJO TrackerRefResponse.java 前缀 responseMessage 被设置为 string 或 object 类型的 response seMessage,因此我们可以创建具有相同名称的 ref 变量的 POJO (java 基础知识:) ,所以我正在寻找相同的解决方案,为动态 JSON中的龟裂。 我知道在具有异步任务的普通 http 客户端中这是非常容易的工作,但是在 REST-Api JSON解析中这不是最佳实践!看看性能 基准 总是排球或改装是最好的选择,但我失败的处理动态 JSON

我知道可能的解决办法

  1. 在 http 客户端解析中使用旧的 asyc 任务

  2. 尝试说服 RESTapi 后端开发人员。

  3. 创建客户端:)

68791 次浏览

Any of your possible solutions will work. What you can also do is send the Retrofit api interface return type to response. With that response you get a body Inputstream which you can convert to a JSON Object and read as you see fit.

Look at: http://square.github.io/retrofit/#api-declaration - under RESPONSE OBJECT TYPE

Updated

Retrofit 2 is out now and with it some changes to the documentation and library.

Look at http://square.github.io/retrofit/#restadapter-configuration there are request and response body object that can be used.

In addition to what you told -

Use Callback Then you can retrieve the fields using regular get method. For more information, go through the javadoc of gson.

http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html

RestClient.java

import retrofit.client.Response;
public interface RestClient {
@GET("/api/foo") Response getYourJson();
}

YourClass.java

RestClient restClient;


// create your restClient


Response response = restClient.getYourJson();


Gson gson = new Gson();
String json = response.getBody().toString();
if (checkResponseMessage(json)) {
Pojo1 pojo1 = gson.fromJson(json, Pojo1.class);
} else {
Pojo2 pojo2 = gson.fromJson(json, Pojo2.class);
}

You must implement "checkResponseMessage" method.

If it was not possible to change the backend API, I would consider the following variants (if Gson is used to convert JSON).

  1. We can use Gson type adapters to create a custom adapter for ResponseMessage type that dynamically decides how to parse the inoming JSON (using something like if (reader.peek() == JsonToken.STRING)).

  2. Put some meta information describing the response type into an HTTP header and use it to determine what type information must be fed to Gson instance.

Late to the party, but you can use a converter.

RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://graph.facebook.com")
.setConverter(new DynamicJsonConverter()) // set your static class as converter here
.build();


api = restAdapter.create(FacebookApi.class);

Then you use a static class which implements retrofit's Converter:

static class DynamicJsonConverter implements Converter {


@Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
try {
InputStream in = typedInput.in(); // convert the typedInput to String
String string = fromStream(in);
in.close(); // we are responsible to close the InputStream after use


if (String.class.equals(type)) {
return string;
} else {
return new Gson().fromJson(string, type); // convert to the supplied type, typically Object, JsonObject or Map<String, Object>
}
} catch (Exception e) { // a lot may happen here, whatever happens
throw new ConversionException(e); // wrap it into ConversionException so retrofit can process it
}
}


@Override public TypedOutput toBody(Object object) { // not required
return null;
}


private static String fromStream(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
out.append("\r\n");
}
return out.toString();
}
}

I have written this sample converter so it returns the Json response either as String, Object, JsonObject or Map< String, Object >. Obviously not all return types will work for every Json, and there is sure room for improvement. But it demonstrates how to use a Converter to convert almost any response to dynamic Json.

The accepted answer seemed over complicated for me, I solve it this way:

Call<ResponseBody> call = client.request(params);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Response<ResponseBody> response) {
if (response.isSuccess()) {
Gson gson = new Gson();
ResponseBody repsonseBody = response.body().string();
if (isEmail) {
EmailReport reports = gson.fromJson(responseBody, EmailReport.class);
} else{
PhoneReport reports = gson.fromJson(repsonseBody, PhoneReport.class);
}
}
}
@Override
public void onFailure(Throwable t) {
Log.e(LOG_TAG, "message =" + t.getMessage());
}
});

This is just an example in attempt to show you how you can use different model.

The variable isEmail is just a boolean for your condition to use the appropriate model.

I know I am very very late to the party. I had a similar issue and just solved it like this:

public class TrackerRefResponse {


private String applicationType;
// Changed to Object. Works fine with String and array responses.
private Object responseMessage;


}

I literally just changed to type to Object. I chose this approach because only one field in the response was dynamic (for me, my response was way more complicated), so using a converter would have made life difficult. Used Gson to work with the Object from there, depending on if it was a String or Array value.

Hope this helps someone looking for a simple answer :).

Try custom deserialisation using gson-converter as below(updated answer for Retrofit 2.0)

Create three models as shown below

ResponseWrapper

public class ResponseWrapper {


@SerializedName("applicationType")
@Expose
private String applicationType;
@SerializedName("responseMessage")
@Expose
private Object responseMessage;


public String getApplicationType() {
return applicationType;
}


public void setApplicationType(String applicationType) {
this.applicationType = applicationType;
}


public Object getResponseMessage() {
return responseMessage;
}


public void setResponseMessage(Object responseMessage) {
this.responseMessage = responseMessage;
}


}

ResponseMessage

public class ResponseMessage extends ResponseWrapper {


@SerializedName("surname")
@Expose
private String surname;
@SerializedName("forename")
@Expose
private String forename;
@SerializedName("dob")
@Expose
private String dob;
@SerializedName("refNo")
@Expose
private String refNo;
@SerializedName("result")
@Expose
private String result;


public String getSurname() {
return surname;
}


public void setSurname(String surname) {
this.surname = surname;
}


public String getForename() {
return forename;
}


public void setForename(String forename) {
this.forename = forename;
}


public String getDob() {
return dob;
}


public void setDob(String dob) {
this.dob = dob;
}


public String getRefNo() {
return refNo;
}


public void setRefNo(String refNo) {
this.refNo = refNo;
}


public String getResult() {
return result;
}


public void setResult(String result) {
this.result = result;
}


}

ResponseString

public class ResponseString extends ResponseWrapper {


}

UserResponseDeserializer(custom deserialiser)

public class UserResponseDeserializer implements JsonDeserializer<ResponseWrapper> {
@Override
public ResponseWrapper deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {




if (((JsonObject) json).get("responseMessage") instanceof JsonObject){
return new Gson().fromJson(json, ResponseMessage.class);
} else {
return new Gson().fromJson(json, ResponseString.class);
}


}
}

Retrofit 2.0 Implementation

Gson userDeserializer = new GsonBuilder().setLenient().registerTypeAdapter(ResponseWrapper.class, new UserResponseDeserializer()).create();




Retrofit retrofit = new Retrofit.Builder()
.baseUrl("base_url")
.addConverterFactory(GsonConverterFactory.create(userDeserializer))
.build();




UserService request = retrofit.create(UserService.class);
Call<ResponseWrapper> call1=request.listAllUsers();


call1.enqueue(new Callback<ResponseWrapper>() {
@Override
public void onResponse(Call<ResponseWrapper> call, Response<ResponseWrapper> response) {
ResponseWrapper responseWrapper=response.body();
Log.i("DYNAMIC RESPONSE", String.valueOf(response.body().getResponseMessage()));
}


@Override
public void onFailure(Call<ResponseWrapper> call, Throwable t) {
}
});

Libraries Used

compile 'com.squareup.retrofit2:retrofit:2.3.0'

compile 'com.squareup.retrofit2:converter-gson:2.3.0'

***** Previous Answer (above answer is more recommended one) *****

Change your pojo like this

public class TrackerRefResponse {


private String applicationType;
private Object responseMessage;


public Object getResponseMessage() {
return responseMessage;
}


public void setResponseMessage(Object responseMessage) {
this.responseMessage = responseMessage;
}
}

and change retrofit's onResponse like this

 @Override
public void onResponse(Response<TrackerRefResponse > response) {
if (response.isSuccess()) {
if (response.getResponseMessage() instanceof String)
{
handleStringResponse();
}
else
{
handleObjectResponse();
}
}
}

you may also check this post for more details about dynamic json parsing

I too ran of this issue. but i am not sure if this was your case , (i am using Retrofit2)

on my case i need to handle error, and success messages.

On Success

{
"call_id": 1,
"status": "SUCCESS",
"status_code": "SUCCESS",
"result": {
"data1": {
"id": "RFP2UjW7p8ggpMXzYO9tRg==",
"name": "abcdef",
"mobile_no": "96655222",
"email": ""
},
"data2": [
{
"no": "12345"
},
{
"no": "45632"
}
]
}
}

On Error,

{
"call_id": 1,
"status": "FAILED",
"status_code": "NO_RECORDS",
"error": {
"error_title": "xxx",
"error_message": "details not found"
}
}

for this i just created another POJO Error,

public class ValidateUserResponse {
@SerializedName("call_id")
public String callId;
@SerializedName("status")
public String status;
@SerializedName("status_code")
public String statusCode;
@SerializedName("result")
public ValidateUserResult result;
@SerializedName("error")
public Error error;
}

Error.java

public class Error {
@SerializedName("error_title")
public String errorTitle;
@SerializedName("error_message")
public String errorMessage;
}

ValidateUser.java

public class ValidateUserResult {


@SerializedName("auth_check")
public String authCheck;
@SerializedName("data1")
public Data1 data1;
@SerializedName("data2")
public List<Data2> data2;
}

in the above case if the result key on json contains data1,data2 then the ValidateUserResult.java get initialised. if error then the Error.java class get initialized.

I know I am late, but I just want to share my thought. I was working on a project where I am writing a method. The method uses retrofit to get data from server. Since other developers in my company will use this method, I could not use a POJO class (in your example, the TrackerRefResponse class). So I used JsonObject / Object like this:

interface APIService.java

public class APIService{
@FormUrlEncoded
@POST
Call<JsonObject> myPostMethod(@Url String url, @Field("input") String input);
}

Then in my method, I wrote this:

Model1 model1 = null;
Model2 model2 = null;
Call<JsonObject> call = RetrofitClient.getAPIService().establishUserSession(post_request_url, someParameter);


call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
JsonObject jsonObject = response.body();
// then do your stuff. maybe something like this
try{
model1 = new Gson().fromJson(jsonObject, Model1.class);
}catch(Exception x){}
try{
model2 = new Gson().fromJson(jsonObject, Model2.class);
}catch(Exception x){}


if(model1 != null) { /*handle model1 */}
if(model2 != null) { /*handle model2*/}
// rest of the code
}
        

If you know that a certain attibute will tell you which type of response it is, you can use JsonObject , read that attribute and then cast the model like this way:

// ... retrofit codes
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
int number = jsonObject.get("applicationType").getAsInt();
if(number == 1) {
model1 = new Gson().fromJson(jsonObject, Model1.class);
}
}
// ... rest of the code

You can also use Object instead of 'JsonObject`. Later, when you will know which kind of response it is, maybe you can cast this into desired object.

Just look into the other options it works for me:-

Json

1.
{
"applicationType":"1",
"responseMessage":
{
"surname":"Jhon",
"forename":" taylor",
"dob":"17081990",
"refNo":"3394909238490F",
"result":"Received"
}


}


2.
{
"applicationType":"4",
"responseMessage":
{
"forename":" taylor",
"dob":"17081990",
}
}
3.
{
"applicationType":"5",
"responseMessage":
{
"refNo":"3394909238490F",
"result":"Received"
}
}

Pojo class will be:-

public class ResponseMessage
{
private String surname;


private String forename;


private String dob;


private String refNo;


private String result;


public void setSurname(String surname){
this.surname = surname;
}
public String getSurname(){
return this.surname;
}
public void setForename(String forename){
this.forename = forename;
}
public String getForename(){
return this.forename;
}
public void setDob(String dob){
this.dob = dob;
}
public String getDob(){
return this.dob;
}
public void setRefNo(String refNo){
this.refNo = refNo;
}
public String getRefNo(){
return this.refNo;
}
public void setResult(String result){
this.result = result;
}
public String getResult(){
return this.result;
}
}


public class Root
{
private String applicationType;


private ResponseMessage responseMessage;


public void setApplicationType(String applicationType){
this.applicationType = applicationType;
}
public String getApplicationType(){
return this.applicationType;
}
public void setResponseMessage(ResponseMessage responseMessage){
this.responseMessage = responseMessage;
}
public ResponseMessage getResponseMessage(){
return this.responseMessage;
}
}

now final code

 if(responseMessage.getSurname() !=null){
---do something---
}
if(responseMessage.getForename !=null){
----do something
}
if(responseMessage.getDob() !=null){
---do something---
}
if(responseMessage.getRefNo() !=null){
---do something---
}
if(responseMessage.getResult() !=null){
---do something---
}

For kotlin developers you can declare the type of your variable as Any then convert it to Gson class with gson converter

data class SomeClass(
...somevariables,
val respnseMessage : Any
)

Then in your activity or fragment wherever you want to parse and use it. based on your use case you can do something like this

 val gson = Gson()
val data = gson.fromJson<YourDesireClass>
(response.responseMessage.toString() , YourDesireClass::class.java)