Swift4 で JSON のパース / magicien 

Swift4からCodable、JSONDecoderが使えるようになって、JSONが簡単にパースできるようになった。
とはいえ、すんなり実装できなかった箇所もあったので、試した結果をいくつか記録しておこう。Gistを埋め込む練習ともいう。

基本的な使い方

struct に Codable を付けてあげるだけで勝手にパースしてくれる。structではなく、classでも同じ。
構造体とJSONのデータが食い違っている場合は、DecodingErrorが返ってくる。

必須でない項目、入れ子

Optional型にしておくと、対象のデータが無くてもエラーにならない。 配列はそのまま配列として定義すれば良い。object型の場合は、Codableな構造体かクラスを使う。

キー名を変える

JSONでは user_name だけど、Swiftでは userName という名前を使いたい、という場合、enum CodingKeys を設定する。
CodingKeysを書いた場合、caseに書いてあるものだけパースされる。

自分でパースする

大分大変だけど、init(for decoder: Decoder) を使って自分でパースもできる。
初期化時に何か計算とかしたい場合に。

デフォルト値を指定する

ここから、ちょっとハマったところ。
やりたかったことは次のとおり。
  • 値が存在しなかった場合のデフォルト値を指定したい
  • initは実装したくない
  • JSONとSwiftで同じキー名を使いたい
  • せっかくデフォルト値があるのだから、Optional型にしたくない
  • デフォルト値と同じ値を明示的に設定したかどうか分かるようにしたい
ちなみに、var name: String? = "NoName" としても、パース時に nil で上書きされてしまう。

パースするキーを後から追加する

これもかなり複雑。
Decoderは、CodingUserInfoKey を使ってユーザデータを受け渡しすることができる。
decoder.userInfo[ CodingUserInfoKey(rawValue: "hogehoge")! ] = "UserData" // こんな感じ
やりたかったことは、次のとおり。
  • JSONをパースして何かしらデータを生成するframeworkを作りたい
  • Codableの構造体はユーザには見えないようにしたい
  • ユーザがデコード時にパースするキーとデータ構造を動的に追加できるようにしたい
この例だと、"additionalInfo" の下の "website" の情報を取得している。
ぶっちゃけてしまうと、glTF の extensions/extras のデータをパースしたかった。

パース中のデータのパスを確認する

init(from decoder: Decoder) で、今自分がどのパスにいるのかによって処理を変えたい場合。
glTF の KHR_materials_common の対応に必要だった。ライトとかノードのマテリアルとか、用途も中身も違うものに同じ名前をつけるのやめてよ!

良い例が思いつかないので、実際のglTFのデータの抜粋をパースしてみる。

ジェネリクスとか、Computed Propertiesとか使えば、もうちょっとおしゃれになるかもしれない。

[Float]をSCNVector3・4に変換する

おまけ。encodeも真面目に書いた。
2017/08/22(Tue) 18:20:12