2014-02-19 61 views
44

例如,调用我如何使用改造从异步回调返回字符串或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 

我想我必须禁用GSON解析成的POJO,但无法弄清楚如何做到这一点。

+0

你看着办吧,我得到相反的错误,并试图得到一个对象返回笑 – Lion789

+1

@ Lion789不,我还没有:(我认为有一种方法可以返回原始响应,然后将其转换为任何对象... – lordmegamax

+0

我其实已经想通了,我发送了一些未被接受的东西,所以如果你发回结果请确保它只是一个字符串或你指定的,让我知道是否有帮助 – Lion789

回答

48

我想通了。这是令人尴尬的,但它是非常简单的...... 临时解决可能是这样的:

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

如果你知道如何改进此代码 - 请随时写下它。

+0

这不起作用,并会引发错误:** retrofit.RetrofitError:No Retrofit注释找不到。 (参数#2)** –

+0

它已经在发布时间。如果您确定存在问题,请告诉我。 – lordmegamax

+0

我想建立一个自定义转换器的改造,但使用转换器也覆盖我的要求。我想有自定义转换器只是为了回应,我怎么能这样做?谢谢 – 3xplore

31

可能的解决方案是使用JsonElement作为Callback类型(Callback<JsonElement>)。在原来的例子:

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

在成功的方法,你可以将JsonElement转换到任何一个StringJsonObject

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

这可行,但效率不高,因为您必须将响应转换为JsonObject,然后再转换为字符串。 InputStream字符串好得多,但有点棘手。 –

4

当@lordmegamax回答完全工作存在被来自

Okio is a new library that complements java.io and java.nio

其他广场项目,该项目与retrofit,因此已经紧你不需要添加任何新的依赖和更漂亮的解决方案它必须是可靠的:

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

ByteString is an immutable sequence of bytes. For character data, String is fundamental. ByteString is String's long-lost brother, making it easy to treat binary data as a value. This class is ergonomic: it knows how to encode and decode itself as hex, base64, and 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); 
    } 
} 
+0

太棒了!实现Converter是这个问题的关键。 –

21

答案可能远小于已经提到的,并且不需要任何额外的库:

在声明中使用Response如下:

... Callback<Response> callback); 

而且在处理响应:

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

真棒!最好的解决方案,没有肮脏的魔法参与 – ruX

+0

这是否仍然在Retrofit 2.0中工作? – hackjutsu

+0

我在1.9.4时使用了它,但我不知道为什么它不适用于2。0 – bgplaya

29

Retrofit 2.0.0-beta3 adds a converter-scalars module provides a Converter.Factory for converting String , the 8 primitive types, and the 8 boxed primitive types as text/plain bodies. Install this before your normal converter to avoid passing these simple scalars through, for example, a JSON converter.

所以,先加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("https://stackoverflow.com/users/{id}/name") 
    Call<String> userName(@Path("userId") String userId); 

    // RxJava version 
    @GET("https://stackoverflow.com/users/{id}/name") 
    Observable<String> userName(@Path("userId") String userId); 
} 
+1

@lordmegamax这应该是2017年被接受的答案 – muetzenflo

-1

这是我做什么,在打探后调试器。注意:这是为了在错误回调中实际获取它,而不是成功回调。

你会看到成功的类型是通过调用retrofitError.getSuccessType()和回报和类型的对象中找到Type

你可以调用retrofitError.getBodyAs(YourType.class)这是所有我需要我,因为做其始终类我希望它成为。

这里是一个班轮答案:

retrofitError.getBodyAs(retrofitError.getSuccessType()) 

现在,我会注意到,我没有做任何事情像这对于成功回调,因为它已经奇迹般地工作。