6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift Decodable(Codable)  色んなJSONデータパターンのまとめ 【備忘録】

Last updated at Posted at 2020-11-24

自分がAPIから取得したJSONのレスポンスパラメータで、色々な構造パターンがあったので備忘録も兼ねてまとめてみたいと思います。

そのままプロパティに適用できるパターン

一番手間がない形式でシンプルにデコード用クラスを定義できる

var data = """
{
  "id": 1,
  "name": "Taro",
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: String
}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

キーとプロパティが一致しないパターン

Swiftの変数名の命名規則はローワーキャメルケースなので、CodingKeyを使って揃えることができる

var data = """
{
  "ID": 1,
  "Name": "Taro",
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: String
    
    enum CodingKeys: String, CodingKey {
        case id = "ID"
        case name = "Name"
    }
}

let user: User = try JSONDecoder().decode(User.self, from: data)

print(user)

データ構造に配列があるパターン

デコードメソッドの引数に配列で渡す

var data = """
[
    {
        "id": 0,
        "name": "Mike"
    },
    {
        "id": 1,
        "name": "John"
    },
    {
        "id": 2,
        "name": "Mary"
    }
]
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: String
}

let user: [User] = try JSONDecoder().decode([User].self, from: data)

print(user)

ネストが深いパターン(自動デコード)

別のデコード用のstructを作る(あんまり自動で済むパターンないですが)

var data = """
{
    "id": 0,
    "name": {
        "last": "Suzuki",
        "first": "Taro"
    }
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name:Name

    struct Name: Decodable {
        let last: String
        let first: String
    }
}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

ネストが深いパターン(手動デコード)

おそらくこっちのがよくあるパターン 別のstructを作らずに済む
取得しない値(キー用意していない変数)は 辞書型ならnestedContainer 、 配列ならnestedUnkeyedContainerで取得できる

階層ごとに別のCodingKeyを用意してあげる

var data = """
{
    "id": 0,
    "name": {
        "last": "Suzuki",
        "first": "Taro"
    }
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let lastName: String
    let firstName: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    enum NameKeys: String, CodingKey {
        case lastName = "last"
        case firstName = "first"
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)

        let nameValues = try values.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
        lastName = try nameValues.decode(String.self, forKey: .lastName)
        firstName = try nameValues.decode(String.self, forKey: .firstName)
    }
}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

キーが存在しない時があるパターン (自動デコード)

プロパティをオプショナル型にする

var data = """
{
    "id": 0
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: String?
}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

キーが存在しない時があるパターン (手動デコード)

decodeIfPresentif let (変数) = try? decode~~ などを使って、もしキーが存在すればのデコードをする

var data = """
{
    "id": 0
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)
        name = try values.decodeIfPresent(String.self, forKey: .name) ?? ""
    }
}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

取得しない値が配列になっているパターン (手動デコード)

あんまりQiitaで見かけないのでこのパターンも記載(JSONデータの例が雑ですみません :pray:)

var data = """
{
    "id": 0,
    "names": [
        {
            "name": "太郎(日本名)"
        },
        {
            "name": "テンプレマン(ニックネーム)"
        },
        {
            "name": "Taro(アメリカネーム)"
        }
    ]
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: [String]
    
    enum CodingKeys: String, CodingKey {
        case id
        case names
    }
    
    enum NameKeys: String, CodingKey {
        case name
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)
        
        var names = try values.nestedUnkeyedContainer(forKey: .names)
        var nameArray: [String] = []

        while !names.isAtEnd {
            let nameDic = try names.nestedContainer(keyedBy: NameKeys.self)
            let nameValue = try nameDic.decode(String.self, forKey: .name)
            nameArray.append(nameValue)
        }
        name = nameArray
    }

}

let user: User = try JSONDecoder().decode(User.self, from: data)
print(user)

どっちかのキーで返ってくるパターン

EnumにDecodableを準拠させたプロパティを用意して、それを使って分岐で両方デコードする

var data1 = """
{
    "id": 0,
    "name": "Taro"
}
""".data(using: .utf8)!

var data2 = """
{
    "id": 1,
    "name": {
        "last": "Suzuki",
        "first": "Ichiro"
    }
}
""".data(using: .utf8)!

struct User: Decodable {
    let id: Int
    let name: NameType
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)
        name = try values.decode(NameType.self, forKey: .name)
    }
    
    enum NameType: Decodable {
        case firstNameOnly(String)
        case fullName(Name)
        
        init(from decoder: Decoder) throws {
            let nameType = try decoder.singleValueContainer()
            
            if let nameValue = try? nameType.decode(Name.self) {
                self = .fullName(nameValue)
            }
            else {
                let nameValue = try nameType.decode(String.self)
                self = .firstNameOnly(nameValue)
            }
        }
    }
}

struct Name: Decodable {
    let last: String
    let first: String
    
    private enum NameKeys: String, CodingKey {
        case last
        case first
    }
    
    init(from decoder: Decoder) throws {
        let nameValues = try decoder.container(keyedBy: NameKeys.self)
        last = try nameValues.decode(String.self, forKey: .last)
        first = try nameValues.decode(String.self, forKey: .first)
    }
}

let user1: User = try JSONDecoder().decode(User.self, from: data1)
print(user1)
let user2: User = try JSONDecoder().decode(User.self, from: data2)
print(user2)

感想

もし記載ミス等あればご指摘お願いします :bow: (JSONデータ例が雑なのは悪しからず。。)
パースって大変ですよね(白目)

追記

仕事時に参考にしていた記事
【Swift】Codableについて備忘録

レスポンスのJSONが異なるAPIにオプショナルを使わず対応する方法(Swift)
Codableについて色々まとめた[Swift4.x]

6
7
2

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?