改造 - 在将其解析为 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 之前从响应正文中删除一些无效字符的主要内容,如果未能解决你的问题,请参考以下文章

如何从android中的php关联数组解析JSON响应?

从改造响应中解析 JSON 对象

在进行改造 2 发布请求时,响应正文为空

如何通过改造解析 JSON 响应

改造无法正确解析正文

在将媒体发送到 HTTP 响应正文之前,我在媒体上执行啥编码