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")

Вам не нужно ничего менять в своей исходной структуре пользователя, и вам не нужна оболочка.