改进 - 在将其解析为json之前从响应主体中删除一些无效字符

时间:2014-10-10 00:50:22

标签: android json jsonp gson retrofit

我有一个外部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之前删除括号的最佳方法是什么?

4 个答案:

答案 0 :(得分:12)

您可以在GsonConverter Gson之前轻松地清除 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(); } }; 在将身体反序列化为类型对象之前的响应。

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

JsonTypedInput:

GsonConverter

这里我将JsonTypedOutput子类化,以便在转换为对象之前访问响应。 restAdapterBuilder.setConverter(new CleanGsonConverter(gson));用于在从垃圾字符中清除响应后保留响应的mime类型。

用法:

{{1}}

归咎于你的后端人员。 :)

答案 1 :(得分:7)

改造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();
            }
        }
    }


}
  • 另一种解决方案是责怪没有经验的后端开发人员。

答案 2 :(得分:1)

将jsonp(脏)转换为json(clean)的替代正则表达式:

String clean = dirty.replaceFirst("(?s)^\\((.*)\\)$", "$1");

答案 3 :(得分:0)

最初回答here

要解析无效的JSON或String或JSONP响应,请使用ScalarConverterFactory。

要解析JSON响应,请使用 GsonConverterFactory

如果您使用say 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)