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 t
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.
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.
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)
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 ;)