LoginSignup
2
2

More than 5 years have passed since last update.

Codableに関する覚書

Posted at

Codable(Decodable)に関して、Modelを作ってるときは覚えているんだけど
忘れてしまうことが多いので、自分用の備忘録的に残しておく。
基本的には手動Codableの書き方。配列形式と、ネスト形式をよく忘れるので。

配列型の場合は.nestedUnkeyedContainer
辞書型の場合は.nestedContainerを使います。

参考

Codable

Codableを親クラスとして指定すると、Encodable + Decodableのメソッドがデフォルトで設定されている。
CodingKeysなどについての詳しいところは、参考記事の方で解説されているので省略

struct User: Codable {
    let name: String
    let age: Int

// ここから以下がデフォルトで定義されている(変更の必要がなければ、書かなくて良い)
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
}
// 本来ならここから下を書く場合には、UserからCodableのプロトコルは外す
extension User:Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
    }
}

extension User:Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        age  = try values.decode(Int.self, forKey: .age)
    }
}

手動Codable

自動に任せれば楽なんだけど、そうするとModelの形がAPIに引きづられてしまうので使いにくいことも多い。
そこで手動でEncode / Decodeを書く必要が出てくるんだけどこれがちょっと厄介というか・・・。
なんか普通にJSONパーサーに任せた方がいいんじゃなかろうかとか思い始める。

配列を手動で解析する場合

struct User {
    let name: String
    let age: Int
    let friends: [Friend]

    enum CodingKeys: String, CodingKey {
        case name
        case age
        case friends
    }

    struct Friend: Codable {
        let name: String
    }
}

extension User: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name,    forKey: .name)
        try container.encode(age,     forKey: .age)
        try container.encode(friends, forKey: .friends)
    }
}

extension User: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        age  = try values.decode(Int.self, forKey: .age)

        var friends:[Friend] = []
        var array = try values.nestedUnkeyedContainer(forKey: .friends)
        while(!array.isAtEnd) {
            let info = try array.decode(Friend.self)
            friends.append(info)
        }
        self.friends = friends
    }
}

ネストしている要素を解析する場合

こんなJSONの場合

{
    "name" : "なまえ",
    "age" : 21,
    "details" : {
        "address" : "渋谷区渋谷"
    }
}
struct User {
    let name: String
    let age: Int
    let address: String

    enum CodingKeys: String, CodingKey {
        case name
        case age
        case details
    }
}

extension User: Encodable {
    private enum DetailKeys: String, CodingKey {
        case address
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name,    forKey: .name)
        try container.encode(age,     forKey: .age)

        var details = container.nestedContainer(keyedBy: DetailKeys.self, forKey: .details)
        try details.encode(address, forKey: .address)
    }
}

extension User: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        age  = try values.decode(Int.self, forKey: .age)

        let details = try values.nestedContainer(keyedBy: DetailKeys.self, forKey: .details)
        address = try details.decode(String.self, forKey: .address)
    }
}

EOF

2
2
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
2
2