Я имел дело с запросом на изменение структуры нашей модели iOS, модификация заключалась в перемещении параметра EncodingOption
в класс EditingOptions
из OutputOptions
struct.
Иерархия конфигурации
AppConfig {
profile: ProfileSetting
}
ProfileSetting {
editSetting:EditingOptions
outputSetting: OutputOptions
}
Предыдущая структура данных:
class EditingOptions: NSObject, Codable {
//..other parameters
}
struct OutputOptions: Codable {
var barcodeEncoding: EncodingOption = .utf8
//..other parameters
}
Измененная структура данных:
class EditSetting: NSObject, Codable {
var encodingOption: EncodingOption
//..other parameters
}
struct OutputSetting: Codable {
//..other parameters
}
Измененная структура была несовместима с предыдущим файлом конфигурации с ошибкой JSON keyNotFound
:
keyNotFound(CodingKeys(stringValue: "encodingOption", intValue: nil),
Swift.DecodingError.Context(codingPath: [
CodingKeys(stringValue: "profileManage", intValue: nil), CodingKeys(stringValue: "deviceName",
intValue: nil), CodingKeys(stringValue: "profiles", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "outputOptions", intValue: nil)
],
debugDescription: "No value associated with key CodingKeys(stringValue: \"encodingOption\", intValue: nil) (\"encodingOption\").", underlyingError: nil))
Решением ChatGPT было добавление метода init(from decoder: Decoder)
для класса ProfileSetting
:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
//...other parameters
if let editingOptions = try? container.decode(EditingOptions.self, forKey: .editingOptions) {
self.editingOptions = editingOptions
} else {
self.editingOptions = EditingOptions()
}
}
barcodeEncoding
был скрытым параметром с настройкой по умолчанию и еще не был опубликован.
Решение устранило ошибку keyNotFound
, однако я не уверен, что это решение было ПРАВИЛЬНЫМ способом решения проблемы.
Извините за недопонимание, изменилась только модель iOS, и она должна быть совместима с предыдущим форматом JSON (который до изменения поставлялся с моделью iOS).
Что вы подразумеваете под «правильным» способом? Ваш код работает так, как вы ожидаете? Если да, то это правильно.
Либо работайте с дополнительными свойствами, либо «исправьте» проблему во время декодирования: сначала декодируйте, используя новую версию, а если это не удается, декодируйте, используя старую версию, немедленно преобразуйте в новую версию и сохраните преобразованные данные.
Я не понимаю, почему вы исправляете это реальное решение. Если encodingOption
нет в JSON (а есть пользовательское значение), вы можете просто сделать enum CodingKeys: String, CodingKey { case abc }
и поместить только ключи, присутствующие в JSON, опуская encodingOption
.
@JoakimDanielson Я тестирую несколько случаев, способ «исправления» приведет к минусам, заключающимся в том, что все параметры редактирования оговорок будут сброшены до значений по умолчанию, если editingOptions
равен нулю. Однако, если не использовать дополнительный метод try
, все работает как по маслу, но я не уверен, как это работает. self.editingOptions = try container.decode(EditingOptions.self, forKey: .editingOptions)
@Larme, encodingOption
уже был в JSON до модификации со значением по умолчанию.
Я не понял, почему этот вопрос получает отрицательный голос, были предоставлены блок кода, журнал консоли и контекст. Вопрос может показаться вам глупым нубом, или мое английское выражение дерьмовое, что заставило вас проголосовать против.
Я не проверял, но, по моему мнению, не всегда ли вы попадаете в self.editingOptions = EditingOptions()
случай?
Используйте app.quicktype.io. Дайте ему все доступные случаи, и он сгенерирует для вас парсер.
Если я правильно понял, вашу проблему можно упростить так:
let json = """
{
"a": { "keyA": "Value for KeyA" },
"b": { "keyB": "Value for KeyB",
"otherKey": "My value To Pass to a now "}
}
"""
Где otherKey
твой encodingOption
. Он присутствует на B
, но теперь вы хотите передать его A
.
Итак, старый код:
func oldCode(with json: String) {
struct OldParent: Codable {
let a: OldA
let b: OldB
}
class OldA: NSObject, Codable {
let keyA: String
}
struct OldB: Codable {
let keyB: String
let otherKey: String
}
let decoder = JSONDecoder()
do {
let parent = try decoder.decode(OldParent.self, from: Data(json.utf8))
print(parent)
print("--")
} catch {
print("Error: \(error)")
}
}
Он работает так, как задумано, ваша модель iOS отражает модель API.
Теперь вы хотите изменить свою модель iOS, но модель API не изменится.
Возможное решение — сохранить otherKey
в B
, а внутри декодирования Parent
(которое знает и A
, и B
) передать это значение в A
:
func newCode(with json: String) {
struct NewParent: Codable {
let a: NewA
let b: NewB
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.a = try container.decode(NewA.self, forKey: .a)
self.b = try container.decode(NewB.self, forKey: .b)
self.a.otherKey = self.b.otherKey //Pass the value
}
}
class NewA: NSObject, Codable {
let keyA: String
var otherKey: String = "defaultValue"
enum CodingKeys: CodingKey {
case keyA //We skip here otherKey to tell the decoder to decode only keyA which is present in the JSON
}
}
struct NewB: Codable {
let keyB: String
let otherKey: String //Only because it's in JSON
}
let decoder = JSONDecoder()
do {
let parent = try decoder.decode(NewParent.self, from: Data(json.utf8))
print(parent)
print("--")
} catch {
print("Error: \(error)")
}
}
Обновлено: добавление еще одной альтернативы:
Если вам не нравится наличие let otherKey: String //Only because it's in JSON
в B
и вы хотите, чтобы он был только в A
, вы действительно можете расшифровать его самостоятельно. Я не проводил тест на эффективность, но думаю, это займет немного больше времени, потому что нужно снова декодировать контейнер для B
(один раз для otherKey
и другой для B
, может быть кеш и другие оптимизации, я полностью не проверял).
func newCode2(with json: String) {
struct NewParent: Codable {
let a: NewA
let b: NewB
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.a = try container.decode(NewA.self, forKey: .a)
self.b = try container.decode(NewB.self, forKey: .b)
// Here decode again b and look for the value of otherKey
let containerForOtherKeyInB = try container.nestedContainer(keyedBy: CustomKey.self, forKey: .b)
self.a.otherKey = try containerForOtherKeyInB.decode(String.self, forKey: .otherKey)
}
}
class NewA: NSObject, Codable {
let keyA: String
var otherKey: String = "defaultValue"
enum CodingKeys: CodingKey {
case keyA //We skip here otherKey
}
}
enum CustomKey: CodingKey {
case otherKey
}
struct NewB: Codable {
let keyB: String
}
let decoder = JSONDecoder()
do {
let parent = try decoder.decode(NewParent.self, from: Data(json.utf8))
print(parent)
print("--")
} catch {
print("Error: \(error)")
}
}
Ваше понимание было правильным! Завтра проверю ваше решение, спасибо за подробное описание!
Непонятно: изменился ли JSON или нужно изменить только вашу модель iOS? Или JSON использует две версии, в которых указан параметр кодирования (в зависимости от вызова)?