46

For example, calling

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 must disable gson parsing into POJOs but can't figure out how to do it.

chubao
  • 5,871
  • 6
  • 39
  • 64
lordmegamax
  • 2,684
  • 2
  • 26
  • 29
  • did you figure it out, I am getting the opposite error and trying to get an object back lol – Lion789 Mar 14 '14 at 20:50
  • 1
    @Lion789 No, I didn't yet :( I think that there is a way to return raw Response and then convert it to any object... – lordmegamax Mar 15 '14 at 20:35
  • I actually figured it out, I was sending something that was not being accepted, so if you are sending results back make sure it is just a string or what you specify, let me know if that helps. – Lion789 Mar 15 '14 at 20:45
  • I meant that I want to convert response body to String... And the body isn't actually a string at all... – lordmegamax Mar 17 '14 at 09:38
  • Well you will have to do that afterwards, you can't say the callback is going to be a string when it isn't in the callback convert it into a string – Lion789 Mar 17 '14 at 09:42
  • No problem but HOW? :) I can call [getBody()](http://square.github.io/retrofit/javadoc/retrofit/client/Response.html#getBody()) but what to do with [TypedInput](http://square.github.io/retrofit/javadoc/retrofit/mime/TypedInput.html) class? – lordmegamax Mar 17 '14 at 09:47

6 Answers6

49

I figured it out. It's embarrassing but it was very simple... Temporary solution may be like 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 to get directly Callback the Better 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 - please feel free to write about it.

lordmegamax
  • 2,684
  • 2
  • 26
  • 29
  • This wouldn't work and would throw an error: **retrofit.RetrofitError: No Retrofit annotation found. (parameter #2)** – IgorGanapolsky Apr 30 '15 at 16:48
  • It had worked on publication time. If you are sure that there is a problem, let me know. – lordmegamax May 07 '15 at 10:18
  • I am trying to build a custom converter for retrofit, but using the converter overrides my request too. I would like to have custom converter just for the response, how could I do that ? Thanks – rahul.ramanujam Jun 17 '15 at 16:51
  • That's a lot of boilerplate code. Is this really the only way to parse Retrofit responses? – IgorGanapolsky Feb 22 '16 at 18:01
  • Great solution. And @r7v the solution to your problem (and mine, which is exactly what brought me to this page) would be to subclass `GsonConverter` and just override the `fromBody()` method, leaving the `toBody()` method intact. – Richard Le Mesurier Sep 21 '16 at 15:10
32

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

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

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

JsonObject jsonObj = element.getAsJsonObject();
String strObj = element.toString();
TPoschel
  • 3,775
  • 2
  • 30
  • 29
  • 1
    This works but it's inefficient, as you have to convert the response to a JsonObject then back to a String. InputStream to String is much better, but a little trickier. – Matt Logan Feb 27 '15 at 18:55
30

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.

So, first add converter-scalars module to build.gradle 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 your Retrofit instance like this:

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 API declaration like this:

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);
}
floating cat
  • 678
  • 7
  • 11
21

The answer may be much shorter than already mentioned and doesn't require any additional libraries:

In declaration use Response as follows:

... Callback<Response> callback);

And while handling response:

@Override
public void success(Response s, Response response) {
    new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes()))
}
Tom Sabel
  • 3,935
  • 33
  • 45
bgplaya
  • 1,105
  • 15
  • 27
4

When @lordmegamax answer completely work there is much nicer solution which is come from

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

other squares project which already tight with retrofit and therefore you don't need to add any new dependency and it's have to be reliable:

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.

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);
  }
}
ar-g
  • 3,417
  • 2
  • 28
  • 39
-1

To get Call JSONObject or JSONArray

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

oziem
  • 470
  • 6
  • 15