Swift 4 可编码;如何使用单个根级密钥解码对象

Posted

技术标签:

【中文标题】Swift 4 可编码;如何使用单个根级密钥解码对象【英文标题】:Swift 4 Codable; How to decode object with single root-level key 【发布时间】:2017-11-26 16:23:47 【问题描述】:

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


  "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 也被解码。看起来有很多冗余代码,因为我需要对我拥有的每种类型都进行额外的包装。有没有办法避免这种包装模式或更正确/优雅的方式来处理这种情况?

【问题讨论】:

我还没有深入了解Codable协议,但我认为最快的方法是直接使用内部字典初始化User对象。您可以通过从字典中访问 userDicitonary 将其从用户字段中取出吗? 如果你的 JSON 只包含单个用户的数据,你真的需要用户键和用户字典作为值吗?仅仅拥有用户字典还不够吗?上下文不应该表明 JSON 描述的是用户吗? @Palle 不幸的是,您并不总是可以选择需要处理的数据格式。 【参考方案1】:

您可以使用字典进行解码:用户组合然后提取用户对象。例如

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


let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)

【讨论】:

不错的解决方案!它展示了这些解码/编码 Swift 4 API 的灵活性;) 非常聪明!我的注意力集中在类和结构上,所以我没想过要把它做成字典!我将此解决方案标记为正确,因为它可能是最普遍有用的答案,但 Paulo 和 Rob 的解决方案也非常好。有点冗长,但更独立。谢谢! @JoshuaBreeden 顺便问一下,Ollie 解决方案是否按预期工作?我仍在运行 Xcode 8,所以我无法自己测试它;( @PauloMattos,是的,效果很好。唯一需要注意的是,就像 Rob 的回答提到的那样,作为调用者,您必须知道 JSON 中用于提取 User 对象的字典键。 有没有办法使用这个解决方案进行编码?【参考方案2】:

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。

【讨论】:

正在寻找其他东西,但这个答案向我展示了nestedContainer,这是我的问题的解决方案!谢谢你!!! 更复杂的 jsons 的好答案! 嘿 Rob,我发布了一个关于多个容器的问题,很想听听你的想法。 ***.com/questions/48410909/…谢谢!【参考方案3】:

当然,您始终可以实现自己的自定义解码/编码 - 但对于这种简单的场景,您的包装器类型是 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 更容易;)

【讨论】:

很好的答案!【参考方案4】:

我为 Codable 创建了一个辅助扩展,这将使这样的事情变得更容易。

见https://github.com/evermeer/Stuff#codable

您可以像这样创建用户对象的实例:

    let v = User(json: json, keyPath: "user")

您不必更改原始 User 结构中的任何内容,也不需要包装器。

【讨论】:

以上是关于Swift 4 可编码;如何使用单个根级密钥解码对象的主要内容,如果未能解决你的问题,请参考以下文章

Swift 4 解码简单的根级 json 值

Swift 可编码初始化

在 swift 4 中使用可编码的 JSON 时出错?

如何在 Core Data 中使用 swift 4 Codable?

如何在 Core Data 中使用 swift 4 Codable?

如何在 Core Data 中使用 swift 4 Codable?