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.
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.
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();
Retrofit 2.0.0-beta3 ajoute un
converter-scalars
le module fournit unConverter.Factory
pour convertirString
, les 8 types primitifs et les 8 types primitifs encadrés entext/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);
}
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()))
}
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);
}
}