Во-первых, вы должны уменьшить количество дополнительных в системе. Существуют различные способы работы с коллекциями Optional (например, мутирующие вспомогательные методы, как вы предлагали), но чрезмерное использование Optional создает много ненужной сложности. Очень редко, чтобы Коллекция любого вида была Факультативной. Это имеет смысл, только если nil
и «пустой» означают разные вещи (и это очень редко).
Вместо того, чтобы обернуть всю модель данных вокруг определенного JSON API, преобразуйте JSON в нужную модель данных. Например, вот модель JSON, которая включает требуемый тип Int и может включать или не включать массив, но внутренне мы хотим рассматривать «отсутствующий массив» как «пустой». Мы также хотим удалить пустые массивы перед их отправкой.
import Foundation
let json = Data("""
{
"y": 1
}
""".utf8)
struct X {
var y: Int
var z: [String]
}
extension X: Codable {
enum CodingKeys: String, CodingKey {
case y, z
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
y = try container.decode(Int.self, forKey: .y)
z = try container.decodeIfPresent([String].self, forKey: .z) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(y, forKey: .y)
if !z.isEmpty {
try container.encode(z, forKey: .z)
}
}
}
let decoder = JSONDecoder()
print(try decoder.decode(X.self, from: json))
let encoder = JSONEncoder()
print(String(data: try encoder.encode(X(y: 1, z: [])), encoding: .utf8)!)
Это перемещает всю работу в два метода, а не распределяет ее по всей программе каждый раз, когда вы получаете доступ к модели данных. Настраиваемые кодируемые файлы по-прежнему немного утомительны для написания (и могут содержать незначительные ошибки на этапе кодирования), поэтому, если у вас их много, вам следует обратиться к SwiftGen , который может написать их для вас.
Если вы действительно хотите отслеживать, был ли ключ пропущен, а не пуст (поэтому вы могли бы перекодировать так же, как он был вам отправлен), тогда я бы, вероятно, скрыл необязательные свойства следующим образом:
struct X: Codable{
enum CodingKeys: String, CodingKey {
case y
case _z = "z"
}
var y: Int
private var _z: [String]? // The actual `z` we got from the JSON
var z: [String] { get { return _z ?? [] } set { _z = newValue } }
init(y: Int, z: [String]?) {
self.y = y
self._z = z
}
}
«Реальный» z
хранится в _z
и доступен для повторной сериализации, но остальная часть программы никогда не видит Необязательный.
Еще один довольно распространенный метод - создать слой адаптера, который преобразует «JSON-совместимую» структуру во внутреннюю модель данных и обратно. Это позволяет вашей внутренней модели данных немного отличаться от JSON, если это удобно.
Конечно, вы также можете создавать вспомогательные методы, но реальный ключ ко всему этому - не допустить утечки Optionalal в остальную часть вашей программы, которая на самом деле не является необязательной. Если где-то в системе должна быть сложность, поместите ее в точку синтаксического анализа / кодирования, а не в точку использования.