2017-06-23 77 views
15

我正在使用带有JSON数据的Swift 4 Codable协议。我的数据被格式化使得存在在根级别的单个密钥与含有我需要的属性,诸如对象值: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也被解码。它看起来像很多冗余的代码,因为我需要每种类型的额外包装。有没有办法避免这种包装模式或更正确/优雅的方式来处理这种情况?

+0

我并没有深入研究'Codable'协议,但我认为最快的方法是直接用内部字典初始化'User'对象。你可以从用户字段中通过从字典中访问'userDicitonary'吗? –

+1

如果您的JSON只包含单个用户的数据,您是否真的需要用户密钥和用户字典作为值?仅仅拥有用户字典是不够的?上下文不应该表明JSON描述了一个用户? – Palle

+1

@Palle不幸的是,你并不总是选择你需要处理的数据格式。 –

回答

19

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

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

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

不错的解决方案!它展示了这些解码/编码Swift 4 API真的有多灵活;) –

+1

非常聪明!我的想法是在类和结构上,所以我不会想到它会成为一本字典!我标记这个解决方案是正确的,因为它可能是最普遍有用的答案,但Paulo和Rob的解决方案也非常好。稍微冗长一些,但是更加独立。谢谢! –

+0

@JoshuaBreeden顺便说一句,没有奥利解决方案按预期工作?我仍然在运行Xcode 8,所以我无法自己测试它( –

4

当然,你总是可以实现自定义的解码/编码 - 但是对于这个简单的场景您的包装类型是一个更好的解决方案海事组织)

相比之下,定制解码是这样的:

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是更容易;)

22

奥利的答案肯定是去这种情况下,最好的办法,但它确实推动了一些知识到调用,这可能是不可取的。它也不是很灵活。我仍然认为这是一个很好的答案,正是你想要的,但这是探索定制结构编码的一个很好的简单例子。

我们怎样才能使这项工作正确:

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

+1

正在寻找其他东西,但是这个答案告诉我nestedContainer这是解决方案对我的问题!谢谢你! –

+0

对于更复杂的jsons很好的答案! –

+0

嗨,Rob,我已经发布了一个关于多个容器的问题,很想听听你的想法https:// stacko verflow.com/questions/48410909/swift-4-decodable-multiple-containers谢谢! –

3

我为Codable创建了一个帮助器扩展,它可以让事情变得更简单。

看到https://github.com/evermeer/Stuff#codable

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

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

你不必在你原来的用户结构改变任何东西,你不需要一个包装。