はじめに
- サーバとの通信処理ではResponseの値をmappingやparseして、Swiftで扱いやすいように変換する処理をよく書きます。JSONからSwiftの値に変換するときにはCodableを使うと楽に実装できます。そんなCodableの実装もAPIが増えるごとにたくさん実装、メンテする必要がでてきます。そんな実装を爆速でするための方法について、まとめました。
JSONの型とSwiftで使う型の変換
{ "name": "Tanaka", "age": "26", "gender": "0" }
- このようなJSONあるときに実装で扱いやすい型に変換するときに苦労した経験があります。(String <-> Intの変換など)
Codableで逐次変換処理を書くと可読性が落ちて、メンテし辛いコードが出来上がります。それを克服するためにDTOを使って、可読性の高いコードにしてみます。
DTOを使ったCodableの処理
DTOとは
-
Data Transfer Object
を省略したものです。 -
Layer間の値の受け渡しに使います。
-
このJSONをSWiftの値に変換する処理を書いてみます。
{ "name": "Tanaka", "age": "26", "gender": "0" }
- before
- 普通に書くと
try
の処理と型変換の処理がたくさん書くことになります
- 普通に書くと
import UIKit
let json = """
{ "name": "Tanaka", "age": "26", "gender": "0" }
""".data(using: .utf8)!
enum Gender: Int {
case male = 0
case female
case unknown
}
struct Person {
let name: String
let age: Int
let gender: Gender
enum CodingKeys: String, CodingKey {
case name
case age
case gender
}
}
enum DecodableError: Error {
case ageError
case genderError
}
extension Person: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
let ageValue = try container.decode(String.self, forKey: .age)
guard let age = Int(ageValue) else {
throw DecodableError.ageError
}
self.age = age
let genderString = try container.decode(String.self, forKey: .gender)
guard let genderValue = Int(genderString), let gender = Gender(rawValue: genderValue) else {
throw DecodableError.genderError
}
self.gender = gender
}
}
let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: json)
- after
-
DTODecodable
を使って、tryの処理を書かなくていいようにします。DTOではJSONで宣言されている型の通りに変換することで値のparseのみ行います。Entity側ではDTOの値を元にEntityで必要な型に変換します。Entity側ではtryの処理を意識しないのでコードの可読性が上がりました。
-
import UIKit
protocol DTODecodable: Decodable {
associatedtype DTO: Decodable
init(dto: DTO) throws
}
extension DTODecodable {
init(from decoder: Decoder) throws {
let dto = try DTO(from: decoder)
self = try Self.init(dto: dto)
}
}
let json = """
{ "name": "Tanaka", "age": "26", "gender": "0" }
""".data(using: .utf8)!
enum Gender: Int {
case male = 0
case female
case unknown
}
struct Person {
let name: String
let age: Int
let gender: Gender
enum CodingKeys: String, CodingKey {
case name
case age
case gender
}
}
extension Person: DTODecodable {
struct DTO: Decodable {
let name: String
let age: String
let gender: String
}
init(dto: DTO) throws {
self.name = dto.name
guard let age = Int(dto.age) else {
throw DTODecodableError.ageError
}
guard let genderValue = Int(dto.gender),
let gender = Gender(rawValue: genderValue) else {
throw DTODecodableError.genderError
}
self.age = age
self.gender = gender
}
enum DTODecodableError: Error {
case ageError
case genderError
}
}
let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: json)
- 実装イメージ
Codableのコードを生成する
-
ここでは2つのツールについて紹介します。2つともJSONを元にCodableの実装を生成するツールです。
-
- WebでJSONからCodableの実装が生成されます
-
- JSONtoCodableを使うとCodableの実装が生成されます
まとめ
- DTOを使うことでCodableの可読性があがります。
- 生成ツールを使うとさらに爆速で開発できます