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

Posted

技术标签:

【中文标题】如何在改造中处理动态 JSON?【英文标题】:How to handle Dynamic JSON in Retrofit? 【发布时间】:2014-08-08 09:02:37 【问题描述】:

我正在使用改造高效网络库,但我无法处理包含单个前缀 responseMessage 的动态 JSON,该前缀随机更改为 object,相同的前缀 (responseMessage) 在某些情况下更改为字符串 (动态)。

responseMessage的Json格式对象:


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


responseMessageJson格式动态变化为字符串类型:

 
       "applicationType":"4",
       "responseMessage":"Success"          
 

我的问题是因为改造内置了JSON 解析,我必须为每个请求分配一个 POJO!但不幸的是,REST-API 是建立在动态 JSON 响应之上的。前缀将在 success(...)failure(...) 方法中从字符串随机更改为对象!

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 设置为字符串或 responseMessage 类型的对象,因此我们可以使用具有相同名称的 ref 变量创建 POJO(java 基础 :))所以我正在寻找相同的动态解决方案JSON 在改造中。 我知道这在具有异步任务的普通 http 客户端中非常容易,但这不是 REST-Api JSON 解析中的最佳实践!看性能Benchmarks总是Volley或Retrofit是最好的选择,但是我处理动态JSON失败了!

我知道的可能解决方案

    在 http 客户端解析中使用旧的 asyc 任务。 :(

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

    创建自定义 Retrofit 客户端 :)

【问题讨论】:

"尝试说服 RESTapi 后端开发人员。"为我做了伎俩!哈哈! ;)(n.b:我也是后端开发人员,我要说服自己!) 【参考方案1】:

聚会迟到了,但你可以使用转换器。

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);

然后你使用一个实现改造转换器的静态类:

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();
    

我编写了这个示例转换器,因此它以 String、Object、JsonObject 或 Map 形式返回 Json 响应。显然,并非所有返回类型都适用于每个 Json,并且肯定有改进的余地。但它演示了如何使用 Converter 将几乎所有响应转换为动态 Json。

【讨论】:

看到RestAdapter 这个例子是针对Retrofit 1的。你将如何在Retrofit 2中实现相同的转换器? ConversionException 在 Retrofit 2 中不可用 :(【参考方案2】:

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);

你必须实现“checkResponseMessage”方法。

【讨论】:

如何在 Retrofit 2 中做同样的事情? 什么是“checkResponseMessage”?【参考方案3】:

使用gson-converter 尝试自定义反序列化,如下所示(Retrofit 2.0 的更新答案)

如下图创建三个模型

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;
    


响应消息

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;



响应字符串

public class ResponseString extends ResponseWrapper 


UserResponseDeserializer(自定义反序列化器)

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);
        



改造 2.0 实施

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) 
        
    );

使用的库

编译'com.squareup.retrofit2:retrofit:2.3.0'

编译'com.squareup.retrofit2:converter-gson:2.3.0'

***** 上一个答案(以上答案是比较推荐的一个) *****

像这样改变你的pojo

public class TrackerRefResponse 

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() 
  return responseMessage;
  

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

并像这样更改改造的 onResponse

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

您也可以查看this post for more details about dynamic json parsing

【讨论】:

ResponseWrapper 类真的有必要吗?我认为它看起来非常混乱。除了层次结构中最高的对象之外,我需要一个转换器... 如果包装类令人困惑,您可以避免使用它并尝试这个freshbytelabs.com/2018/12/…【参考方案4】:

您的任何可能的解决方案都会奏效。您还可以做的是将 Retrofit api 接口返回类型发送到响应。通过该响应,您将获得一个正文 Inputstream,您可以将其转换为 JSON 对象并按照您认为合适的方式读取。

查看:http://square.github.io/retrofit/#api-declaration - 在响应对象类型下

更新

Retrofit 2 现已推出,随之而来的是对文档和库的一些更改。

看http://square.github.io/retrofit/#restadapter-configuration有可以使用的请求和响应体对象。

【讨论】:

