Не удается декодировать JSON, когда ответ имеет разные типы

#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?
}