web-dev-qa-db-ja.com

gsonを使用してデフォルトの逆シリアル化を呼び出す方法

「フィールド」フィールドを持つjsonを取得します。
「フィールド」にデータがある場合、オブジェクトでもある他の多くの(約20)フィールドを持つオブジェクトがあります。問題なく逆シリアル化できます。
しかし、「フィールド」にデータがない場合、それは空の配列です(クレイジーだとわかっていますが、それはサーバーからの応答であり、変更できません)。このようなもの:

空の場合:

"extras":[

]

いくつかのデータがあります:

"extras": {
    "22":{ "name":"some name" },
    "59":{ "name":"some other name" },
    and so on...
}

したがって、データがない場合(空の配列)、明らかに例外が発生します

Caused by: com.google.gson.JsonSyntaxException: Java.lang.IllegalStateException:
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 4319

カスタムJavaDeserializerを使用しようとしました:

public class ExtrasAdapter implements JsonDeserializer<Extras> {
    @Override
    public Extras deserialize(JsonElement json, Type typeOf,
        JsonDeserializationContext context) throws JsonParseException {
        try {
            JsonObject jsonObject = json.getAsJsonObject();
            // deserialize normally

            // the following does not work, as it makes recursive calls
            // to the same function
            //return context.deserialize(jsonObject,
            //                       new TypeToken<Object>(){}.getType());
        } catch (IllegalStateException e) {
            return null;
        }
    }
}

私はjsonを次のように読みます

Gson gsonDecoder = new GsonBuilder().registerTypeAdapter(Extras.class, new ExtrasAdapter();
// httpResponse contains json with extras filed. 
Reader reader = new InputStreamReader(httpResponse.getEntity().getContent());
Extras response = gsonDecoder.fromJson(reader, Extras.class);

20個のフィールドすべてを手動で逆シリアル化するのではなく(これがオプションであることはわかっています)、context.defaultDeserialize()などを呼び出すだけです。
もう一度:通常のjsonの逆シリアル化、カスタムオブジェクトの作成、カスタムTypeAdapters、カスタムJavaDeserializersの登録に問題はありません。それはすべてすでに機能しています。データを処理するために必要なソリューションは、ARRAYとOBJECTの両方です。
助けてくれてありがとう。

======================


ジョーイの答えは完璧に機能します。そうです、私が探していたものです。ここにコードを投稿します。

public class SafeTypeAdapterFactory implements TypeAdapterFactory {
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<T>() {
            public void write(JsonWriter out, T value) throws IOException {
                try {
                    delegate.write(out, value);
                } catch (IOException e) {
                    delegate.write(out, null);
                }
            }
            public T read(JsonReader in) throws IOException {
                try {
                    return delegate.read(in);
                } catch (IOException e) {
                    Log.w("Adapter Factory", "IOException. Value skipped");
                    in.skipValue();
                    return null;
                } catch (IllegalStateException e) {
                    Log.w("Adapter Factory", "IllegalStateException. Value skipped");
                    in.skipValue();
                    return null;
                } catch (JsonSyntaxException e) {
                    Log.w("Adapter Factory", "JsonSyntaxException. Value skipped");
                    in.skipValue();
                    return null;
                }
            }
        };
    }
}
28
Anton Rutkevich

GSON> = 2.2.1を使用して、 TypeAdapterFactory クラスを探してみてください。

これにより、オブジェクトを逆シリアル化する前にオブジェクトを検査し、再帰を回避しながらカスタムコードを適用することができます。

使用できる getDelegateAdapter の例を次に示します。

22
Joey
public class ExtrasAdapter implements JsonDeserializer<Extras> {
@Override
public Extras deserialize(JsonElement json, Type typeOf, 
              JsonDeserializationContext context) throws JsonParseException {
    try {
        JsonObject jsonObject = json.getAsJsonObject();
        return new Gson().fromJson(jsonObject , Extras.class); // default deserialization

    } catch (IllegalStateException e) {
        return null;
    }
}
13
Rahul

遅れて来る人にとっては、この問題を解決するためにTypeAdapterを実装する必要はありませんが、そうすることは完全に有効な解決策です。

この問題に対する答えは、実際には元の質問にあります。

public class ExtrasAdapter implements JsonDeserializer<Extras> {

@Override
public Extras deserialize(JsonElement json, Type typeOf, 
          JsonDeserializationContext context) throws JsonParseException {
    try {
        JsonObject jsonObject = json.getAsJsonObject();
        // deserialize normally

        // the following does not work, as it makes recursive calls 
        // to the same function 
        //return context.deserialize(jsonObject, new TypeToken<Object>(){}.getType());
    } catch (IllegalStateException e) {
        return null;
    }
}

コメントアウト

return context.deserialize(jsonObject, new TypeToken<Object>(){}.getType());

ほとんど解決策です。問題は2つあります。まず、jsonObjectは、この関数に最初に渡された正確なオブジェクトです。

JsonObject jsonObject = json.getAsJsonObject();

したがって、それをcontext.deserialize()に渡すと、再帰が作成され、最終的にはOOMが作成されます。ここでの解決策は、jsonObject内のオブジェクトを解析することです。

これは2番目の問題につながります。それはここで2つのことが混ざり合っているということです。 「Extras」はオブジェクト型であり、おそらくそれを裏付ける具象クラス(そしておそらく空の配列)を持っています。 「エクストラ」は地図です。 「Extra」を「Extras」として解析しようとしても機能しません。そのために、私は「エクストラ」の次の定義を提案します。

public class Extras {
    Map<String, Map<String, String>> extras;
    // you could also create a concrete class for "Extra"
    //and have this be a Map<String, Extra>
}

この場合、context.deserializeを使用して解決するために問題は簡単になります。

上で述べたように、TypeAdatperはこの問題の完全に有効なソリューションです。私はそれがあなたが必要とする以上のものだと信じています。

3
MrMannWood

空の配列をnullに逆シリアル化する必要性に基づいて、代替のTypeAdapterを作成しましたが、指定したクラスに対してのみです。

class EmptyArraysAsNullTypeAdapterFactory @Inject constructor() : TypeAdapterFactory {

companion object {

    // Add classes here as needed
    private val classesAllowedEmptyArrayAsNull = arrayOf(Location::class.Java,
                                                         Results::class.Java)

    private fun isAllowedClass(rawType: Class<*>): Boolean {
        return classesAllowedEmptyArrayAsNull.find { rawType.isAssignableFrom(it) } != null
    }
}

override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
    val delegate = gson.getDelegateAdapter(this, type)

    val rawType = type.rawType as Class<T>

    return object : TypeAdapter<T>() {

        override fun write(out: JsonWriter, value: T?) {
            delegate.write(out, value)
        }

        override fun read(reader: JsonReader): T? {
            return if (reader.peek() === JsonToken.BEGIN_ARRAY && isAllowedClass(rawType)) {
                reader.beginArray()

                // If the array is empty, assume it is signifying null
                if (!reader.hasNext()) {
                    reader.endArray()
                    null
                } else {
                    throw JsonParseException("Not expecting a non-empty array when deserializing: ${type.rawType.name}")
                }

            } else {
                delegate.read(reader)
            }
        }
    }
}

}

0