Как декодировать JSON массив с различными объектами с помощью Codable в Swift? - PullRequest
0 голосов
/ 11 января 2020

У меня есть JSON, который состоит из верхнего объекта, а затем массива, состоящего из различных JSON объектов. Я хочу декодировать это json с минимальной структурой и без дополнительных переменных. Если я могу достичь этого, я также хочу разработать структуру, которая обрабатывает все объекты массива путем написания только соответствующей структуры.

Я постараюсь упростить пример

JSON Image

Как вы видите на изображении, оба "Id", "Token", "ServicePublicKey" являются различными JSON объектами. Весь мой бэкэнд возвращается в этой архитектуре JSON. То, чего я хочу достичь - это одна структура в качестве оболочки и структура для (Id, ServicePublicKey, Token et c ..). В конце, когда появляется новый тип из JSON, мне нужно написать только соответствующую структуру и добавить некоторый код внутри оболочки.

Мой вопрос таков: Как я могу разобрать это JSON без какой-либо необязательной переменной?

Как я пытаюсь ее проанализировать:

struct Initialization: Decodable {
var error: BunqError? //TODO: Change this type to a right one
var id: Int?
var publicKey: String?
var token: Token?

enum CodingKeys: String, CodingKey {
    case error = "Error"
    case data = "Response"
    case Id = "Id"
    case id = "id"
    case ServerPublicKey = "ServerPublicKey"
    case Token = "Token"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    error = nil
    if let errorArray = try container.decodeIfPresent([BunqError].self, forKey: .error) {
        if !errorArray.isEmpty {
            error = errorArray[0]
        }
    }
    if let unwrappedResponse = try container.decodeIfPresent([Response<Id>].self, forKey: .data) {
        print(unwrappedResponse)
    }
}
}
struct Response<T: Decodable>: Decodable {
let responseModel: T?

enum CodingKeys: String, CodingKey {
    case Id = "Id"
    case ServerPublicKey = "ServerPublicKey"
    case Token = "Token"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    switch "\(T.self)"
    {
    case CodingKeys.Id.rawValue:
        self.responseModel = try container.decode(T.self, forKey: .Id)
        break;
    case CodingKeys.ServerPublicKey.rawValue:
        self.responseModel = try container.decode(T.self, forKey: .ServerPublicKey)
        break;
    default:
        self.responseModel = nil
        break;
    }
}
}

struct Id: Decodable {
let id: Int

enum CodingKeys: String, CodingKey {
    case id = "id"
}
}

struct ServerPublicKey: Decodable {
let server_public_key: String
}
struct Token: Decodable {
let created: String
let updated: String
let id: Int
let token: String
}

Json Пример:

    {
  "Response" : [
    {
      "Id" : {
        "id" : 123456
      }
    },
    {
      "Token" : {
        "token" : "myToken",
        "updated" : "2020-01-11 13:55:43.397764",
        "created" : "2020-01-11 13:55:43.397764",
        "id" : 123456
      }
    },
    {
      "ServerPublicKey" : {
        "server_public_key" : "some key"
      }
    }
  ]
}

Вопрос в том, как получить n-й элемент массива JSON при декодировании с Codable в Swift?

1 Ответ

3 голосов
/ 11 января 2020

То, чего я хочу достичь, - это одна структура как оболочка и структура для (Id, ServicePublicKey, Token et c ..). В конце, когда появляется новый тип из JSON, мне нужно написать только соответствующую структуру и добавить некоторый код внутри оболочки. У меня такой вопрос: как я могу разобрать JSON без какой-либо необязательной переменной?

Прежде всего, я полностью согласен с вашей идеей. При декодировании JSON мы всегда должны стремиться к

  • без опций (если это гарантировано бэкэндом)
  • легкая расширяемость

Давайте начнем

Итак, учитывая это JSON

let data = """
    {
        "Response": [
            {
                "Id": {
                    "id": 123456
                }
            },
            {
                "Token": {
                    "token": "myToken",
                    "updated": "2020-01-11 13:55:43.397764",
                    "created": "2020-01-11 13:55:43.397764",
                    "id": 123456
                }
            },
            {
                "ServerPublicKey": {
                    "server_public_key": "some key"
                }
            }
        ]
    }
""".data(using: .utf8)!

