Пропуск пустой строки с помощью JSONEncoders swift

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