恐怕找不到您提供的部分,有同义词吗?【参考方案5】:

接受的答案对我来说似乎过于复杂,我是这样解决的:

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());
    
);

这只是一个示例,旨在向您展示如何使用不同的模型。

变量isEmail 只是一个布尔值,您的条件可以使用适当的模型。

【讨论】:

您能详细说明一下吗?此代码是非描述性的。 mType 是从哪里来的? @Desdroid 我简化了代码,然后用解释扩展了 不过,如果您在拨打电话之前不知道响应类型,我相信这将无济于事,问题就是这种情况。当然你可以先拿到Response Body的InputStream,读几行,确定Body是哪个Type,然后再转换。但这并不是那么简单。 我正在寻找处理不同返回类型的最佳方法。你的回答看起来很有希望,但我不确定你从哪里知道类型。这就是我想让你详细说明的原因;) 我认为它不会起作用,因为解串器会抛出异常并到达 onFailure() 而不是 onResponse()【参考方案6】:

我知道我迟到了。我有一个类似的问题,只是这样解决了:

public class TrackerRefResponse 

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


我实际上只是将类型更改为 Object。我选择这种方法是因为响应中只有一个字段是动态的(对我来说,我的响应要复杂得多),所以使用转换器会让生活变得困难。使用 Gson 从那里处理对象,具体取决于它是字符串还是数组值。

希望这对寻找简单答案的人有所帮助:)。

【讨论】:

【参考方案7】:

如果无法更改后端 API,我会考虑以下变体(如果使用 Gson 转换 JSON)。

    我们可以使用 Gson type adapters 为 ResponseMessage 类型创建一个自定义适配器,动态决定如何解析 inoming JSON(使用类似 if (reader.peek() == JsonToken.STRING) 的东西)。

    将一些描述响应类型的元信息放入 HTTP 标头中,并使用它来确定必须将哪些类型信息提供给 Gson 实例。

【讨论】:

【参考方案8】:

除了你所说的-

使用回调 然后,您可以使用常规 get 方法检索字段。 更多信息,请浏览 gson 的 javadoc。

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

【讨论】:

【参考方案9】:

我知道我迟到了,但我只是想分享一下我的想法。我正在研究一个正在编写方法的项目。该方法使用改造从服务器获取数据。由于我公司的其他开发人员将使用此方法,因此我无法使用 POJO 类(在您的示例中为 TrackerRefResponse 类)。所以我像这样使用JsonObject/Object

接口 APIService.java

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

然后在我的方法中,我写了这个:

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

        

如果您知道某个属性会告诉您它是哪种类型的响应,您可以使用 JsonObject ,读取该属性,然后像这样转换模型:

// ... 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

您也可以使用Object 代替“JsonObject”。稍后,当你知道它是哪种响应时,也许你可以将它转换为所需的对象。

【讨论】:

这个解决方案更适合我的问题。如果一切正常,API 响应之一为 null,并在出现问题时在简单的 Json 对象中返回成功(代码 200)和错误消息。我使用 JsonNull 作为默认响应。【参考方案10】:

我也遇到过这个问题。 但我不确定这是不是你的情况,(我正在使用 Retrofit2)

就我而言,我需要处理错误和成功消息。

成功


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


出错时,


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


为此,我刚刚创建了另一个 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;

在上述情况下,如果 json 上的 result 键包含 data1,data2 则 ValidateUserResult.java 会被初始化。 如果出错则 Error.java 类被初始化。

【讨论】:

【参考方案11】:

看看它对我有用的其他选项:-

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 类将是:-

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;
    
 

现在是最终代码

 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---
 

【讨论】:

以上是关于如何在改造中处理动态 JSON?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Android 改造解析动态键 JSON 数组

如何在改造中使用原始 json 对象调用改造 @DELETE 方法?

如何使用android studio中的改造服务在请求中传递json

如何在 Android 的改造请求中传递 JSON 对象并检索 JSON 对象?

如何在改造请求的正文中发布原始的整个 JSON?

如何在改造中为json字符串响应编写模型类?