#json #swift #decode
Вопрос:
У меня есть ответ API ниже. Ответ «USER_LIST» отличается в зависимости от значения «DATA_NUM».
Проблема, с которой я сталкиваюсь, заключается в том, что когда «DATA_NUM» равно «0», он возвращает пустую строку, А когда «DATA_NUM» равно «1», «USER_LIST» возвращает как объект, так и пустую строку, так что я не могу декодировать с помощью модели ниже. Я хочу построить модель, подходящую для каждого случая, независимо от значения «DATA_NUM».
Как я могу этого достичь? Заранее спасибо.
Ответ API
// when "DATA_NUM": "0"
{
"RESPONSE": {
"DATA_NUM": "0",
"USER_LIST": ""
}
}
// when "DATA_NUM": "1"
{
"RESPONSE": {
"DATA_NUM": "1",
"USER_LIST": [
{
"USER_NAME": "Jason",
"USER_AGE": "30",
"ID": "12345"
},
""
]
}
}
// when "DATA_NUM": "2"
{
"RESPONSE": {
"DATA_NUM": "2",
"USER_LIST": [
{
"USER_NAME": "Jason",
"USER_AGE": "30",
"ID": "12345"
},
{
"USER_NAME": "Amy",
"USER_AGE": "24",
"ID": "67890"
}
]
}
}
Модель
struct UserDataResponse: Codable {
let RESPONSE: UserData?
}
struct UserData: Codable {
let DATA_NUM: String?
let USER_LIST: [UserInfo]?
}
struct UserInfo: Codable {
let USER_NAME: String?
let USER_AGE: String?
let ID: String?
}
Расшифровывать
do {
let res: UserDataResponse = try JSONDecoder().decode(UserDataResponse.self, from: data)
guard let userData: UserData = res.RESPONSE else { return }
print("Successfully decoded", userData)
} catch {
print("failed to decode") // failed to decode when "DATA_NUM" is "0" or "1"
}
Комментарии:
1. Я бы посоветовал не делать все необязательным и использовать перечисления CodingKey, чтобы вы могли использовать лучшие имена свойств. Что касается проблемы декодирования, вам необходимо реализовать пользовательский ввод(от:), я бы сначала попытался декодировать список как массив userInfo, и если это не удастся, просто назначьте свойству пустой массив
2. Я только что увидел, что ваш json недействителен, пожалуйста, опубликуйте правильный json.
3. @JoakimDanielson Спасибо за ваш комментарий? какой случай JSON недействителен? И вы не могли бы показать мне коды, декодирующие список в виде массива Userinfo?
4. На самом деле проблема в том, что во всех примерах отсутствует закрытие } (Я, должно быть, ошибся раньше, потому что думал, что с ними было больше проблем)
5. @JoakimDanielson ты был прав. Я пропустил закрытие}, но проблема все еще остается той же.
Ответ №1:
Вот решение, использующее пользовательский init(from:)
интерфейс для обработки странного СПИСКА ПОЛЬЗОВАТЕЛЕЙ
структура UserDataResponse: Декодируемый { пусть ответ : Данные пользователя
enum CodingKeys: String, CodingKey {
case response = "RESPONSE"
}
}
struct UserData: Decodable {
let dataNumber: String
let users: [UserInfo]
enum CodingKeys: String, CodingKey {
case dataNumber = "DATA_NUM"
case users = "USER_LIST"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
dataNumber = try container.decode(String.self, forKey: .dataNumber)
if let _ = try? container.decode(String.self, forKey: .users) {
users = []
return
}
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .users)
var temp: [UserInfo] = []
do {
while !nestedContainer.isAtEnd {
let user = try nestedContainer.decode(UserInfo.self)
temp.append(user)
}
} catch {}
self.users = temp
}
}
struct UserInfo: Decodable {
let name: String
let age: String
let id: String
enum CodingKeys: String, CodingKey {
case name = "USER_NAME"
case age = "USER_AGE"
case id = "ID"
}
}
Пример (данные 1,данные 2,данные 3 соответствуют примерам json, опубликованным в вопросе)
let decoder = JSONDecoder()
for data in [data1, data2, data3] {
do {
let result = try decoder.decode(UserDataResponse.self, from: data)
print("Response (result.response.dataNumber)")
print(result.response.users)
} catch {
print(error)
}
}
Выход
Ответ 0
[]
Отзыв 1
[__символы отладки_выраж_93.Сведений о пользователях(имя: «Джейсон», возраст: «30», идентификатор: «12345»)]
ответ 2
[__символы отладки_выраж_93.Сведений о пользователях(имя: «Джейсон», возраст: «30», идентификатор: «12345»), __символы отладки_выраж_93.Сведений о пользователях(имя: «Эми», Возраст: «24», идентификатор: «67890»)]
Отредактируйте с помощью альтернативного решения для while
цикла
В приведенном выше коде есть while
цикл, окруженный a do/catch
, так что мы выходим из цикла, как только возникает ошибка, и это работает нормально, так как проблемная пустая строка является последним элементом в массиве json. Это решение было выбрано, поскольку итератор для nestedContainer
не переходит к следующему элементу в случае сбоя декодирования, поэтому простое выполнение обратного с do/catch
(где catch
предложение пусто) внутри цикла приведет к бесконечному циклу.
Альтернативное решение, которое действительно работает, состоит в том, чтобы декодировать «» в улове для продвижения итератора. Я не уверен, нужно ли это здесь, но решение становится немного более гибким в случае, если пустая строка находится где-то еще в массиве, чем в прошлом.
Альтернативный цикл:
while !nestedContainer.isAtEnd {
do {
let user = try nestedContainer.decode(UserInfo.self)
temp.append(user)
} catch {
_ = try! nestedContainer.decode(String.self)
}
}
Ответ №2:
Вы можете написать этот код, чтобы решить эту проблему со строками массива.
struct UserDataResponse: Codable {
let RESPONSE: UserData?
}
struct UserData: Codable {
let DATA_NUM: String?
let USER_LIST: [UserInfo]?
struct USER_LIST: Codable {
var USER_LIST: CustomMetadataType
}
}
enum CustomMetadataType: Codable {
case array([String])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .array(container.decode(Array.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(CustomMetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .array(let array):
try container.encode(array)
case .string(let string):
try container.encode(string)
}
}
}
struct UserInfo: Codable {
let USER_NAME: String?
let USER_AGE: String?
let ID: String?
}