web-dev-qa-db-de.com

Wie kann ich String oder JSONObject von einem asynchronen Rückruf mit Retrofit zurückgeben?

Zum Beispiel anrufen

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

ursache:

retrofit.RetrofitError: retrofit.converter.ConversionException:
com.google.gson.JsonSyntaxException: Java.lang.IllegalStateException: 
Expected a string but was BEGIN_OBJECT at line 1 column 2

Ich denke, ich muss gson-Parsing in POJOs deaktivieren, kann aber nicht herausfinden, wie es geht.

45
lordmegamax

Ich habe es herausgefunden. Es ist peinlich, aber es war sehr einfach ... Temporäre Lösung könnte so aussehen:

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

Aber wenn Sie direkt einen Rückruf erhalten möchten, ist der bessere Weg Konverter zu verwenden.

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

Wenn Sie wissen, wie Sie diesen Code verbessern können, können Sie gerne darüber schreiben.

49
lordmegamax

Eine mögliche Lösung wäre die Verwendung von JsonElement als Callback-Typ (Callback<JsonElement>). In Ihrem ursprünglichen Beispiel:

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

In der Erfolgsmethode können Sie JsonElement entweder in String oder JsonObject konvertieren.

JsonObject jsonObj = element.getAsJsonObject();
String strObj = element.toString();
32
TPoschel

Retrofit 2.0.0-beta3 fügt ein converter-scalars Modul hinzu, das einen Converter.Factory Für die Konvertierung von String, den 8 primitiven Typen und den 8 boxed primitiven Typen als text/plain Bereitstellt. Körper. Installieren Sie dies vor Ihrem normalen Konverter, um zu vermeiden, dass diese einfachen Skalare beispielsweise durch einen JSON-Konverter geleitet werden.

Fügen Sie also zuerst das Modul converter-scalars Zur Datei build.gradle Für Ihre Anwendung hinzu.

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'
}

Erstellen Sie dann Ihre Retrofit -Instanz wie folgt:

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

Jetzt können Sie die API-Deklaration wie folgt verwenden:

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

Die Antwort ist möglicherweise viel kürzer als bereits erwähnt und erfordert keine zusätzlichen Bibliotheken:

Verwenden Sie in der Deklaration Response wie folgt:

... Callback<Response> callback);

Und während der Bearbeitung der Antwort:

@Override
public void success(Response s, Response response) {
    new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes()))
}
21
bgplaya

Wenn @lordmegamax Antwort vollständig funktioniert, gibt es eine viel schönere Lösung, die von kommt

Okio ist eine neue Bibliothek, die Java.io und Java.nio ergänzt

andere Quadrate projizieren, die bereits mit retrofit dicht sind, und daher müssen Sie keine neuen Abhängigkeiten hinzufügen, und es muss zuverlässig sein:

ByteString.read(body.in(), (int) body.length()).utf8();

ByteString ist eine unveränderliche Folge von Bytes. Für Zeichendaten ist String von grundlegender Bedeutung. ByteString ist der lange verlorene Bruder von String, mit dem es einfach ist, Binärdaten als Wert zu behandeln. Diese Klasse ist ergonomisch: Sie kann sich selbst als hex, base64 und UTF-8 codieren und decodieren.

Vollständiges Beispiel:

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);
  }
}
4
ar-g