web-dev-qa-db-fra.com

Comment puis-je renvoyer String ou JSONObject à partir d'un rappel asynchrone à l'aide de Retrofit?

Par exemple, appeler

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

Je pense que je dois désactiver l'analyse gson dans les POJO, mais je ne sais pas comment le faire.

45
lordmegamax

Je l'ai compris. C'est embarrassant mais c'était très simple ... La solution temporaire peut être comme ceci:

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

Mais si vous voulez obtenir directement le rappel, le meilleur moyen est d'utiliser 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()));
    }
  }
}

Si vous savez comment améliorer ce code, n'hésitez pas à écrire à ce sujet.

49
lordmegamax

Une solution possible serait d'utiliser JsonElement comme type Callback (Callback<JsonElement>). Dans votre exemple d'origine:

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

Dans la méthode de réussite, vous pouvez convertir le JsonElement en String ou en JsonObject.

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

Retrofit 2.0.0-beta3 ajoute un converter-scalars le module fournit un Converter.Factory pour convertir String, les 8 types primitifs et les 8 types primitifs encadrés en text/plain corps. Installez-le avant votre convertisseur normal pour éviter de faire passer ces scalaires simples à travers, par exemple, un convertisseur JSON.

Donc, ajoutez d'abord converter-scalars module à build.gradle fichier pour votre 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'
}

Ensuite, créez votre instance Retrofit comme ceci:

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

Vous pouvez maintenant utiliser la déclaration API comme ceci:

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

La réponse peut être beaucoup plus courte que celle déjà mentionnée et ne nécessite aucune bibliothèque supplémentaire:

Dans la déclaration, utilisez Response comme suit:

... Callback<Response> callback);

Et lors de la gestion de la réponse:

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

Lorsque @lordmegamax répond complètement, il y a une solution beaucoup plus agréable qui vient de

Okio est une nouvelle bibliothèque qui complète Java.io et Java.nio

d'autres projets carrés qui sont déjà serrés avec retrofit et donc vous n'avez pas besoin d'ajouter de nouvelle dépendance et cela doit être fiable:

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

ByteString est une séquence immuable d'octets. Pour les données de caractères, la chaîne est fondamentale. ByteString est le frère perdu de String depuis longtemps, ce qui facilite le traitement des données binaires comme une valeur. Cette classe est ergonomique: elle sait se coder et se décoder en hexadécimal, en base64 et en UTF-8.

Exemple complet:

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