Codable で root 階層無視したい場合
Codable
というかDecodable
の話か。。。
とある json で root 階層を無視してマッピングするには...?
という話
Swift 4 イチオシ(?)の機能の一つでもあるCodable
使っているときにふと...あーこれどうしよ...ってなったのでそのメモ
環境
- Xcode 9.4
たとえば...
このような json
let jsonString = """
{
"status": 200,
"access_date" : "2018-06-13",
"user": {
"id": 1,
"name": "anz.factory"
}
}
"""
どう定義していこうか...🤔
純粋にただ行うなら...
2つ用意してそれぞれマッピングしていく感じですかね。
struct Root: Codable {
private enum CodingKeys: String, CodingKey {
case status, accessDate = "access_date", user
}
let status: Int
let accessDate: String
let user: User
}
struct User: Codable {
var id: Int
var name: String
}
let root = try JSONDecoder().decode(Root.self, from: jsonString.data(using: .utf8))
print(root.user.name)
これでok!
なのですが... Root って絶対使わない...これを定義する意味...😓
Root なしでしたいよう...
上記のような感じでDecodable
に完全にまかせてマッピングさせることもできますが、
そこを自分でコントロールすることもできます。
そこでnestedContainer(keyedBy:,forKey:)
というものを使うといけます!
struct User: Codable {
// keyの定義だけはする
private enum RootKeys: String, CodingKey {
case status, accessDate = "access_date", user
}
var id: Int
var name: String
init(from decoder: Decoder) throws {
// root
let root = try decoder.container(keyedBy: RootKeys.self)
// user
let user = try root.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
id = try user.decode(Int.self, forKey: .id)
name = try user.decode(String.self, forKey: .name)
}
}
// User をつかえる!
let user = try JSONDecoder().decode(User.self, from: jsonString.data(using: .utf8))
print(user.name)
こういう感じ。
これでわざわざ Root を定義しなくてもできます!
ただ...
今回 User のプロパティが少ないからこれでいいけど...
多いとこっちのほうが断然めんどくさくなるので...そこはトレードオフで Root を定義する方を選ぶかもですね(笑)
json のフラット化も同じ感じでいけますよ!
let jsonString = """
{
"name": "image.png",
"size": {
"width": 60,
"height": 40
}
}
"""
// size をフラット化したい!
struct ImageData {
private enum CodingKeys: String, CodingKey {
case name, size
}
private enum SizeKeys: String, CodingKey {
case width, height
}
var name: String
var width: Int
var height: Int
}
extension ImageData: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
let size = try values.nestedContainer(keyedBy: SizeKeys.self, forKey: .size)
width = try size.decode(Int.self, forKey: .width)
height = try size.decode(Int.self, forKey: .height)
}
}
こういう感じ。
プロパティと CodingKeys が合わないので、 Decodable
だけで...
Codable
でやる場合は、encode(to encoder:) throws
もちゃんとやるとできます!👍
参考
Encoding and Decoding Custom Types | Apple Developer Documentation