酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Realm JavaでnullableなInt配列のjsonファイルを読み込む

メモ。あとで書き足す(予定)。

問題

Realm Javaではjsonファイルを読み込んでそのままRealmのオブジェクトをすることができます。しかし、intやstringといったプリミティブ型配列の場合にはそのままダイレクトにRealmに格納することができません。

JSON APIは数値や文字列型のようなプリミティブ型(オブジェクト型ではない)の配列を含むことがあります。そのような配列を表現することは現時点のRealmではサポートされていません。

JSON APIを変更するのが不可能な場合、カスタムTypeAdapterを用いて、JSONのプリミティブ型をRealmオブジェクトに自動でマッピングするようにカスタマイズすることができます。

Realm Java 3.3.0

Realm JavaでnullableなInt配列のjsonファイルを読み込んでみました。対象となるフォーマットは、例えば下記のような場合です。

{
  "contents": [
    {
      "code": 1,
      "weapons": null,
    },
    {
      "code": 9,
      "weapons": [26],
    }
  ],
  "updated": "2016-07-19T19:00:00+09:00"
}

過去に似たような件でハマっていたことはあったのですが、フィールドがnot-nullableなこともあり"weapons": null"weapons": [26]が入り混じることはありませんでした。

このファイルを読み込ませるとgsonに怒られ、例外が吐かれました。

W/System.err: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was NULL at line 1 column 126 path $[0].weapons
W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)
W/System.err:     at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
W/System.err:     at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
W/System.err:     at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)

解決

IntObject.java

public class IntObject extends RealmObject {
    private int value;

    public IntObject() {
    }

    public IntObject(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

IntObjectTypeAdapter.java

配列として処理をし始める前にJsonToken.NULLかどうか確かめる処理を追加しました。

public class IntObjectTypeAdapter extends TypeAdapter<RealmList<IntObject>> {

    @Override
    public void write(JsonWriter out, RealmList<IntObject> value) throws IOException {

    }

    @Override
    public RealmList<IntObject> read(JsonReader in) throws IOException {
        RealmList<IntObject> list = new RealmList<>();
        if (in != null) {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return list;
            }

            in.beginArray();
            while (in.hasNext()) {
                list.add(new IntObject(in.nextInt()));
            }
            in.endArray();
        }
        return list;
    }
}

Realmへのオブジェクトパース部分

final Type token = new TypeToken<RealmList<IntObject>>() { }.getType();
final Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getDeclaringClass().equals(RealmObject.class);
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}).registerTypeAdapter(token, new IntObjectTypeAdapter()).create();

List<Weapon> objects = gson.fromJson(contents.toString(), new TypeToken<List<Weapon>>() {
}.getType());
realm.copyToRealm(objects);