Retrofit2+Gson发POST请求体带额外引号无法解析JSON如何解决
在Android模拟器环境调用Java编写的RestFul API,接口支持接收纯文本、JSON格式入参,返回JSON响应;客户端采用Retrofit2搭配GsonConverterFactory发起网络请求,用Gson做字段序列化/解析,未定义POJO类,仅通过HashMap存储登录参数后转换为JSON字符串,本地打印生成的JSON格式正常:{"password":"K16073","userid":"K16073"}
客户端实现代码如下:
/* Android code */ Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(myRFReceivingApis.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); Map<String, String> userData = new HashMap<>(); userData.put("userid",edtTxtUserId.getText().toString()); userData.put("password",editTxtPassword.getText().toString()); Gson gson = new Gson(); System.out.println(" generated gson " +gson.toJson(userData)); call = ApiClient.getInstance().getMyApi().callLogin(gson.toJson(userData)); call.enqueue(new Callback<Object>() { @Override public void onResponse(Call<Object> call, Response<Object> response) { textViewResp.setText(response.body().toString()); : /* End of Android code */
实际请求到服务端时,收到的字符串被额外包裹了一层双引号,打印结果为:"{\"password\":\"K16073\",\"userid\":\"K16073\"}"
直接解析会抛出java.lang.IllegalStateException: Not a JSON Object异常;尝试手动通过substring移除首尾引号后,又抛出com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Expected name at line 1 column 2 path $异常,始终无法拿到合法JSON结构。
服务端实现代码如下:
@POST @Produces(MediaType.APPLICATION_JSON) @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON}) @Path("login") public String RFLoginNew(String jsonString) { String result = jsonString.substring(1, jsonString.length() - 1); System.out.println(" Json String "+result); // tried using JsonParser -- line 1 below JsonObject o = JsonParser.parseString(result).getAsJsonObject(); // tried using Gson gson.fromJson Gson gson = new Gson(); JsonElement element = gson.fromJson (result, JsonElement.class); //Converts the json string to JsonElement without POJO JsonObject jsonObj = element.getAsJsonObject(); //Converting JsonElement to JsonObject // --line 2 below System.out.println(" RFLoginNew struser "+ jsonObj.get("userid").getAsString());
问题来自双重序列化:提前把HashMap转成了JSON字符串,再把这个字符串作为参数传给Retrofit的接口方法时,GsonConverterFactory会默认将传入的String类型值再次做一次JSON序列化——把原本的JSON对象字符串转成了JSON标准的字符串值,因此才会多出外层双引号,字符串内部的双引号也被自动加上了转义符\。
手动截取首尾引号无法解决问题,因为截完的字符串里仍然保留了转义符,不符合JSON语法规范,自然会抛出解析异常。
优先从客户端修复根源问题,二选一即可:
- 方案1(最简便):不要提前将HashMap转为JSON字符串,直接把HashMap对象作为请求参数传给Retrofit接口,让GsonConverterFactory自动完成一次序列化即可,不会出现双重转义。
注意Retrofit的接口方法需要给参数加上@Body注解,参数类型声明为Map<String, String>,修改请求调用代码:// 去掉gson.toJson转换,直接传HashMap对象 call = ApiClient.getInstance().getMyApi().callLogin(userData); - 方案2:如果业务要求必须传提前序列化好的JSON字符串,不要让GsonConverterFactory处理该参数,将参数类型改为
RequestBody,直接构造JSON格式的请求体传入,避免二次序列化:String jsonStr = gson.toJson(userData); RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonStr); call = ApiClient.getInstance().getMyApi().callLogin(requestBody);
如果暂时无法修改客户端,可在服务端做兼容处理:不要手动截取字符串,先将收到的带转义的内容反序列化为普通字符串(自动处理转义符、去掉外层引号),再解析为JsonObject:
Gson gson = new Gson(); // 第一步:将JSON字符串值反序列化为普通Java字符串,自动去除转义和外层引号 String realJson = gson.fromJson(jsonString, String.class); // 第二步:再解析为JsonObject JsonObject jsonObj = JsonParser.parseString(realJson).getAsJsonObject();
内容的提问来源于stack exchange,提问作者Rache

