Как вернуть String или JSONObject из асинхронного обратного вызова с помощью Retrofit?
Например, вызов
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, но не могу понять, как это сделать.
Ответы
Ответ 1
Я понял это. Это неловко, но это было очень просто... Временное решение может быть следующим:
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()));
}
}
}
Если вы знаете, как улучшить этот код, не стесняйтесь писать об этом.
Ответ 2
Возможным решением было бы использовать JsonElement
как тип Callback
(Callback<JsonElement>
). В исходном примере:
api.getUserName(userId, new Callback<JsonElement>() {...});
В методе успеха вы можете преобразовать JsonElement
в String
или JsonObject
.
JsonObject jsonObj = element.getAsJsonObject();
String strObj = element.toString();
Ответ 3
Retrofit 2.0.0-beta3 добавляет модуль converter-scalars
, который предоставляет Converter.Factory
для преобразования String
, 8 примитивных типов, и 8 бокс-примитивных типов как тела text/plain
. Установите это перед вашим нормальным преобразователем, чтобы избежать прохождения этих простых скаляров через, например, конвертер JSON.
Итак, сначала добавьте модуль 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("/users/{id}/name")
Call<String> userName(@Path("userId") String userId);
// RxJava version
@GET("/users/{id}/name")
Observable<String> userName(@Path("userId") String userId);
}
Ответ 4
Ответ может быть намного короче, чем уже упоминалось, и не требует дополнительных библиотек:
В объявлении используйте Response
следующим образом:
... Callback<Response> callback);
И при обработке ответа:
@Override
public void success(Response s, Response response) {
new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes()))
}
Ответ 5
Когда @lordmegamax полностью отвечает за работу, гораздо приятнее решение, которое исходит от
Okio - это новая библиотека, которая дополняет java.io и java.nio
проект других квадратов, который уже сжат с помощью retrofit
, и поэтому вам не нужно добавлять какую-либо новую зависимость, и она должна быть надежной:
ByteString.read(body.in(), (int) body.length()).utf8();
ByteString - неизменная последовательность байтов. Для символьных данных String является фундаментальным. ByteString - String long-lost brother, что упрощает обработку двоичных данных в качестве значения. Этот класс эргономичен: он умеет кодировать и декодировать себя как hex, base64 и 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);
}
}
Ответ 6
Вот что я сделал, просунув в отладчик. Примечание: это для фактического получения его внутри обратного вызова ошибки, а не для обратного вызова успеха.
Вы увидите, что тип успеха найден, вызывая retrofitError.getSuccessType()
и возвращает и объект типа Type
Затем вы можете вызвать retrofitError.getBodyAs(YourType.class)
, что все, что мне нужно сделать, потому что для меня всегда это класс, который я ожидаю.
Вот ответ с одним лайнером:
retrofitError.getBodyAs(retrofitError.getSuccessType())
Теперь, я должен отметить, что мне не нужно делать что-либо подобное в отношении обратного вызова успеха, потому что он уже работает магически.
Ответ 7
Чтобы получить вызов JSONObject или JSONArray
вы можете создать пользовательский factory или скопировать его здесь: https://github.com/marcinOz/Retrofit2JSONConverterFactory