如何通过 Retrofit 和 GsonConverter 处理 JSONP 响应?

Ans*_*bra 5 android jsonp gson retrofit2

我需要解析来自 Flickr API 的响应。
http://api.flickr.com/services/feeds/photos_public.gne?tagmode=any&format=json

它在 jsonFlickrFeed jQuery 回调函数中返回响应(这不是有效的 JSON 响应)。

我知道我们可以使用nojsoncallback=1查询删除 Flickr API 的 JSON 回调方法。

但是,如果强制使用 JSON with Padding (JSONP),是否有更好的方法来处理 JSONP 响应?

而不是将响应作为字符串获取,然后修剪 JSON 填充,然后解析剩余的 JSON 数据。

示例 Flickr API 响应 -

jsonFlickrFeed({
"title": "Recent Uploads tagged mountrainier",
"link": "http:\/\/www.flickr.com\/photos\/tags\/mountrainier\/",
"description": "",
"modified": "2016-12-15T16:56:42Z",
"generator": "http:\/\/www.flickr.com",
"items": [ {
    "title": "Gateway Arts District Open Studio Tour, December 10, 2016",
    "link": "http:\/\/www.flickr.com\/photos\/kimsworldofart\/31274762970\/",
    "media": {
        "m": "http:\/\/farm1.staticflickr.com\/381\/31274762970_c40599d623_m.jpg"
    },
    "date_taken": "2016-12-10T15:49:03-08:00",
    "description": " <p><a href=\"http:\/\/www.flickr.com\/people\/kimsworldofart\/\">kimsworldofart<\/a> posted a photo:<\/p> <p><a href=\"http:\/\/www.flickr.com\/photos\/kimsworldofart\/31274762970\/\" title=\"Gateway Arts District Open Studio Tour, December 10, 2016\"><img src=\"http:\/\/farm1.staticflickr.com\/381\/31274762970_c40599d623_m.jpg\" width=\"240\" height=\"135\" alt=\"Gateway Arts District Open Studio Tour, December 10, 2016\" \/><\/a><\/p> <p>This photo was taken at the Otis Street Art Project in Mount Rainier, Maryland.<\/p>",
    "published": "2016-12-14T20:25:11Z",
    "author": "nobody@flickr.com (\"kimsworldofart\")",
    "author_id": "8508061@N02",
    "tags": "otisstreetartsproject gatewayartsdistrict mountrainier princegeorgescounty maryland"
}]})
Run Code Online (Sandbox Code Playgroud)

如何覆盖 GSON Converter 以修剪这些额外的函数语法,然后解析剩余的有效 JSON?

iag*_*een 5

使用标准GsonConverterFactory作为指导,我们可以构建一个从流前面删除 JSONP 的模型,从而避免读取整个内容并进行修剪——

public final class GsonPConverterFactory extends Converter.Factory {

  Gson gson;

  public GsonPConverterFactory(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 GsonPResponseBodyConverter<>(gson, adapter);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                        Annotation[] parameterAnnotations,
                                                        Annotation[] methodAnnotations,
                                                        Retrofit retrofit) {
    return null;
  }
}
Run Code Online (Sandbox Code Playgroud)

和转换器本体。通过创建我们自己的 json 读取器,我们避免了流已完全消耗的断言。这允许我们在关闭流时将关闭的 JSONP 元素保留在流中。

final public class GsonPResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonPResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    Reader reader = value.charStream();
    int item = reader.read();
    while(item != '(' && item != -1) {
      item = reader.read();
    }
    JsonReader jsonReader = gson.newJsonReader(reader);
    try {
      return adapter.read(jsonReader);
    } finally {
      reader.close();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

像普通 Gson 工厂一样添加到您的改造中——

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(/* you base url */)
    .addConverterFactory(new GsonPConverterFactory(new Gson()))
    .build();
Run Code Online (Sandbox Code Playgroud)

注意:使用此转换器将要求所有响应都采用 JSONP 格式。它会在常规 JSON 响应上失败,并且您不能同时使用 Gson 和 GsonP 转换器。