Модель ID

struct ID: Decodable {
    let id: Int
}

Модель токена

struct Token: Decodable {
    let token: String
    let updated: String
    let created: String
    let id: Int
}

Модель ServerPublicKey

struct ServerPublicKey: Decodable {
    let serverPublicKey: String
    enum CodingKeys: String, CodingKey {
        case serverPublicKey = "server_public_key"
    }
}

Результат Модель

struct Result: Decodable {

    let response: [Response]

    enum CodingKeys: String, CodingKey {
        case response = "Response"
    }

    enum Response: Decodable {

        enum DecodingError: Error {
            case wrongJSON
        }

        case id(ID)
        case token(Token)
        case serverPublicKey(ServerPublicKey)

        enum CodingKeys: String, CodingKey {
            case id = "Id"
            case token = "Token"
            case serverPublicKey = "ServerPublicKey"
        }

        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            switch container.allKeys.first {
            case .id:
                let value = try container.decode(ID.self, forKey: .id)
                self = .id(value)
            case .token:
                let value = try container.decode(Token.self, forKey: .token)
                self = .token(value)
            case .serverPublicKey:
                let value = try container.decode(ServerPublicKey.self, forKey: .serverPublicKey)
                self = .serverPublicKey(value)
            case .none:
                throw DecodingError.wrongJSON
            }
        }
    }
}

Давайте расшифруем!

Мы наконец-то сможем декодировать ваш JSON

do {
    let result = try JSONDecoder().decode(Result.self, from: data)
    print(result)
} catch {
    print(error)
}

Вывод

И это вывод

Result(response: [
    Result.Response.id(
        Result.Response.ID(
            id: 123456
        )
   ),
   Result.Response.token(
        Result.Response.Token(
            token: "myToken",
            updated: "2020-01-11 13:55:43.397764",
            created: "2020-01-11 13:55:43.397764",
            id: 123456)
    ),
    Result.Response.serverPublicKey(
        Result.Response.ServerPublicKey(
            serverPublicKey: "some key"
        )
    )
])

Декодирование даты

Я оставляю вам расшифровку даты как домашнее задание; -)

ОБНОВЛЕНИЕ

Эта дополнительная часть должен ответить на ваш комментарий

Можем ли мы хранить переменные типа id, serverPublicKey внутри структуры Result без массива Response. Я имею в виду вместо ResponseArray мы можем просто иметь свойства? Я думаю, что это требует своего рода отображения, но я не могу понять.

Да, я думаю, что мы можем.

Нам нужно добавить еще одну структуру к уже описанным выше.

Вот оно

struct AccessibleResult {

    let id: ID
    let token: Token
    let serverPublicKey: ServerPublicKey

    init?(result: Result) {

        typealias ComponentsType = (id: ID?, token: Token?, serverPublicKey: ServerPublicKey?)

        let components = result.response.reduce(ComponentsType(nil, nil, nil)) { (res, response) in
            var res = res
            switch response {
            case .id(let id): res.id = id
            case .token(let token): res.token = token
            case .serverPublicKey(let serverPublicKey): res.serverPublicKey = serverPublicKey
            }
            return res
        }

        guard
            let id = components.id,
            let token = components.token,
            let serverPublicKey = components.serverPublicKey
        else { return nil }

        self.id = id
        self.token = token
        self.serverPublicKey = serverPublicKey
    }
}

Эта структура AccessibleResult имеет инициализатор, который получает значение Result и пытается заполнить его 3 свойства

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

Если все идет хорошо, я имею в виду, если вход Result содержит хотя бы ID, Token и ServerPublicKey, тогда AccessibleResponse инициализируется, в противном случае инициализация завершается неудачно и возвращается nil`.

Тест

if
    let result = try? JSONDecoder().decode(Result.self, from: data),
    let accessibleResult = AccessibleResult(result: result) {
        print(accessibleResult)
}
...