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)
関連記事:
