JSON → Swift Codable 変換ツール

JSONから、Codableに準拠したSwiftのstructを生成します。デコード用のイニシャライザも付いています。

JSONからSwiftのCodable structを生成

CodableはSwiftでJSONをデコードする標準的な方法ですが、大きなペイロードのモデルstructを手で書くのは面倒です。このツールはオブジェクトごとにCodableに準拠したstructを生成し、Int、Double、String、Bool、ネストしたstruct、配列で型付けされたletプロパティを持たせます。JSONのキーとSwiftのプロパティ名が異なる場合はCodingKeys enumを追加し、さらにData、JSON文字列、URLからデコードするイニシャライザを備えたextension、変更したコピーを作るwith(...)メソッド、エンコードし直すためのjsonData()/jsonString()も生成します。

使い方

  1. iOSやmacOSアプリが受け取るレスポンスなど、JSONサンプルを貼り付けます。
  2. ルート型の名前を入力します(例: Article)。
  3. ファイルをXcodeプロジェクトに追加し、try Article(jsonString) または JSONDecoder().decode(Article.self, from: data) でデコードします。

よくある質問

SwiftUIやURLSessionで使えますか?
はい。structはシンプルなCodableの値型なので、URLSessionが返すDataをデコードし、その結果を他のモデルと同じようにSwiftUIのビューに渡せます。
snake_caseのキーはどう扱われますか?
プロパティにはSwiftらしいcamelCaseの名前が付き(is_featuredはisFeaturedになります)、CodingKeys enumがそれぞれを元のJSONキーに対応付けるため、keyDecodingStrategyを設定する必要はありません。
便利なextensionは削除できますか?
はい。Codableに必要なのはstructの宣言とCodingKeysだけです。extensionやnewJSONDecoder/newJSONEncoderヘルパーは任意の便利機能なので、削除してかまいません。

例: JSONからSwiftへの変換

入力JSON

{
  "id": 3,
  "title": "Hello Swift",
  "author": { "name": "Taylor", "followers": 1200 },
  "is_featured": true
}

生成されたSwift(Article)

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let article = try Article(json)

import Foundation

// MARK: - Article
struct Article: Codable {
    let id: Int
    let title: String
    let author: Author
    let isFeatured: Bool

    enum CodingKeys: String, CodingKey {
        case id, title, author
        case isFeatured = "is_featured"
    }
}

// MARK: Article convenience initializers and mutators

extension Article {
    init(data: Data) throws {
        self = try newJSONDecoder().decode(Article.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func with(
        id: Int? = nil,
        title: String? = nil,
        author: Author? = nil,
        isFeatured: Bool? = nil
    ) -> Article {
        return Article(
            id: id ?? self.id,
            title: title ?? self.title,
            author: author ?? self.author,
            isFeatured: isFeatured ?? self.isFeatured
        )
    }

    func jsonData() throws -> Data {
        return try newJSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

// MARK: - Author
struct Author: Codable {
    let name: String
    let followers: Int
}

// MARK: Author convenience initializers and mutators

extension Author {
    init(data: Data) throws {
        self = try newJSONDecoder().decode(Author.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func with(
        name: String? = nil,
        followers: Int? = nil
    ) -> Author {
        return Author(
            name: name ?? self.name,
            followers: followers ?? self.followers
        )
    }

    func jsonData() throws -> Data {
        return try newJSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

// MARK: - Helper functions for creating encoders and decoders

func newJSONDecoder() -> JSONDecoder {
    let decoder = JSONDecoder()
    if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
        decoder.dateDecodingStrategy = .iso8601
    }
    return decoder
}

func newJSONEncoder() -> JSONEncoder {
    let encoder = JSONEncoder()
    if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
        encoder.dateEncodingStrategy = .iso8601
    }
    return encoder
}

JSONを他の言語に変換