Elegantly populate dictionary from a struct checking nil values

后端 未结 2 1866
温柔的废话
温柔的废话 2021-01-15 21:47

Given a struct A, I want to populate a NSDictionary with values from that struct, provided they are not nil.
To do that I insert all the valu

相关标签:
2条回答
  • 2021-01-15 21:57

    As @vacawama says, you should generally steer clear of Dictionary types with optional values, as the Dictionary API is built around using nil to indicate the lack of a value for a given key. Therefore using an optional type for a Dictionary's Value results in confusing double-wrapped optionals, which can often exhibit unintuitive behaviour.

    Another (more overkill) solution would be to directly utilise dictionary literals by defining your own 'value unwrapping' ExpressibleByDictionaryLiteral type.

    extension Dictionary {
    
        /// A dictionary wrapper that unwraps optional values in the dictionary literal
        /// that it's created with.
        struct ValueUnwrappingLiteral : ExpressibleByDictionaryLiteral {
    
            typealias WrappedValue = Dictionary.Value
    
            fileprivate let base: [Key : WrappedValue]
    
            init(dictionaryLiteral elements: (Key, WrappedValue?)...) {
    
                var base = [Key : WrappedValue]()
    
                // iterate through the literal's elements, unwrapping the values,
                // and updating the base dictionary with them.
                for case let (key, value?) in elements {
    
                    if base.updateValue(value, forKey: key) != nil {
                        // duplicate keys are not permitted.
                        fatalError("Dictionary literal may not contain duplicate keys")
                    }
                }
    
                self.base = base
            }
        }
    
        init(unwrappingValues literal: ValueUnwrappingLiteral) {
            self = literal.base // simply get the base of the unwrapping literal.
        }
    }
    

    You can then simply pass instances of this type to our init(unwrappingValues:) initialiser:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "foo", second: nil, third: "baz")
    
    let dictionary = Dictionary(unwrappingValues: [
        "first"  : a.first,
        "second" : a.second,
        "third"  : a.third
    ])
    
    print(dictionary) // ["third": "baz", "first": "foo"]
    
    0 讨论(0)
  • 2021-01-15 22:24

    It's usually not a good idea to have a dictionary with a value that is optional. Dictionaries use the assignment of nil as an indication that you want to delete a key/value pair from the dictionary. Also, dictionary lookups return an optional value, so if your value is optional you will end up with a double optional that needs to be unwrapped twice.

    You can use the fact that assigning nil deletes a dictionary entry to build up a [String : String] dictionary by just assigning the values. The ones that are nil will not go into the dictionary so you won't have to remove them:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "one", second: nil, third: "three")
    
    let pairs: [(String, String?)] = [
        ("first", a.first),
        ("second", a.second),
        ("third", a.third)
    ]
    
    var dictionary = [String : String]()
    
    for (key, value) in pairs {
        dictionary[key] = value
    }
    
    print(dictionary)
    
    ["third": "three", "first": "one"]
    

    As @Hamish noted in the comments, you can use a DictionaryLiteral (which internally is just an array of tuples) for pairs which allows you to use the cleaner dictionary syntax:

    let pairs: DictionaryLiteral<String,String?> = [
        "first":  a.first,
        "second": a.second,
        "third":  a.third
    ]
    

    All of the other code remains the same.

    Note: You can just write DictionaryLiteral and let the compiler infer the types, but I have seen Swift fail to compile or compile very slowly for large dictionary literals. That is why I have shown the use of explicit types here.


    Alternatively, you can skip the Array or DictionaryLiteral of pairs and just assign the values directly:

    struct A {
        var first: String?
        var second: String?
        var third: String?
    }
    
    let a = A(first: "one", second: nil, third: "three")
    
    var dictionary = [String : String]()
    
    dictionary["first"] = a.first
    dictionary["second"] = a.second
    dictionary["third"] = a.third
    
    print(dictionary)
    
    ["third": "three", "first": "one"]
    
    0 讨论(0)
提交回复
热议问题