SwiftのJSONEncoder/Decoderは JSON文字列をCodable(Encodable & Decodable) protocolを実装したClassやStructにエンコード/デコードするオブジェクト。
全ての変数がCodableで、特に何もする必要がない場合はCodableを付ければうまくいく。 String、Int、Doubleといったstandard libraryの型や、DateやDataなどFoundationの型はCodableになっている。
CodingKeyでフィールド名のマッピングができる。デコード時にOptionalでないフィールドが足りなかったり型が異なると例外が飛び、エンコード時にはnilを無視する。
struct Foo: Codable {
var nums: [Int]
var str: String
enum CodingKeys: String, CodingKey {
case nums = "numbers"
case str
}
}
var data = """
{
"numbers": [100, 120],
"str": "Aaa"
}
""".data(using: .utf8)!
let json = try! JSONDecoder().decode(Foo.self, from: data)
print(json.str) // => Aaa
let enc = try! JSONEncoder().encode(json)
print(String(data: enc, encoding: .utf8)!) // => {"numbers":[100,120],"str":"Aaa"}
var data = """
{
"numbers": [100, 120],
}
""".data(using: .utf8)!
let json = try! JSONDecoder().decode(Foo.self, from: data) // Swift.DecodingError.keyNotFound
Codableの関数を自分で実装すれば柔軟に処理できる。次の例ではJSONのbar.strをFoo.strに対応させている。
struct Foo {
var nums: [Int]
var str: String
enum CodingKeys: String, CodingKey {
case nums = "numbers"
case bar
case str
}
enum BarKeys: String, CodingKey {
case str
}
}
extension Foo: Codable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nums = try values.decode([Int].self, forKey: .nums)
let barValues = try values.nestedContainer(keyedBy: BarKeys.self, forKey: .bar)
let barstr = try barValues.decode(String.self, forKey: .str)
str = barstr
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(nums, forKey: .nums)
var bar = container.nestedContainer(keyedBy: BarKeys.self, forKey: .bar)
try bar.encode(str, forKey: .str)
}
}
var data = """
{
"numbers": [100, 120],
"bar": {
"str": "aiueo"
}
}
""".data(using: .utf8)!
let json = try! JSONDecoder().decode(Foo.self, from: data)
print(json.str) // => aiueo
let enc = try! JSONEncoder().encode(json)
print(String(data: enc, encoding: .utf8)!) // => {"numbers":[100,120],"bar":{"str":"aiueo"}}