JSONからCodable化されたstructを自動生成するツールを作った話


はじめに

皆さん,Codableは使用していますか? 便利ですよね。

CodableによってJSONが簡単に扱えるようになった一方で,まだまだ途上という段階で,扱いづらさを覚えている方もいらっしゃるのではないでしょうか。

中でも,

Codable: Tips and Tricks

One of the major downsides of Codable is that as soon as you need custom decoding logic - even for a single key - you have to provide custom everything: manually define all the coding keys, and implementing an entire init(from decoder: Decoder) throws initializer by hand. This isn’t ideal.

とあるように,現在 (Swift 4.2) のCodableでは,(initは省略できるようになったものの) 対応するCodingKeysや,JSONの返すKeyに不足がある場合JSONDecoder().decode()が失敗するなど,面倒な場面があります。

自分で管理するAPIならまだしも,公開されているAPIで「何がOptionalで欠損しているのかが分からない!」といった場面に遭遇した際にはかなり苦戦させられます。
手動で「?」を付けたり,CodingKeyを追加したり。イヤですよね。

そこで,その作業を自動化するツールを作成しました。

作成したもの

JSONtoCodable という,生のJSONからCodable化されたstructを吐き出すツールを作成しました。

Demoをダウンロードし,JSONテキストを貼り付けて「Generate!」ボタンを押すと,よしなに解析,整形されたstructテキストを出力します。

例えば,

{
    "user": {
        "Name": "Yuto Mizutani"
    },
    "lib": {
        "lib-name": "JSONtoCodable",
        "year": 2018,
        "version": "1.0.2",
        "released": "2018-09-22"
    },
    "text": "Hello, world!!"
}

というJSONを入力した場合,

public struct Result: Codable {
    public let user: User
    public let lib: Lib
    public let text: String

    public struct User: Codable {
        public let name: String

        private enum CodingKeys: String, CodingKey {
            case name = "Name"
        }
    }

    public struct Lib: Codable {
        public let libName: String
        public let year: Int
        public let version: String
        public let released: String

        private enum CodingKeys: String, CodingKey {
            case libName = "lib-name"
            case year
            case version
            case released
        }
    }
}

というstructが生成されます。

CodingKeysは不要な場合省略され,JSONのKeyに対応した順序でstructに変換されていきます。
また,"foo": ["bar", "baz"]といった配列にも対応し,もし配列がオブジェクトであった場合には,欠損値に応じてOptional型に変換されます。

出力された値を貼り付けるだけでResponseまたはModelが完成するため,(特に初心者の方の) 開発を助けること間違いなしです!

Q. 何で作ったの?

「JSON Codable Swift」 などとGitHubで検索すれば確かに存在するように思えます。

一方で,5以上のネストされたオブジェクトに対応していなかったり, オブジェクトが辞書型になってしまっていたり,CodingKeyが機能せず変数名に使えない名前が定義される など,イマイチな代物ばかりでした。

誰もこの ロックなJSON をパスできなかったのです!

JSONtoCodableはオブジェクトがいくつネストしていても変換できます。

Q. 対応している型は?

現在は JSONtoCodable#translations の基本型のみです。将来的にDateやURLにも対応できるようにする予定です。

Q. 使用する上での注意は?

現在,JSONのvalueがStringとNumber型,または配列と値による複数型であった場合,エラーとして失敗せず,Any型や重複されたimmutableとして出力できてしまうようになっています。Swift4.2ではまだCodable内のAny型はコンパイルエラーとなってしまいます。
これは,Swift側で実行時にJSON側のエラーと見分けられるようにするため,現在は意図的にそうしています。エラーとして返してしまうと,どういう結果であったのか,すぐ直せるようなJSONのミスをError型で書き消してしまうのを防ぐためです。

Q. このJSON上手くいかないんだけど? / なんかコード汚ないんだけど?

上手くいかないケースがありましたら下記 issues にお願い致します。日本語でも対応致します。
可能であればJSONの失敗例がありますと,TDDで解決しやすくなり大変助かります!
https://github.com/YutoMizutani/JSONtoCodable/issues

コードが汚ないと感じた方,下記よりお待ちしております!
https://github.com/YutoMizutani/JSONtoCodable/pulls

おわりに

まずは,Demoを試していただきたいです。

そして,よければスターをお願い致します!

皆さんの開発ライフを少しでも手助けできれば幸いです。

追記 (2018/10/25)

CLIに対応しました!
curl等からパイプで繋いで生成することができるようになりました!

インストール

$ brew tap YutoMizutani/jc
$ brew install jc

使い方

$ curl https://httpbin.org/get | jc
$ curl https://httpbin.org/get | jc > Result.swift

References