То, чего я хочу достичь, - это одна структура как оболочка и структура для (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)
}