自分が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)
キーが存在しない時があるパターン (手動デコード)
decodeIfPresent
やif 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データの例が雑ですみません )
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)
感想
もし記載ミス等あればご指摘お願いします (JSONデータ例が雑なのは悪しからず。。)
パースって大変ですよね(白目)
追記
仕事時に参考にしていた記事
【Swift】Codableについて備忘録
レスポンスのJSONが異なるAPIにオプショナルを使わず対応する方法(Swift)
Codableについて色々まとめた[Swift4.x]