#swift #codable
#swift #codable
Вопрос:
У меня кодируемое расширение сериализации, которое я использую для преобразования моей кодируемой структуры в словари, проблема, с которой я сталкиваюсь, — это строки. Я получаю строковое значение из своего UITextField, иногда это значение может быть пустым, и в результате декодируется пустая строка. Как я могу вернуть nil, если значение является пустой строкой.
extension Encodable {
var requestDictionary: [String: Any]? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let data = try? encoder.encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
если у меня есть структура
let example = Exa(age: 10, name: "")
let dict = example.requestDictionary
print(dict)
Я хочу, чтобы он просто печатал ["age": 10]
и возвращал nil для пустой строки
Комментарии:
1. исправить это в
Exa
init?self.name = name.isEmpty ? nil : name
. Кстати, почему вы кодируете в JSON, а затем снова декодируете его в словарь?2. Потому что я использую кодируемый объект и выполняю вызов API с параметрами словаря.
3. вы не можете настроить внутри общего расширения, поэтому вам нужно сделать это внутри самой структуры
4. Да, @Sh_Khan ты не можешь.
Ответ №1:
Вы можете реализовать свой собственный String
метод кодирования, расширяя KeyedEncodingContainer
:
extension KeyedEncodingContainer {
mutating func encode(_ value: String, forKey key: K) throws {
guard !value.isEmpty else { return }
try encodeIfPresent(value, forKey: key)
}
}
Кстати, ваш словарь запросов может быть упрощен следующим образом:
extension Encodable {
var dictionary: [String: Any]? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return try? JSONSerialization.jsonObject(with: encoder.encode(self)) as? [String: Any]
}
}
Тестирование игровой площадки:
struct Exa: Encodable {
let age: Int
let name: String
}
let example = Exa(age: 10, name: "")
let dict = example.dictionary!
print(dict) // "["age": 10]n"
Комментарии:
1. Это не удается, когда свойство String не является пустым, например
let example = Exa(age: 10, name: "Leo")
. Функцияencode
продолжает вызывать себя в бесконечном цикле.2. @MarioHuizinga это действительно странно. Не уверен, почему это может вызвать цикл.
3. Я попробовал именно ваш пример на игровой площадке, просто изменив имя. Вы пробовали это? Кажется, что
try encode(value, forKey: key)
оператор вызывает себя снова (и снова).4. @MarioHuizinga да, я заметил, что
5. Я исправил ваше решение, заменив
try encode
наtry encodeIfPresent
. Это предотвращает рекурсию. Теперь это отлично работает, спасибо за эту идею. Я отправил редактирование вашего ответа.
Ответ №2:
Я просто использую другой подход, используя оболочку свойств, чтобы отметить, какие свойства могут быть пропущены.
@propertyWrapper
struct SkipEmpty {
var wrappedValue: String
}
extension SkipEmpty: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
// nothing to do here, see below
}
}
Но для фактического пропуска вам также необходимо создать перегрузку для KeyedEncodingContainer.encode
метода для SkipEmpty
типа:
extension KeyedEncodingContainer {
mutating func encode(_ value: SkipEmpty, forKey key: K) throws {
if !value.wrappedValue.isEmpty {
try encode(value.wrappedValue, forKey: key) // encode the value here
}
}
}
Возможно, вы могли бы попытаться сделать его более общим, например SkipEmpty<T: Codable>
, и предоставить другой аргумент для пропуска значения или предиката и т.д…
Использование:
struct Exa: Encodable {
var age: Int
@SkipEmpty var name: String
}