Swift 4 Codable; Как декодировать объект с помощью одного корневого уровня
Я использую протокол Swift 4 Codable
с данными JSON. Мои данные отформатированы таким образом, что на корневом уровне есть один ключ с значением объекта, содержащим необходимые мне свойства, например:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
У меня есть структура User
, которая может декодировать ключ User
:
struct User: Codable {
let id: Int
let username: String
}
Так как id
и username
являются свойствами User
, а не на корневом уровне, мне нужно сделать такой тип оболочки:
struct UserWrapper: Codable {
let user: User
}
Затем я могу декодировать JSON через UserWrapper
, а также декодировать User
. Это похоже на много избыточного кода, так как мне понадобится дополнительная оболочка для каждого типа, который у меня есть. Есть ли способ избежать этого шаблона обертки или более правильного/элегантного способа обработки этой ситуации?
Ответы
Ответ 1
Вы можете расшифровать с помощью словаря: комбинацию пользователей, затем извлечь пользовательский объект. например.
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
Ответ 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 для этой проблемы.
Более подробно об этом см. Пользовательские типы кодирования и декодирования.
Ответ 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")
Вам не нужно ничего менять в своей исходной структуре пользователя, и вам не нужна оболочка.