可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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