「フィールド」フィールドを持つ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;
}
}
};
}
}
GSON> = 2.2.1を使用して、 TypeAdapterFactory クラスを探してみてください。
これにより、オブジェクトを逆シリアル化する前にオブジェクトを検査し、再帰を回避しながらカスタムコードを適用することができます。
使用できる getDelegateAdapter の例を次に示します。
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;
}
}
遅れて来る人にとっては、この問題を解決するために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はこの問題の完全に有効なソリューションです。私はそれがあなたが必要とする以上のものだと信じています。
空の配列を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)
}
}
}
}
}