3

I'm looking for a type safe, generic version of this answer.

This is the method signature I'm looking for:

extension Dictionary where Value == Optional<T> {
    func filterNil() -> <Key, T>
}

Is there any way to express this in Swift 3?

Edit:

My motivation for creating a Dictionary with optional values is that I need something like this:

struct User {
    var mail: String?
    var name: String?

    func marshaled() -> [String: Any] {
        return [
            "mail": mail,
            "name": name
        ].filterNil()
    }
}

I much prefer the dictionary literal to creating an empty dictionary and filling the values manually.

Xavier Lowmiller
  • 1,381
  • 1
  • 15
  • 24
  • 2
    Optional dictionary values are nonsensical anyway. By definition a `nil` value means *key is missing*. It is much more efficient to declare all dictionaries with non-optional values. Then you can fix the issue(s) at **compile time**. – vadian Aug 02 '17 at 09:34
  • I get your point and agree with you. I edited the answer to state my motivations for having `Optional` values. Maybe I'll overload the dictionary literal initializer to filter `nil` values. – Xavier Lowmiller Aug 02 '17 at 10:08
  • 3
    Compare https://stackoverflow.com/q/43356680/2976878 – Hamish Aug 02 '17 at 10:13
  • It's easier to declare the members in `User` as non-optional. Most likely in practice every user will require a name and a mail address. – vadian Aug 02 '17 at 10:14
  • @Hamish Thanks, this is golden – Xavier Lowmiller Aug 02 '17 at 10:16
  • @vadian That was just an example to make my problem obvious. My real struct is slightly more complex. – Xavier Lowmiller Aug 02 '17 at 10:18

1 Answers1

12

Update: As of Swift 5 this would be:

let filtered = dict.compactMapValues { $0 }

Update: As of Swift 4, you can simply do

let filtered = dict.filter( { $0.value != nil }).mapValues( { $0! })

It is currently being discussed if Dictionary should get a compactMapValues method which combines filter and mapValues.


(Previous answer:) You can use the same "trick" as in How can I write a function that will unwrap a generic property in swift assuming it is an optional type? and Creating an extension to filter nils from an Array in Swift: define a protocol to which all optionals conform:

protocol OptionalType {
    associatedtype Wrapped
    func intoOptional() -> Wrapped?
}

extension Optional : OptionalType {
    func intoOptional() -> Wrapped? {
        return self
    }
}

Then your dictionary extension can be defined as:

extension Dictionary where Value: OptionalType {
    func filterNil() -> [Key: Value.Wrapped] {
        var result: [Key: Value.Wrapped] = [:]
        for (key, value) in self {
            if let unwrappedValue = value.intoOptional() {
                result[key] = unwrappedValue
            }
        }
        return result
    }
}

Example:

let dict = ["mail": nil, "name": "John Doe"] // Type is [String : String?]
let filtered = dict.filterNil() // Type is [String : String]
print(filtered) // Output: ["name": "John Doe"]
huwr
  • 1,720
  • 3
  • 19
  • 34
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I can't get to write an extension on Dictionary using your Swift 4 update. `OptionalType` doesn't seem to be a thing anymore and `extension Dictionary where Value: Optional` is illegal. Did you manage to do so? – pommefrite Jan 30 '18 at 09:46
  • 1
    @Performat: Which Xcode version are you using? Everything compiles in my Xcode 9.2 (with Swift language version set to 4.0) – But actually you don't need a dictionary extension for Swift 4, it can be done with filter/mapValues alone, as demonstrated in the first code block. – Martin R Jan 30 '18 at 09:50
  • My bad, I just realize you declared `OptionalType` as a proxy. Everything compiles when using your first solution. I was looking for a way to reach the same result without using `OptionalType`. Using `extension Dictionary where Value == Optional`, I lose the type, so this isn't optimal either. – pommefrite Jan 30 '18 at 10:10
  • 1
    @Performat: Then you can use the extension method from the older answer, it still works in Swift 4. – Martin R Jan 30 '18 at 10:11
  • 1
    @Performat: Yes, the "problem" is that you cannot restrict `Value` to a generic type like `Optional`. – Martin R Jan 30 '18 at 10:19
  • Love that Swift is making these pain points progressively nicer! – Xavier Lowmiller Feb 27 '19 at 08:08