改造 - 在将其解析为 json 之前从响应正文中删除一些无效字符
Posted
技术标签:
【中文标题】改造 - 在将其解析为 json 之前从响应正文中删除一些无效字符【英文标题】:Retrofit - removing some invalid characters from response body before parsing it as json 【发布时间】:2014-12-05 01:59:30 【问题描述】:我有一个外部 Web 服务,它在响应正文中返回 json 但嵌套在括号中,如下所示:
("door_x":"103994.001461","door_y":"98780.7862376", "distance":"53.3")
使用此代码:
class AddressInfo
String door_x;
String door_y;
interface AddressWebService
@GET("/reversegeocoding")
AddressInfo reverseGeocoding(@Query("x") double x, @Query("y") double y);
显然失败了。这是堆栈跟踪:
retrofit.RetrofitError: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:377)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at com.something.$Proxy7.reverseGeocoding(Native Method)
at com.something.ReverseGeocodingService.getAddress(ReverseGeocodingService.java:24)
at com.something.LocationProvider$1.run(LocationProvider.java:77)
at java.lang.Thread.run(Thread.java:864)
Caused by: retrofit.converter.ConversionException: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1
at retrofit.converter.GsonConverter.fromBody(GsonConverter.java:67)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:362)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at com.something.$Proxy7.reverseGeocoding(Native Method)
at com.something.ReverseGeocodingService.getAddress(ReverseGeocodingService.java:24)
at com.something.LocationProvider$1.run(LocationProvider.java:77)
at java.lang.Thread.run(Thread.java:864)
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:176)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at retrofit.converter.GsonConverter.fromBody(GsonConverter.java:63)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:362)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at com.something.$Proxy7.reverseGeocoding(Native Method)
at com.something.ReverseGeocodingService.getAddress(ReverseGeocodingService.java:24)
at com.something.LocationProvider$1.run(LocationProvider.java:77)
at java.lang.Thread.run(Thread.java:864)
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1
at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:374)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:165)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at retrofit.converter.GsonConverter.fromBody(GsonConverter.java:63)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:362)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at com.something.$Proxy7.reverseGeocoding(Native Method)
at com.something.ReverseGeocodingService.getAddress(ReverseGeocodingService.java:24)
at com.something.LocationProvider$1.run(LocationProvider.java:77)
at java.lang.Thread.run(Thread.java:864)
在解析 json 之前删除括号的最佳方法是什么?
【问题讨论】:
显然服务器是启用 JSONP 的服务器,但我无法避免括号包裹 【参考方案1】:原回复here
要解析无效的 JSON 或字符串或 JSONP 响应,请使用 ScalarConverterFactory。
要解析 JSON 响应,请使用 GsonConverterFactory。
如果您使用 flatMap 调用 JSON API,然后调用 JSONP API,则同时使用 GsonConverterFactory(JSON 需要) 和 ScalarConverterFactory(JSONP 需要)。
确保你的 gradle 中有以下依赖项:
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//For serialising JSONP add converter-scalars
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
//An Adapter for adapting RxJava 2.x types.
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
添加converterFactories 进行改造并在构建Gson 时使用setLenient() 以消除错误JSON document was not fully consumed.
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit = Retrofit.Builder()
.baseUrl("http://api.flickr.com/")
.client(builder.build())
.addConverterFactory(ScalarsConverterFactory.create()) //important
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
@GET("end-point/to/some/jsonp/url")
fun getJsonpData() : Observable<String>
使用转换器通过删除存在的前缀和后缀将无效的 JSON 转换为 JSON。 然后通过
将字符串转换为您的数据模型SomeDataModel model = Gson().fromJson<SomeDataModel>(jsonResponse,
SomeDataModel::class.java)
【讨论】:
替代:JsonObject【参考方案2】:改造解决方案 2
以下代码与GsonConverter
相同,只是您可以在转换为模型之前编辑Response
编辑 public T convert(ResponseBody value)
以清理您的 Response
/**
* Modified by TarekkMA on 8/2/2016.
*/
public class MyJsonConverter extends Converter.Factory
public static MyJsonConverter create()
return create(new Gson());
public static JsonHandler create(Gson gson)
return new JsonHandler(gson);
private final Gson gson;
private JsonHandler(Gson gson)
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit)
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody>
private final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter)
this.gson = gson;
this.adapter = adapter;
@Override
public RequestBody convert(T value) throws IOException
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T>
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter)
this.gson = gson;
this.adapter = adapter;
@Override
public T convert(ResponseBody value) throws IOException
String dirty = value.string();
String clean = dirty.replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
"<string xmlns=\"http://tempuri.org/\">","").replace("</string>","");
try
return adapter.fromJson(clean);
finally
value.close();
另一种解决方案是责怪缺乏经验的后端开发人员。
【讨论】:
您有一些拼写错误:您需要将“JsonHandler”替换为“MyJsonConverter”,反之亦然。【参考方案3】:从 jsonp(脏)转换为 json(干净)的替代正则表达式:
String clean = dirty.replaceFirst("(?s)^\\((.*)\\)$", "$1");
【讨论】:
【参考方案4】:在Gson
将正文反序列化为类型对象之前,您可以轻松清除GsonConverter
中的响应。
public class CleanGsonConverter extends GsonConverter
public CleanGsonConverter(Gson gson)
super(gson);
public CleanGsonConverter(Gson gson, String encoding)
super(gson, encoding);
@Override
public Object fromBody(TypedInput body, Type type) throws ConversionException
String dirty = toString(body);
String clean = dirty.replaceAll("(^\\(|\\)$)", "");
body = new JsonTypedInput(clean.getBytes(Charset.forName(HTTP.UTF_8)));
return super.fromBody(body, type);
private String toString(TypedInput body)
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try
br = new BufferedReader(new InputStreamReader(body.in()));
while ((line = br.readLine()) != null)
sb.append(line);
catch (IOException e)
e.printStackTrace();
finally
if (br != null)
try
br.close();
catch (IOException e)
e.printStackTrace();
return sb.toString();
;
JsonTypedInput:
public class JsonTypedInput implements TypedInput
private final byte[] mStringBytes;
JsonTypedInput(byte[] stringBytes)
this.mStringBytes = stringBytes;
@Override
public String mimeType()
return "application/json; charset=UTF-8";
@Override
public long length()
return mStringBytes.length;
@Override
public InputStream in() throws IOException
return new ByteArrayInputStream(mStringBytes);
在这里,我将GsonConverter
子类化以在响应转换为对象之前访问它。 JsonTypedOutput
用于在从垃圾字符中清除响应后保留响应的 mime 类型。
用法:
restAdapterBuilder.setConverter(new CleanGsonConverter(gson));
归咎于你的后端人员。 :)
【讨论】:
很好,我还没有机会测试它。只是一个问题,我应该如何初始化传递给CleanGsonConverter构造函数的gson参数? 与您对 GsonConverter 所做的相同。new CleanGsonConverter(gsonBuilder.create())
谢谢!有用。我唯一需要更改的是正则表达式,因为原来的内容正在替换整个文本。我用“(^\(|\)$)”
编辑已获批准。享受! :)
如何使用改造 2?以上是关于改造 - 在将其解析为 json 之前从响应正文中删除一些无效字符的主要内容,如果未能解决你的问题,请参考以下文章