King
King

Reputation: 2025

Skipping empty string with JSONEncoders swift

I a codable serialization extension which I use to turn my Codable struct to dictionaries, the problem I am facing is strings. I get string value from my UITextField at at times this value could be empty and as a result an empty string is decoded. How can I return nil if the value is an empty string.

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

if I have a Struct

let example = Exa(age: 10, name: "")
let dict = example.requestDictionary
print(dict)

I want it to just print ["age": 10] and return nil for the empty string

Upvotes: 2

Views: 1286

Answers (2)

Leo Dabus
Leo Dabus

Reputation: 236360

You can implement your own String encoding method extending KeyedEncodingContainer:

extension KeyedEncodingContainer {
    mutating func encode(_ value: String, forKey key: K) throws {
        guard !value.isEmpty else { return }
        try encodeIfPresent(value, forKey: key)
    }
}

Btw your request dictionary can be simplified as:

extension Encodable {
    var dictionary: [String: Any]? {
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        return try? JSONSerialization.jsonObject(with: encoder.encode(self)) as? [String: Any]
    }
}

Playground testing:

struct Exa: Encodable {
    let age: Int
    let name: String
}

let example = Exa(age: 10, name: "")
let dict = example.dictionary!
print(dict)  // "["age": 10]\n"

Upvotes: 2

New Dev
New Dev

Reputation: 49590

I'll just another approach using a property wrapper to mark which properties could be skipped.

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

But to actually skip, you'd also need to create a overload for the KeyedEncodingContainer.encode method for the SkipEmpty type:

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

You could possibly try to make it more generic, e.g. SkipEmpty<T: Codable> and provide another argument for the value to skip or a predicate, etc...


The usage is:

struct Exa: Encodable {
   var age: Int
   @SkipEmpty var name: String
}

Upvotes: 0

Related Questions