LoginSignup
64
44

More than 5 years have passed since last update.

【Swift】Codable(Decodable)を使ってみた

Last updated at Posted at 2017-12-05

Codableとは

Swift 4で導入されたCodable、主にJSONのエンコードやデコードをする時に使うプロトコルです。
JSON以外にも独自でカスタマイズしてデコードもできるようです。

Codableの使い方

詳しい説明は省略させていただきます。
・基本形

pattern1.swift
    let json = """
    {
        "id": 1,
        "title": "タイトル",
        "message": "メッセージ",
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest: Decodable {
    var id: Int?
    var title: String?
    var message: String?
}

簡単な構造のJSONの場合はこれだけでデコードすることができます。
取得したデータはhoge.id等で取得することができます。
structでなくclassでも使うことができます。

・Jsonのキーがスネークケースの場合など

pattern2.swift
    let json = """
    {
        "items": [
            {
                "id": 1,
                "title": "タイトル1",
                "message": "メッセージ1",
            },
            {
                "id": 2,
                "title": "タイトル2",
                "message": "メッセージ2",
            }
        ],
        "total_count": 2,
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest {

    struct Content {
        var id: Int?
        var title: String?
        var message: String?
    }

    private enum RootKeys: String, CodingKey {
        case items
        case totalCount = "total_count"
    }

    private enum ItemsKeys: String, CodingKey {
        case id
        case title
        case message
    }

    var contents: [Content]?
    var totalCount: Int = 0
}

extension CodableTest: Decodable {

    init(from decoder: Decoder) throws {
        self.contents = []
        let root = try decoder.container(keyedBy: RootKeys.self)
        var items = try root.nestedUnkeyedContainer(forKey: .items)

        while !items.isAtEnd {
            let container = try items.nestedContainer(keyedBy: ItemsKeys.self)
            var content = Content()
            content.id = try container.decode(Int.self, forKey: .id)
            content.title = try container.decode(String.self, forKey: .title)
            content.message = try container.decode(String.self, forKey: .message)
            self.contents?.append(content)
        }
        totalCount = try root.decode(Int.self, forKey: .totalCount)
    }
}

次は少し現実的?なAPIを想定した場合です。
記述が増えましたが、JSONのキーがスネークケースや保存する変数名を変更する場合はenumでJSONのキーを指定してあげます。
配列の場合はfor文等が使えないためwhile文を使用します。

・nullが帰ってきたりキーが存在しない場合

pattern3.swift
    let json = """
    {
        "items": [
            {
                "id": 1,
                "title": "タイトル1",
                "message": "メッセージ1",
            },
            {
                "id": 2,
                "title": null,
            },
            {
                "id": 3,
                "title": "タイトル3",
                "message": "メッセージ3",
            }
        ],
        "total_count": 2,
    }
    """.data(using: .utf8)!

    func decode() {
        do {
            let hoge = try JSONDecoder().decode(CodableTest.self, from: json)
        } catch {
            print(error)
        }
    }

struct CodableTest {
//上と同じ為、省略
}

extension CodableTest: Decodable {

    init(from decoder: Decoder) throws {
        self.contents = []
        let root = try decoder.container(keyedBy: RootKeys.self)
        var items = try root.nestedUnkeyedContainer(forKey: .items)

        while !items.isAtEnd {
            let container = try items.nestedContainer(keyedBy: ItemsKeys.self)
            var content = Content()
            content.id = try container.decode(Int.self, forKey: .id)
            do {
                content.title = try container.decode(String.self, forKey: .title)
            } catch {
                print(error)
            }
            do {
                content.message = try container.decode(String.self, forKey: .message)
            } catch {
                print(error)
            }
            self.contents?.append(content)
        }
        totalCount = try root.decode(Int.self, forKey: .totalCount)
    }
}

配列の途中でnullが入っていたりキーが存在しない場合に、perrern2の記述だとdecode()のキャッチに飛ぶのでwhile文の途中で終了してしまい最後まで処理が行われないので、do catchを入れています。

do catchでなくif文で判定することもできます。

    if container.contains(./*key*/) {
        // keyが存在している場合
    }
    if try !container.decodeNil(forKey: ./*key*/) {
        // keyがnullではない場合
    }

まとめ

JSONSerializationに比べるとキーがenumなので補完が出るようになっているので良くなったと思います。
簡単なデコードならJSONSerializationより楽にできるようになりました。
ただ、情報がまだ少ないため、ネストや配列の場合にどうやるのか理解するのに時間がかかったので、まだライブラリの方が情報がたくさんあり、わかりやすいかなと思いました。

64
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
44