LoginSignup
191
163

More than 1 year has passed since last update.

【Swift】Codableについて備忘録

Last updated at Posted at 2018-09-06

記事を移動しました。

https://zenn.dev/dd_sho/articles/45de4efe7dc296
今後は上記の記事で更新します。(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で十分だなと感じました。
間違い等ございましたら、ご指摘お願いします。

191
163
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
191
163