How to return a String or JSONObject from an asynchronous callback using Retrofit?

For example, a call

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

Cause:

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

I think I should disable gson analysis in POJO, but cannot figure out how to do this.

+43
android retrofit retrofit2
Feb 19 '14 at 13:29
source share
7 answers

I get it. This is awkward, but it was very simple ... The workaround could be this:

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

But if you want a direct callback. The best way is to use 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())); } } } 

If you know how to improve this code, feel free to write about it.

+48
Apr 02 '14 at 18:36
source share
β€” -

A possible solution would be to use JsonElement as the type of Callback ( Callback<JsonElement> ). In the original example:

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

In the success method, you can convert JsonElement to String or JsonObject .

 JsonObject jsonObj = element.getAsJsonObject(); String strObj = element.toString(); 
+31
Aug 25 '14 at 1:33 on
source share

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

So, first add the converter-scalars module converter-scalars file for your application.

 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' } 

Then create an instance of Retrofit as follows:

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

Now you can use the API declaration as follows:

 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); } 
+29
Aug 29 '15 at 7:30
source share

The answer can be much shorter than already mentioned, and does not require additional libraries:

In the declaration, use Response as follows:

 ... Callback<Response> callback); 

And when processing the response:

 @Override public void success(Response s, Response response) { new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes())) } 
+21
Jul 07 '15 at 14:58
source share

When @lordmegamax is fully responsible for the work, a much nicer solution that comes from

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

a project of other squares that is already compressed with retrofit , and therefore you do not need to add any new dependency, and it should be reliable:

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

ByteString - An unchanging sequence of bytes. For character data, String is fundamental. ByteString - String long-lost brother, which simplifies the processing of binary data as a value. This class is ergonomic: it can encode and decode itself as hex, base64 and UTF-8.

Full example:

 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); } } 
+4
May 30 '15 at 17:19
source share

This is what I did by putting it into the debugger. Note: this is for actually getting it inside the error callback, not for success callback.

You will see that the success type is found by calling retrofitError.getSuccessType() and returns an object of type Type

Then you can call retrofitError.getBodyAs(YourType.class) , which is all I need to do, because for me it is always the class that I expect.

Here is the answer with one liner:

 retrofitError.getBodyAs(retrofitError.getSuccessType()) 

Now, I have to point out that I don’t need to do anything similar with respect to the success callback, because it already works magically.

-one
Mar 23 '16 at 19:21
source share

To get a JSONObject or JSONArray call

you can create a custom factory or copy it here: https://github.com/marcinOz/Retrofit2JSONConverterFactory

-one
Apr 21 '16 at 13:35
source share



All Articles