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?
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.