質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

1回答

971閲覧

JSONをKotlin標準のコレクションに変換したい

shal0ne

総合スコア51

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2018/11/15 10:22

前提・実現したいこと

Kotlin標準のコレクションと、JSONを相互に変換したいです。
具体例としては、下記に記す通りMutableSet<Long>とJSONの相互変換をしたいです.

発生している問題・エラーメッセージ

JSONに変換したいKotlinのクラスを自作するドキュメントは結構見つかったのですが、既存のクラスに変換する方法がわかりません。

Interface MutableSet does not have constructor

該当のソースコード

Kotlin

1//Kotlin->JSON 2 val inputLongSet= mutableSetOf<Long>(0,1,2,3,4,5,6,7,8,9) 3 val longSetString = Gson().toJson(inputLongSet) 4//JSON->Kotlin 5 val setString= longSetString 6 //fromJson()の第二引数でエラーが発生 7 val longSet:MutableSet<Long> = Gson().fromJson(setString, MutableSet<Long>)

試したこと

  1. JavaのArrayListのものが見つかったので見よう見まねでKotlinのMutableSetに適用してみましたが、うまくいきませんでした。

  2. KotlinのMutableListはInterfaceでインスタンス化できないnoga原因ぽかったので、mutableSetOfでどんなインスタンスを作っているのか調べてみましたが、力尽きました。

  3. fromJsonの第二引数にMutableSet<Long>の代わりに

Kotlin

1 val type: Type = TypeToken<MutableSet<Long>> {}.type

上記のようなtypeというものを用意して試してみたのですが、

Can not access '<init>' it is public/*package*/ in TypeToken

というメッセージが出て、うまくいかないです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

訂正: 型引数に指定した具象型を得るための条件(A)が不適切だったので訂正しました。また若干の誤記を訂正しました。


KotlinにせよJavaにせよメソッドの引数に指定できるのは「値」です。しかし「型名」は文法上特別な意味を持つ構文要素でしてそれは「値」にはなれません。型が指定できる特別な場所にしか書けないのです。

val a: Intとかclass MyClass : MutableSet<Long> ...とはかけますが、
val a = IntとかsomeFunction(Int)とは書けません。Intは型名であって「値」ではないからです。

そこで型に関する情報を「値としてやりとりする」ために登場するのがjava.lang.Class, kotlin.reflect.KClass, java.lang.reflect.Typeなどの型のメタ情報を保持するクラス(メタクラス)です。

Intに関する型情報を渡すなら

Int.javaClass => Class<Int>のインスタンス
Int::class => KClass<Int>のインスタンス

などと記述できます。しかしJVMのtype erasureの仕様のためgenericsの場合単純にMutableSet<Long>.javaClassのようには書けません。また

kotlin

1val o = mutableSetOf(1L, 2L) 2val clazz = o.javaClass

としても、clazzがMutableSetを継承する具象クラスであることはわかるのですが、その具象クラスの型引数に何の型が指定されていたかまではわからない仕様になっています。

そこで実際にどのような型引数が用いられているか実行時にわかるようなメタクラスとしてTypeを用います。ただし、型引数の実際の型がわかるためには条件があり

A) 「型引数に具象型を指定したGenericsを継承したクラスを定義し、そのクラス情報からsuperclassとして参照したときのTypeからしか型引数がわからない」(訂正:元の説明には余計な条件を含んでいましたので記述を改めました)

ということになっています。言葉だとわかりにくいですね。

Kotlin

1class A : MutableSet<Long> 2... 3val typeOfSuperA = MutableSetOfLong::class.supertypes[0] 4println(typeOfSuperA) // ==> kotlin.collections.HashSet<kotlin.Long> /* = java.util.HashSet<kotlin.Long> */

gsonで用意されているTypeTokenはそのままではAの制約のためtypeを参照しても期待通りの内容になってません。正しくはTypeTokenの派生クラスを定義しそのtypeをを参照しなくてはならないのです。そういう事情があるためTypeTokenは直接インスタンスを生成できないよう「コンストラクターがprotectedになっている」のです。

Can not access '<init>' it is public/package/ in TypeToken

このエラーが出るのはそういう理由です。

結局どう書けばよいかといえば、TypeTokenの派生クラスを定義すればよいのですが、そうしたものに一々名前を付けるのは煩雑なので、無名クラスのインスタンス化の構文を使うのが一番簡潔です。

val type: Type = object: TypeToken<MutableSet<Long>>() {}.type

ということで

val longSet:MutableSet<Long> = Gson().fromJson(setString, object: TypeToken<MutableSet<Long>>() {}.type)

と記述するとお望みの動作になります。しかし・・・せっかく型推論が導入されているKotlinで同じ型MutableSet<Long>を代入文の左辺にも右辺にも書かなければならないのは業腹です。

それについてはstackoverflowにraified type parameterを使ったうまい方法がありました。

https://stackoverflow.com/questions/33381384/how-to-use-typetoken-generics-with-gson-in-kotlin

Kotlin

1// まずこういう関数を定義しておく 2inline fun <reified T> Gson.fromJson(json: String) = 3 this.fromJson<T>(json, object: TypeToken<T>() {}.type) 4 5// 使い方 6fun foo() { 7 val setString = "[1, 2]" 8 9 val longSet1 = Gson().fromJson<MutableSet<Long>>(setString) 10 // or 11 val longSet2: MutableSet<Long> = Gson().fromJson(setString) 12}

これなら概ね満足できると思います。

ただ一点「自前でこの関数を定義しなくてはならない」という不満だけが残ります。自分はJackson(のkotlin対応ライブラリーのjackson-module-kotlin)にはこれににたextensionが用意されているのを見つけました。しかしGsonやそのKotlin用ラッパーの中にこうした便利な定義があるかどうかまでは残念ながらわかりません。


その他:

mutableSetOfでどんなインスタンスを作っているのか

こうすればわかります。

println(mutableSetOf(1L)::class) => java.util.LinkedHashSet

投稿2018/11/15 17:53

編集2018/11/16 14:21
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shal0ne

2018/11/16 06:25

回答ありがとうございます。 疑問点が片っ端から解消されていて、とても読みやすかったです。 はるか昔に学んだような学んでないような::classの知識が必要になる日が今日になるとは思いませんでした。 () {}の部分が全くわからなくてパニクっていたのですが、無名クラスの宣言をしていたのですね。 また、性格上こだわってしまうコードの美しさにも配慮されていて、言葉が出ないです。 調べてみるとGsonよりもJacksonのほうが速そうなので、ぜひ使いたいと思います。 かなり手詰まりだったので、大変助かりました。 丁寧な解説ありがとうございました。
KSwordOfHaste

2018/11/16 14:25

他の新しい言語に比べやぼったさが目立つ感があるJavaではありますが、ことJVM言語を扱う上では重要な概念のベースとなってます。中でも(本文にかいたAのように)type erasureはJVM言語への影響が強くかつ難解なものの一つではないでしょうか。こういうものはJavaの仕様を踏まえながら読み解くと理解の助けになると思います。 ところで本部の(A)の条件が冗長だった(必要でない条件が含まれていた)ため訂正させていただきました。すみませんでした。
shal0ne

2018/11/17 02:47

訂正ありがとうございます。 確かになぜそうなるのかについて、自然なものではないですね。 そこまで詳しく書いてあったかは覚えていませんが、今一度本を読み返してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問