我使用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
也会被解码。这似乎是很多冗余代码,因为我需要在我拥有的每种类型上都有一个额外的包装器。有没有办法避免这种包装模式或更正确/更优雅的方式处理这种情况?
答案 0 :(得分:38)
您可以使用字典解码:用户组合然后提取出用户对象。 e.g。
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
答案 1 :(得分:37)
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)
}
}
但是我绝对会为这个问题做奥利的方式。
有关详情,请参阅Encoding and Decoding Custom Types。
答案 2 :(得分:10)
当然,您总是可以实现自己的自定义解码/编码 - 但是对于这个简单的场景,您的包装类型是一个更好的解决方案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
更容易;)
答案 3 :(得分:5)
我为Codable创建了一个帮助扩展,可以使这样的事情变得更容易。
请参阅https://github.com/evermeer/Stuff#codable
使用它可以创建用户对象的实例,如下所示:
let v = User(json: json, keyPath: "user")
您不必更改原始User结构中的任何内容,也不需要包装器。