📚

【Swift】Codableについて備忘録

2021/10/03に公開

Codableについて今まで触れてなかったので調べてみました。
忘れないうちにまとめてみます。

Codableって何?

API通信等で取得したJSONやプロパティリストを任意のデータ型に変換するプロトコル
→ データをアプリを実装しやすいデータ型に変換することで処理が楽になる

Apple Reference はこちら

(例)ユーザ情報のJSONを「User型」に変換する

json = {
  "name": "太郎"
  "age": "20"
}

let name = json["name"]
User.swift

struct User: Codable {
  name: String
  age: String
}

let name = User.name

Encodable, Decodable との関係

初めてコードを見た時、EncodableとかDecodableとか複数あって混乱しましたが

Codable = Encodable + Decodable

ってことみたいです。

自動Codable

・常にCodableな型
 String, Int, Double, Data, Date, URL

・条件付きでCodableな型
 Array, Dictionary, Optional → 中身がCodableの場合
 struct → Codableなプロパティのみから構成されている場合

↓↓こんな感じで書くとCodableとして使える↓↓

User.swift

struct Friend: Codable {  // -> プロパティが全てCodableなのでCodable
  name: String // -> Codable
  age: Int // -> Codable
}

struct User: Codable {
  name: String
  age: Int
  friends: [Friend] // -> 中身がCodableなのでCodable
  startDate: Date
  role: String
}

CodingKeysの利用

上記のように記述すればCodableとして使えるけど、EncodeとDecodeでキー名が異なる時に一対一対応させる必要がある。その時に使うのが「CodingKeys」。

↓↓こんな感じ使う↓↓

User.swift
struct User: Codable {
  name: String
  age: Int
  friends: [Friend]
  startDate: Date
  role: String

  enum CodingKeys: String, CodingKey {
    case name
    case age
    case friends
    case startDate = "start_date"  // ← これ
    case role
  }
}

【実装する時の決まり事】
・enumの名前は「CodingKeys」にする
・case名をプロパティ名、rawValueをエンコード結果のフィールド名として定義する
・case自体を省略するとエンコード・デコードされない。この時、Decodableにするにはdefault valueが必要。

例で示した@「SnakeCase ↔︎ CamelCase」の場合は
Swift4.1以降では、DecoderのkeyDecodingStrategyを使うと省略できるみたいです。
参考ページはこちら

手動Codable

定義したデータ構造がエンコードのフォーマットと合わない場合は、EncodeとDecodeの処理を自分で実装する必要がある。

(例)
ネストしている場合

User.swift
struct User: Codable {
  name: String
  age: Int
  friends: [Friend]
  startDate: Date
  role: String // ← これ

  enum CodingKeys: String, CodingKey {
    case name
    case age
    case friends
    case startDate = "start_date"

    case additionalInfo // ← これ
  }

  enum AdditionalInfoKeys: String, CodingKey {
    case role
  }
}

● Decode

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

    let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
    role = try additionalInfo.decode(Double.self, forKey: .role)
  }
}

● Encode

User.swift
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)
    try container.encode(startDate, forKey: .startDate)

    var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
    try additionalInfo.encode(role, forKey: .role)
  }
}

Encoder, Decoder

● Decoder

let decoder = JSONDecoder()
let data = decoder.decode(**変換する型**.self, json: json)

Apple Reference はこちら

● Encoder

let encoder = JSONEncoder()
// JSONにエンコードする型のデータ
let user = User(name: "太郎", age: 20)
let data = encoder.encode(user)

Apple Reference はこちら

継承クラスのエンコード

  1. 子のクラスのみ「Codable」
     → 親クラスのプロパティはエンコードされない
  2. 親のクラスのみ「Codable」
     → 子クラスのプロパティはエンコードされない

↓↓継承クラスのエンコードの場合、手動で実装する必要がある。↓↓

Cat.swift
class Animal {
  var name = "Animal"
}

class Cat: Animal, Encodable {
  var catName = "Cat"

  enum CodingKeys: String, CodingKey {
    case name
    case catName
  }
}

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

DecodingContainer, EncodingContainer

自分で実装する時に使う。

● Decode

名称
KeyedContainer 辞書
UnkeyedDecodingContainer 配列
SingleValueDecodingContainer 単一値

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

● Encode

名称
KeyedEncodingContainer 辞書
UnkeyedEncodingContainer 配列
SingleValueEncodingContainer 単一値

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)
  }
}

ネストさせる場合は、nestedContainerを使う。


// 辞書
container.nestedContainer

// 配列
container.nestedUnKeyedContainer

まとめ

これまでObjectMapperなどライブラリを使っていましたが、Codableで十分だなと感じました。
間違い等ございましたら、ご指摘お願いします。

Discussion