オンラインWebサービスとの通信にRetrofit(OkHttpおよびGSONと組み合わせて)を使用しています。 Webサービスには、次のように、すべての応答を囲むデフォルトのラッパーがあります。
{
"resultCode":"OK",
"resultObj":"Can be a string or JSON object / array",
"error":"",
"message":""
}
この例では、resultCode
はOK
またはNO
のいずれかになります。さらに、error
とmessage
には、リクエストの処理中にエラーが発生した場合にのみ内容が含まれます。最後になりましたが、resultObj
には、呼び出しの実際の結果が含まれます(これは例では文字列ですが、一部の呼び出しはJSON配列またはJSONオブジェクトを返します)。
このメタデータを処理するために、次のようなジェネリッククラスを作成しました。
public class ApiResult<T> {
private String error;
private String message;
private String resultCode;
private T resultObj;
// + some getters, setters etcetera
}
また、resultObj
で時々与えられる応答を表すクラスを作成し、Retrofitで使用するインターフェイスを定義しました。これは次のようになります。
public interface SomeWebService {
@GET("/method/string")
ApiResult<String> someCallThatReturnsAString();
@GET("/method/pojo")
ApiResult<SomeMappedResult> someCallThatReturnsAnObject();
}
リクエストが有効である限り、これはすべて正常に機能します。ただし、サーバー側でエラーが発生した場合でも、String型のresultObj
が返されます。これにより、someCallThatReturnsAnObject
がRetrofitRestAdapter/GSONライブラリ内でクラッシュし、次のようなメッセージが表示されます。
retrofit.RetrofitError:com.google.gson.JsonSyntaxException:Java.lang.IllegalStateException:
BEGIN_OBJECTが必要ですが、行1、列110のパス$ .resultObjでSTRINGでした
さて、最後に、私の質問は次のとおりです。
次のようにモデルを定義します。
public class ApiResult {
private String error;
private String message;
private String resultCode;
private MyResultObject resultObj;
}
次に、TypeAdapterFactory for MyResultObjectを作成します。
public class MyResultObjectAdapterFactory implements TypeAdapterFactory {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType()!= MyResultObject.class) return null;
TypeAdapter<MyResultObject> defaultAdapter = (TypeAdapter<MyResultObject>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new MyResultObjectAdapter(defaultAdapter);
}
public class MyResultObjectAdapter extends TypeAdapter<MyResultObject> {
protected TypeAdapter<MyResultObject> defaultAdapter;
public MyResultObjectAdapter(TypeAdapter<MyResultObject> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
@Override
public void write(JsonWriter out, MyResultObject value) throws IOException {
defaultAdapter.write(out, value);
}
@Override
public MyResultObject read(JsonReader in) throws IOException {
/*
This is the critical part. So if the value is a string,
Skip it (no exception) and return null.
*/
if (in.peek() == JsonToken.STRING) {
in.skipValue();
return null;
}
return defaultAdapter.read(in);
}
}
}
最後に、登録MyResultObjectAdapterFactory for Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyResultObjectAdapterFactory())
.create();
さて、ApiResult jsonをそのGsonオブジェクトで逆シリアル化すると、resultObjが設定されますnullstringの場合。
これで問題が解決することを願っています=)
私は同様の問題を抱えていて、最終的に次の解決策を思いつきました:
要素をString
またはArray
に解析する代わりに、単純なJava.lang.Objectにデータを格納してみてください
これにより、解析がクラッシュしたり、例外がスローされたりするのを防ぎます。
例えば。 GSONアノテーションを使用すると、モデルのプロパティは次のようになります。
@SerializedName("resultObj")
@Expose
private Java.lang.Object resultObj;
次に、実行時にデータにアクセスするときに、resultObj
プロパティがString
のインスタンスであるかどうかを確認できます。
if(apiResultObject instanceof String ){
//Cast to string and do stuff
} else{
//Cast to array and do stuff
}
カスタム---(JsonDeserializer を使用してこのケースを処理できます。
レトロフィットに登録します。
MyJsonDeserializer deserializer = new MyJsonDeserializer()).create();
final Gson gson = new GsonBuilder().registerTypeAdapter(ApiResult.class, deserializer);
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.setConverter(new GsonConverter(gson))
.build();
このコードも使用できます。
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
yourObject = objectMapper.readValue(jsonString, <ClassName>.class);
応答ポジョを再利用しています。
1つの応答ではString
であり、別の応答ではList<MajicalModel> magicalField
であるため、1つの解析が失敗しました。
com.google.gson.JsonElement magicalField;
に変更しました。このようにして、生のjsonを解析し、型の不一致も無視します。