MoshiでJSONをパース(JSONの記述⇔データクラスのオブジェクト)する場合に、一般クラスは未対応です。
例えば、データクラス以外の、ユーザ定義のクラスはパース出来ません。
対応させるためには、そのクラスのカスタムJsonAdapterを作成します。
そして、相互変換する方法をプログラマー側で定義します。
カスタムJsonAdapterの作成方法をまとめます。
※環境:Android Studio Ladybug Feature Drop | 2024.2.2
Kotlin 2.0.0
Moshi 1.15.2
目次
標準で対応する型、しない型
MoshiでJSONをパース(JSONの記述⇔データクラスのオブジェクト)する場合に、標準で対応する型は次の通りです。
- ・Int,Long,Float,Double,Booleanなど(プリミティブ型)
- ・String(文字列)
- ・Array(配列)
- ・List,Map,Set(コレクション型)
- ・Enum(列挙型)
- ・データクラス
上記以外の、一般クラス(ユーザ定義のクラスを含む)は未対応です。
例えば、次のようなクラスは標準でパース出来ません。
class TimeLog(hour: Int, min: Int, sec: Int) { val hh: Int = hour val mm: Int = min val ss: Int = sec override fun toString(): String { return "%s:%s:%S".format(hh, mm, ss) } }
一般クラスをパースするには、そのクラスのカスタムJsonAdapterが必要です。
カスタムJsonAdapterの作成
JsonAdaperは「MoshiがJSONをパースする際に呼び出される関数」を持ちます。
public abstract class JsonAdapter<T> { public JsonAdapter() { } ... public abstract T fromJson(JsonReader var1) throws IOException; ... public abstract void toJson(JsonWriter var1, @Nullable T var2) throws IOException; ... }
「JSONの記述⇒データクラスのオブジェクト」で呼び出し
JsonAdapter#toJson( )「JSONの記述⇐データクラスのオブジェクト」で呼び出し
ですので、カスタムJsonAdapterは、この抽象クラスJsonAdapterを継承して、相互変換の方法を#fomJsonと#toJsonへ実装します。
class LogTimeJsonAdapter: JsonAdapter<TimeLog>() { override fun fromJson(reader: JsonReader): TimeLog { val _hhmmss = reader.nextString().split(":") val _hh = _hhmmss[0].toInt() val _mm = _hhmmss[1].toInt() val _ss = _hhmmss[2].toInt() return TimeLog(_hh, _mm, _ss) } override fun toJson(writer: JsonWriter, value: TimeLog?) { writer.value("%s:%s:%s".format(value?.hh, value?.mm, value?.ss)) } }
カスタムJsonAdapterの使用
カスタムJsonAdapterはMoshi.Builder#add( )で登録します。
相互変換を行う際に、アダプターは登録された中から検索されます。そして、対象のアダプターがあれば使用されます。
※MoshiでJSONをパースする方法については「MoshiでJSONをパース」を参照
class TimeLog(hour: Int, min: Int, sec: Int) { val hh: Int = hour val mm: Int = min val ss: Int = sec override fun toString(): String { return "%s:%s:%S".format(hh, mm, ss) } } @JsonClass(generateAdapter = true) data class Person( val id: Int, val name: String, @Json(name = "position") val post: String, // JSONのキーに別名を定義 val times: TimeLog )
JSON⇒データクラス変換
JsonAdapter#fromJsonを呼び出します。
val _jPerson = """ { "id":1, "name":"Android", "position":"Manager", "times":"8:55:22" } """.trimIndent() val _moshi = Moshi.Builder() .add(LogTime::class.java, LogTimeJsonAdapter()) .build() val _adapter = _moshi.adapter(Person::class.java) val _oPerson = _adapter.fromJson(_jPerson) Log.i(TAG, "(Moshi) Person Obj = ${_oPerson}")
(Moshi) Person Obj = Person8(id=1, name=Android, post=Manager, times=8:55:22)
データクラス⇒JSON変換
JsonAdapter#toJsonを呼び出します。
val _moshi = Moshi.Builder() .add(LogTime::class.java, LogTimeJsonAdapter()) .build() val _adapter = _moshi.adapter(Person::class.java) val _oPerson = Person( id = 2, name = "Droid", post = "Staff", times = LogTime(12, 2, 0) ) val _jPerson = _adapter.toJson(_oPerson) Log.i(TAG, "(Moshi) Person Json = ${_jPerson}")
(Moshi) Person Json = {"id":2,"name":"Droid","position":"Staff","times":"12:2:0"}
アダプターのファクトリー化
Moshi.Builder#add( )でJsonAdapterを登録する際に、ファクトリークラスで登録することが出来ます。
アダプターをファクトリー化するために、JsonAdapter.Factoryインターフェースが用意されています。
public abstract class JsonAdapter<T> { ... public interface Factory { @CheckReturnValue @Nullable JsonAdapter<?> create(Type var1, Set<? extends Annotation> var2, Moshi var3); } }
このインターフェースを継承して、ファクトリークラスを作成します。
専用ファクトリー
カスタムJsonAdapter専用のファクトリークラスです。
val LogTimeJsonAdapterFactory = LogTimeJsonAdapter.FACTORY class LogTimeJsonAdapter: JsonAdapter<LogTime>() { companion object { val FACTORY: Factory = object: Factory { override fun create( type: Type, annotations: Set<out Annotation>, moshi: Moshi ): JsonAdapter<*>? { if (type === LogTime::class.java) return LogTimeJsonAdapter() return null } } } override fun fromJson(reader: JsonReader): LogTime { val _hhmmss = reader.nextString().split(":") val _hh = _hhmmss[0].toInt() val _mm = _hhmmss[1].toInt() val _ss = _hhmmss[2].toInt() return LogTime(_hh, _mm, _ss) } override fun toJson(writer: JsonWriter, value: LogTime?) { writer.value("%s:%s:%s".format(value?.hh, value?.mm, value?.ss)) } }
Moshiはアダプターを検索する際にファクトリーを見つけると、Factory#create( )を実行して、目的のアダプターを作成しようとします。作成できれば、そのアダプターを使います。
val _jPerson = """ { "id":1, "name":"Android", "position":"Manager", "times":"8:55:22" } """.trimIndent() val _moshi = Moshi.Builder() .add(LogTimeJsonAdapterFactory) .build() val _adapter = _moshi.adapter(Person::class.java) val _oPerson = _adapter.fromJson(_jPerson) Log.i(TAG, "(Moshi) Person Obj = ${_oPerson}")
(Moshi) Person Obj = Person8(id=1, name=Android, post=Manager, times=8:55:22)
複数ファクトリー
複数のカスタムJsonAdapterをまとめたファクトリークラスです。
object UserJsonAdaptersFactory : JsonAdapter.Factory { override fun create( type: Type, anotations: Set<Annotation?>, moshi: Moshi ): JsonAdapter<*>? { if (type === LogTime::class.java) return LOGTIME_JSON_ADAPTER // 同様に複数のAdapterを作成可能 // Adapter XXXの作成 // Adapter YYYの作成 // Adapter ZZZの作成 return null } val LOGTIME_JSON_ADAPTER = object : JsonAdapter<LogTime>() { override fun fromJson(reader: JsonReader): LogTime { val _hhmmss = reader.nextString().split(":") val _hh = _hhmmss[0].toInt() val _mm = _hhmmss[1].toInt() val _ss = _hhmmss[2].toInt() return LogTime(_hh, _mm, _ss) } override fun toJson(writer: JsonWriter, value: LogTime?) { writer.value("%s:%s:%s".format(value?.hh, value?.mm, value?.ss)) } } // ここに、複数のAdapterを記述可能 // Adapter XXXの記述 // Adapter YYYの記述 // Adapter ZZZの記述 }
Moshiはアダプターを検索する際にファクトリーを見つけると、Factory#create( )を実行して、目的のアダプターを作成しようとします。作成できれば、そのアダプターを使います。
val _jPerson = """ { "id":1, "name":"Android", "position":"Manager", "times":"8:55:22" } """.trimIndent() val _moshi = Moshi.Builder() .add(UserJsonAdaptersFactory) .build() val _adapter = _moshi.adapter(Person::class.java) val _oPerson = _adapter.fromJson(_jPerson) Log.i(TAG, "(Moshi) Person Obj = ${_oPerson}")
(Moshi) Person Obj = Person8(id=1, name=Android, post=Manager, times=8:55:22)
関連記事: