问题
I'm using the Swift 4 Codable
protocol with JSON data. My data is formatted such that there is a single key at the root level with an object value containing the properties I need, such as:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
I have a User
struct that can decode the user
key:
struct User: Codable {
let id: Int
let username: String
}
Since id
and username
are properties of user
, not at the root level, I needed to make a wrapper type like so:
struct UserWrapper: Codable {
let user: User
}
I can then decode the JSON via the UserWrapper
, and the User
is decoded also. It seems like a lot of redundant code since I'll need an extra wrapper on every type I have. Is there a way to avoid this wrapper pattern or a more correct/elegant way of handling this situation?
回答1:
You could decode using a dictionary: user combination then extract out the user object. e.g.
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
回答2:
Ollie's answer is definitely the best way to go for this case, but it does push some knowledge into the caller, which may be undesirable. It also isn't very flexible. I still think it's a great answer and exactly what you want here, but this is a nice simple example to explore custom structural encoding.
How can we make this work correctly:
let user = try? JSONDecoder().decode(User.self, from: json)
We can't use the default conformances anymore. We have to build our own decoder. That's slightly tedious, but not difficult. First, we need to encode the structure into 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
}
}
With that, we can decode User
by hand by pulling out the nested container:
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)
}
}
But I'd absolutely do it Ollie's way for this problem.
For much more on this see Encoding and Decoding Custom Types.
回答3:
Of course, you can always implement your own custom decoding/encoding — but for this simple scenario your wrapper type is a much better solution IMO ;)
For comparison, the custom decoding would look like this:
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)
}
}
and you still to conform to the Encodable
protocol if you want to support encoding as well. As I said before, your simple UserWrapper
is much easier ;)
回答4:
I created a helper extension for Codable that will make things like this easier.
see https://github.com/evermeer/Stuff#codable
With that you can create an instance of your user object like this:
let v = User(json: json, keyPath: "user")
You don't have to change anything in your original User struct and you don't need a wrapper.
来源:https://stackoverflow.com/questions/44715494/swift-4-codable-how-to-decode-object-with-single-root-level-key