Swift 4 可编码;如何使用单个根级密钥解码对象
Swift 4 Codable; How to decode object with single root-level key
我正在使用 Swift 4 Codable
协议和 JSON 数据。我的数据被格式化为在根级别有一个键,其对象值包含我需要的属性,例如:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
我有一个 User
结构可以解码 user
键:
struct User: Codable {
let id: Int
let username: String
}
因为 id
和 username
是 user
的属性,而不是在根级别,我需要像这样制作一个包装器类型:
struct UserWrapper: Codable {
let user: User
}
然后我可以通过 UserWrapper
解码 JSON,并且 User
也被解码。看起来有很多冗余代码,因为我需要对我拥有的每种类型进行额外的包装。有没有办法避免这种包装模式或更多 correct/elegant 处理这种情况的方法?
当然,您始终可以实现自己的自定义 decoding/encoding — 但对于这个简单的场景,您的包装器类型是一个更好的解决方案 IMO ;)
为了比较,自定义解码看起来像这样:
struct User {
var id: Int
var username: String
enum CodingKeys: String, CodingKey {
case user
}
enum UserKeys: String, CodingKey {
case id, username
}
}
extension User: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
self.id = try user.decode(Int.self, forKey: .id)
self.username = try user.decode(String.self, forKey: .username)
}
}
如果你也想支持编码,你仍然要遵守 Encodable
协议。正如我之前所说,您的简单 UserWrapper
更容易 ;)
您可以使用字典解码:用户组合,然后提取用户对象。例如
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
Ollie 的回答绝对是处理这种情况的最佳方法,但它确实将一些知识强加给了调用者,这可能是不可取的。它也不是很灵活。我仍然认为这是一个很好的答案,并且正是您想要的,但这是探索自定义结构编码的一个很好的简单示例。
我们怎样才能使它正常工作:
let user = try? JSONDecoder().decode(User.self, from: json)
我们不能再使用默认的一致性了。我们必须构建自己的解码器。这有点乏味,但并不困难。首先,我们需要将结构编码成CodingKeys:
struct User {
let id: Int
let username: String
enum CodingKeys: String, CodingKey {
case user // The top level "user" key
}
// The keys inside of the "user" object
enum UserKeys: String, CodingKey {
case id
case username
}
}
这样,我们就可以通过拉出嵌套容器来手动解码User
:
extension User: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("user")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the user object as a nested container
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
// Extract each property from the nested container
id = try user.decode(Int.self, forKey: .id)
username = try user.decode(String.self, forKey: .username)
}
}
但我绝对会按照 Ollie 的方式来解决这个问题。
有关更多信息,请参阅 Encoding and Decoding Custom Types。
我为 Codable 创建了一个帮助程序扩展,它将使这样的事情变得更容易。
见https://github.com/evermeer/Stuff#codable
有了它,您可以像这样创建用户对象的实例:
let v = User(json: json, keyPath: "user")
您不必更改原始用户结构中的任何内容,也不需要包装器。
我正在使用 Swift 4 Codable
协议和 JSON 数据。我的数据被格式化为在根级别有一个键,其对象值包含我需要的属性,例如:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
我有一个 User
结构可以解码 user
键:
struct User: Codable {
let id: Int
let username: String
}
因为 id
和 username
是 user
的属性,而不是在根级别,我需要像这样制作一个包装器类型:
struct UserWrapper: Codable {
let user: User
}
然后我可以通过 UserWrapper
解码 JSON,并且 User
也被解码。看起来有很多冗余代码,因为我需要对我拥有的每种类型进行额外的包装。有没有办法避免这种包装模式或更多 correct/elegant 处理这种情况的方法?
当然,您始终可以实现自己的自定义 decoding/encoding — 但对于这个简单的场景,您的包装器类型是一个更好的解决方案 IMO ;)
为了比较,自定义解码看起来像这样:
struct User {
var id: Int
var username: String
enum CodingKeys: String, CodingKey {
case user
}
enum UserKeys: String, CodingKey {
case id, username
}
}
extension User: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
self.id = try user.decode(Int.self, forKey: .id)
self.username = try user.decode(String.self, forKey: .username)
}
}
如果你也想支持编码,你仍然要遵守 Encodable
协议。正如我之前所说,您的简单 UserWrapper
更容易 ;)
您可以使用字典解码:用户组合,然后提取用户对象。例如
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
Ollie 的回答绝对是处理这种情况的最佳方法,但它确实将一些知识强加给了调用者,这可能是不可取的。它也不是很灵活。我仍然认为这是一个很好的答案,并且正是您想要的,但这是探索自定义结构编码的一个很好的简单示例。
我们怎样才能使它正常工作:
let user = try? JSONDecoder().decode(User.self, from: json)
我们不能再使用默认的一致性了。我们必须构建自己的解码器。这有点乏味,但并不困难。首先,我们需要将结构编码成CodingKeys:
struct User {
let id: Int
let username: String
enum CodingKeys: String, CodingKey {
case user // The top level "user" key
}
// The keys inside of the "user" object
enum UserKeys: String, CodingKey {
case id
case username
}
}
这样,我们就可以通过拉出嵌套容器来手动解码User
:
extension User: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("user")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the user object as a nested container
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
// Extract each property from the nested container
id = try user.decode(Int.self, forKey: .id)
username = try user.decode(String.self, forKey: .username)
}
}
但我绝对会按照 Ollie 的方式来解决这个问题。
有关更多信息,请参阅 Encoding and Decoding Custom Types。
我为 Codable 创建了一个帮助程序扩展,它将使这样的事情变得更容易。
见https://github.com/evermeer/Stuff#codable
有了它,您可以像这样创建用户对象的实例:
let v = User(json: json, keyPath: "user")
您不必更改原始用户结构中的任何内容,也不需要包装器。