Подтвердить что ты не робот

Как вернуть 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, но не могу понять, как это сделать.

4b9b3361

Ответ 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())

Теперь, я должен отметить, что мне не нужно делать что-либо подобное в отношении обратного вызова успеха, потому что он уже работает магически.