How can I return String or JSONObject from asynchr

2019-01-07 07:54发布

问题:

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.

回答1:

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.



回答2:

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


回答3:

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


回答4:

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


回答5:

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


回答6:

Here is what I did, after poking around in the debugger. Note: this is for actually getting it inside an error callback, not the success callback.

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

You can then call retrofitError.getBodyAs(YourType.class) which is all I needed to do because for me its always the class I expect it to be.

Here is the one-liner answer:

retrofitError.getBodyAs(retrofitError.getSuccessType())

Now, I'll note that I dont have to do anything like this regarding the success callback because it's already working magically.



回答7:

To get Call JSONObject or JSONArray

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