如何使用Retrofit从asynchronouscallback中返回String或JSONObject?
例如,打电话
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
我想我必须禁用gsonparsing到POJO,但不知道如何做到这一点。
我想到了。 这很尴尬,但非常简单… 临时解决scheme可能是这样的:
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(); } }
但是,如果你想直接获取callback更好的方法是使用转换器 。
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())); } } }
如果你知道如何改进这个代码 – 请随时写下来。
一个可能的解决scheme是使用JsonElement
作为Callback
types( Callback<JsonElement>
)。 在你原来的例子中:
api.getUserName(userId, new Callback<JsonElement>() {...});
在成功的方法中,您可以将JsonElement
转换为String
或JsonObject
。
JsonObject jsonObj = element.getAsJsonObject(); String strObj = element.toString();
Retrofit 2.0.0-beta3增加了一个
converter-scalars
模块,它提供了一个Converter.Factory
用于将String
,8种基本types和8种盒装基元types转换为text/plain
。 在你的普通转换器之前安装这个,以避免通过这些简单的标量,例如,一个JSON转换器。
所以,首先添加converter-scalars
build.gradle
模块来为您的应用程序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' }
然后,像这样创build你的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); }
答案可能比已经提到的要短得多,不需要任何额外的库:
在声明中使用Response
如下:
... Callback<Response> callback);
并在处理响应时:
@Override public void success(Response s, Response response) { new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes())) }
当@lordmegamax答案完全工作,有更好的解决scheme来自
Okio是一个新的库,它补充了java.io和java.nio
其他广场项目,已经紧张的retrofit
,因此你不需要添加任何新的依赖项,它必须是可靠的:
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); } }
这是我在debugging器中查到的。 注意:这是为了真正获取它在一个错误callback,而不是成功的callback。
你会看到成功的types可以通过调用retrofitError.getSuccessType()
来find,并返回types为Type
对象
然后你可以调用retrofitError.getBodyAs(YourType.class)
,这是我所需要做的,因为对我来说,它总是我期望的类。
这是一个简单的答案:
retrofitError.getBodyAs(retrofitError.getSuccessType())
现在,我会注意到,我不必做任何这样的成功callback,因为它已经神奇地工作。
获取调用JSONObject或JSONArray
您可以创build自定义工厂或从这里复制它: https : //github.com/marcinOz/Retrofit2JSONConverterFactory