【问题标题】:How can I return String or JSONObject from asynchronous callback using Retrofit?如何使用 Retrofit 从异步回调中返回 String 或 JSONObject?
【发布时间】:2014-03-19 20:51:57
【问题描述】:

例如调用

api.getUserName(userId, new Callback<String>() {...});

原因:

retrofit.RetrofitError: retrofit.converter.ConversionException:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: 
Expected a string but was BEGIN_OBJECT at line 1 column 2

我想我必须禁用 gson 解析为 POJO,但不知道该怎么做。

【问题讨论】:

  • 你明白了吗,我得到相反的错误并试图取回一个对象哈哈
  • @Lion789 不,我还没有 :( 我认为有一种方法可以返回原始响应,然后将其转换为任何对象...
  • 我实际上想通了,我发送了一些未被接受的东西,所以如果您要发回结果,请确保它只是一个字符串或您指定的内容,如果有帮助,请告诉我。
  • 我的意思是我想将响应正文转换为字符串...而正文实际上根本不是字符串...
  • 好吧,你以后必须这样做,当它不在回调中时,你不能说回调将是一个字符串,将它转换为一个字符串

标签: android retrofit retrofit2


【解决方案1】:

我想通了。很尴尬但是很简单……临时解决办法可能是这样的:

 public void success(Response response, Response ignored) {
            TypedInput body = response.getBody();
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(body.in()));
                StringBuilder out = new StringBuilder();
                String newLine = System.getProperty("line.separator");
                String line;
                while ((line = reader.readLine()) != null) {
                    out.append(line);
                    out.append(newLine);
                }

                // Prints the correct String representation of body. 
                System.out.println(out);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

但如果你想直接获得回调,更好的方法是使用Converter

public class Main {
public interface ApiService {
    @GET("/api/")
    public void getJson(Callback<String> callback);
}

public static void main(String[] args) {
    RestAdapter restAdapter = new RestAdapter.Builder()
            .setClient(new MockClient())
            .setConverter(new StringConverter())
            .setEndpoint("http://www.example.com").build();

    ApiService service = restAdapter.create(ApiService.class);
    service.getJson(new Callback<String>() {
        @Override
        public void success(String str, Response ignored) {
            // Prints the correct String representation of body.
            System.out.println(str);
        }

        @Override
        public void failure(RetrofitError retrofitError) {
            System.out.println("Failure, retrofitError" + retrofitError);
        }
    });
}

static class StringConverter implements Converter {

    @Override
    public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
        String text = null;
        try {
            text = fromStream(typedInput.in());
        } catch (IOException ignored) {/*NOP*/ }

        return text;
    }

    @Override
    public TypedOutput toBody(Object o) {
        return null;
    }

    public static String fromStream(InputStream in) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder out = new StringBuilder();
        String newLine = System.getProperty("line.separator");
        String line;
        while ((line = reader.readLine()) != null) {
            out.append(line);
            out.append(newLine);
        }
        return out.toString();
    }
}

public static class MockClient implements Client {
    @Override
    public Response execute(Request request) throws IOException {
        URI uri = URI.create(request.getUrl());
        String responseString = "";

        if (uri.getPath().equals("/api/")) {
            responseString = "{result:\"ok\"}";
        } else {
            responseString = "{result:\"error\"}";
        }

        return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST,
                new TypedByteArray("application/json", responseString.getBytes()));
    }
  }
}

如果您知道如何改进此代码 - 请随时撰写。

【讨论】:

  • 这不起作用并且会抛出错误:retrofit.RetrofitError: No Retrofit annotation found。 (参数#2)
  • 它在出版时间有效。如果您确定有问题,请告诉我。
  • 我正在尝试构建一个自定义转换器进行改造,但使用该转换器也会覆盖我的请求。我想为响应定制转换器,我该怎么做?谢谢
  • 那是很多样板代码。这真的是解析 Retrofit 响应的唯一方法吗?
  • 很好的解决方案。 @r7v 解决您的问题(以及我的问题,这正是将我带到此页面的原因)将是子类化 GsonConverter 并覆盖 fromBody() 方法,保持 toBody() 方法完好无损。
【解决方案2】:

一个可能的解决方案是使用JsonElement 作为Callback 类型(Callback&lt;JsonElement&gt;)。在您的原始示例中:

api.getUserName(userId, new Callback<JsonElement>() {...});

在成功方法中,您可以将JsonElement 转换为StringJsonObject

JsonObject jsonObj = element.getAsJsonObject();
String strObj = element.toString();

【讨论】:

  • 这可行,但效率低下,因为您必须将响应转换为 JsonObject 然后再转换回字符串。 InputStream to String 要好得多,但有点棘手。
【解决方案3】:

Retrofit 2.0.0-beta3 添加了一个converter-scalars 模块提供了一个 Converter.Factory 用于转换 String,这 8 种基本类型, 以及 8 个盒装原始类型为 text/plain 主体。安装这个 在您的普通转换器之前避免通过这些简单的标量 例如,通过 JSON 转换器。

所以,首先将converter-scalars 模块添加到您的应用程序的build.gradle 文件中。

dependencies {
    ...
    // use your Retrofit version (requires at minimum 2.0.0-beta3) instead of 2.0.0
    // also do not forget to add other Retrofit module you needed
    compile 'com.squareup.retrofit2:converter-scalars:2.0.0'
}

然后,像这样创建您的 Retrofit 实例:

new Retrofit.Builder()
        .baseUrl(BASE_URL)
        // add the converter-scalars for coverting String
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .build()
        .create(Service.class);

现在您可以像这样使用 API 声明:

interface Service {

    @GET("/users/{id}/name")
    Call<String> userName(@Path("userId") String userId);

    // RxJava version
    @GET("/users/{id}/name")
    Observable<String> userName(@Path("userId") String userId);
}

【讨论】:

  • @lordmegamax 这应该是 2017 年公认的答案
【解决方案4】:

答案可能比已经提到的要短得多,并且不需要任何额外的库:

在声明中使用Response,如下所示:

... Callback<Response> callback);

在处理响应时:

@Override
public void success(Response s, Response response) {
    new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes()))
}

【讨论】:

  • 我在 1.9.4 的时候用过,但不知道为什么在 2.0 上不行
  • 如果您的 POST 没有返回任何内容,这是正确的答案。谢谢!
【解决方案5】:

当@lordmegamax 回答完全起作用时,会有更好的解决方案来自

Okio 是一个补充 java.io 和 java.nio 的新库

已经与 retrofit 紧密结合的其他 squares 项目,因此您不需要添加任何新的依赖项并且它必须是可靠的:

ByteString.read(body.in(), (int) body.length()).utf8();

ByteString 是一个不可变的字节序列。对于字符数据,String 是基础。 ByteString 是 String 失散多年的兄弟,可以很容易地将二进制数据视为一个值。这个类符合人体工程学:它知道如何将自己编码和解码为 hex、base64 和 UTF-8。

完整示例:

public class StringConverter implements Converter {
  @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
    try {
      return ByteString.read(body.in(), (int) body.length()).utf8();
    } catch (IOException e) {
      throw new ConversionException("Problem when convert string", e);
    }
  }

  @Override public TypedOutput toBody(Object object) {
    return new TypedString((String) object);
  }
}

【讨论】:

  • 太棒了!实现Converter是解决这个问题的关键。
【解决方案6】:

获取调用 JSONObject 或 JSONArray

您可以创建自定义工厂或从此处复制它:https://github.com/marcinOz/Retrofit2JSONConverterFactory

【讨论】:

    猜你喜欢
    • 2016-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多