2

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

4
  • 1
    fix that in Exa init? self.name = name.isEmpty ? nil : name. By the way, why do you encode to JSON and then decode it again into a dictionary? Commented Oct 7, 2020 at 10:38
  • Because I am using a codable object and making an API call with dictionary parameters. Commented Oct 7, 2020 at 10:40
  • you cant customize inside a general extension so you have to make it inside the struct itself Commented Oct 7, 2020 at 10:44
  • Yeah, @Sh_Khan you can't. Commented Oct 7, 2020 at 10:45

2 Answers 2

2

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"
Sign up to request clarification or add additional context in comments.

7 Comments

This fails when a String property is not empty, e.g. let example = Exa(age: 10, name: "Leo"). The func encode keeps calling itself in an infinite loop.
@MarioHuizinga that is really weird. Not sure why would it cause a loop.
I have tried exactly your example on Playground, just changing the name. Did you try that? It seems that the try encode(value, forKey: key) statement calls itself again (and again).
@MarioHuizinga yes I did notice that
I fixed your solution by replacing try encode with try encodeIfPresent. This prevents the recursion. It works great now, thanks for this idea. I have submitted an Edit on your answer.
|
0

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
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.