Swift 4 Codable;如何使用单根级别密钥解码对象

时间:2017-06-23 07:25:51

标签: json swift deserialization swift4 codable

我使用Swift 4 Codable协议和JSON数据。我的数据被格式化为在根级别有一个键,其对象值包含我需要的属性,例如:

{
  "user": {
    "id": 1,
    "username": "jdoe"
  }
}

我有一个可以解码User密钥的user结构:

struct User: Codable {
  let id: Int
  let username: String
}

由于idusernameuser的属性,而不是根级别,因此我需要创建一个包装类型:

struct UserWrapper: Codable {
  let user: User
}

然后我可以通过UserWrapper解码JSON,User也会被解码。这似乎是很多冗余代码,因为我需要在我拥有的每种类型上都有一个额外的包装器。有没有办法避免这种包装模式或更正确/更优雅的方式处理这种情况?

4 个答案:

答案 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结构中的任何内容,也不需要包装器。