web-dev-qa-db-ja.com

GSONは間違ったタイプの要素を無視します

オンラインWebサービスとの通信にRetrofit(OkHttpおよびGSONと組み合わせて)を使用しています。 Webサービスには、次のように、すべての応答を囲むデフォルトのラッパーがあります。

{  
  "resultCode":"OK",
  "resultObj":"Can be a string or JSON object / array",
  "error":"",
  "message":""
}

この例では、resultCodeOKまたはNOのいずれかになります。さらに、errormessageには、リクエストの処理中にエラーが発生した場合にのみ内容が含まれます。最後になりましたが、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でした

さて、最後に、私の質問は次のとおりです。

  1. 期待される型と一致しない場合、プロパティを無視する(別名「nullify」)必要があることをGSONに伝える(簡単な)方法はありますか?
  2. 空の文字列をnullとして扱うようにGSONに指示できますか?
17
Arno Moonen

次のようにモデルを定義します。

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の場合。

これで問題が解決することを願っています=)

21
Heikki Hautala

私は同様の問題を抱えていて、最終的に次の解決策を思いつきました:

要素を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

}

元の投稿: https://stackoverflow.com/a/34178082/3708094

8
RWIL

まず、これはあなたが扱っている悪いAPI設計です。 :

カスタム---(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();
3
Paul Burke

このコードも使用できます。

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
yourObject = objectMapper.readValue(jsonString, <ClassName>.class);
1
Anuj Jindal

応答ポジョを再利用しています。

1つの応答ではStringであり、別の応答ではList<MajicalModel> magicalFieldであるため、1つの解析が失敗しました。

com.google.gson.JsonElement magicalField;に変更しました。このようにして、生のjsonを解析し、型の不一致も無視します。

1
Qamar4P