Generic Swift Dictionary Extension for Nil Filtering

Generic Swift Dictionary Extension for nil filtering

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

Creating an extension to filter nils from an Array in Swift

It's not possible to restrict the type defined for a generic struct or class - the array is designed to work with any type, so you cannot add a method that works for a subset of types. Type constraints can only be specified when declaring the generic type

The only way to achieve what you need is by creating either a global function or a static method - in the latter case:

extension Array {
static func filterNils(_ array: [Element?]) -> [Element] {
return array.filter { $0 != nil }.map { $0! }
}
}

var array:[Int?] = [1, nil, 2, 3, nil]

Array.filterNils(array)

Or simply use compactMap (previously flatMap), which can be used to remove all nil values:

[1, 2, nil, 4].compactMap { $0 } // Returns [1, 2, 4]

Elegantly populate dictionary from a struct checking nil values

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

Add constraints to generic parameters in extension

Try this code in the Playground:

// make sure only `Optional` conforms to this protocol
protocol OptionalEquivalent {
typealias WrappedValueType
func toOptional() -> WrappedValueType?
}

extension Optional: OptionalEquivalent {
typealias WrappedValueType = Wrapped

// just to cast `Optional<Wrapped>` to `Wrapped?`
func toOptional() -> WrappedValueType? {
return self
}
}

extension Dictionary where Value: OptionalEquivalent {
func flatten() -> Dictionary<Key, Value.WrappedValueType> {
var result = Dictionary<Key, Value.WrappedValueType>()
for (key, value) in self {
guard let value = value.toOptional() else { continue }
result[key] = value
}
return result
}
}

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil]
a.flatten() //["a": "a", "c": "c"]

Because you cannot specify an exact type in the where clause of a protocol extension, one way you may detect exactly the Optional type is to make Optional UNIQUELY conforms to a protocol (say OptionalEquivalent).

In order to get the wrapped value type of the Optional, I defined a typealias WrappedValueType in the custom protocol OptionalEquivalent and then made an extension of Optional, assgin the Wrapped to WrappedValueType, then you can get the type in the flatten method.

Note that the sugarCast method is just to cast the Optional<Wrapped> to Wrapped?(which is exactly the same thing), to enable the usage guard statement.

UPDATE

Thanks to Rob Napier 's comment I have simplified & renamed the sugarCast() method and renamed the protocol to make it more understandable.

Dynamically remove null value from swift dictionary using function

Rather than using a global function (or a method), why not making it a method of Dictionary, using an extension?

extension Dictionary {
func nullKeyRemoval() -> Dictionary {
var dict = self

let keysToRemove = Array(dict.keys).filter { dict[$0] is NSNull }
for key in keysToRemove {
dict.removeValue(forKey: key)
}

return dict
}
}

It works with any generic types (so not limited to String, AnyObject), and you can invoke it directly from the dictionary itself:

var dic : [String: AnyObject] = ["FirstName": "Anvar", "LastName": "Azizov", "Website": NSNull(),"About": NSNull()]
let dicWithoutNulls = dic.nullKeyRemoval()

How can I write a function that will unwrap a generic property in swift assuming it is an optional type?

The "trick" is to define a protocol to which all optionals conform
(this is from Creating an extension to filter nils from an Array in Swift
with a minor simplification; the idea goes back to this Apple Forum Thread):

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

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

You can use that in your case as:

class GenericClass<SomeType> {
var someProperty: [SomeType] = []
}

extension GenericClass where SomeType : OptionalType {
func filterNilValuesOfSomeProperty() -> [SomeType.Wrapped] {
return someProperty.flatMap { $0.intoOptional() }
}
}

which uses the flatMap() method from SequenceType:

extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

Example:

let aClass = GenericClass<Int?>()
aClass.someProperty = [3,5,6,nil,4,3,6, nil]

let x = aClass.someProperty
print(x) // [Optional(3), Optional(5), Optional(6), nil, Optional(4), Optional(3), Optional(6), nil]

let y = aClass.filterNilValuesOfSomeProperty()
print(y) // [3, 5, 6, 4, 3, 6]

In Swift 3 and later the protocol has to be defined as

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

Swift Dictionary access value using key within extension

Swift type Dictionary has two generic parameters Key and Value, and Key may not be String.

This works:

public extension Dictionary {
public func bool(_ key: Key) -> Bool? {
return self[key] as? Bool
}
}

let dict: [String: Any] = [
"a": true,
"b": 0,
]
if let a = dict.bool("a") {
print(a) //->true
}
if let b = dict.bool("b") {
print(b) //not executed
}

For ADDED part.

If you introduce a new generic parameter T in extension of Dictionary, the method needs to work for all possible combination of Key(:Hashable), Value and T(:Hashable). Key and T may not be the same type.

(For example, Key may be String and T may be Int (both Hashable). You know you cannot subscript with Int when Key is String.)

So, you cannot subscript with key of type T.


For updated ADDED part.

Seems to be a reasonable misunderstanding. And you have found a good example that explains protocol with associated type is not just a generic protocol.

Swift 3 - extend an Array of Dictionary String, Any


extension Sequence where Iterator.Element == [String: Any] {

func values(of key: String) -> [Any]? {
var result: [Any] = []
for value in self {
let val = value
for (k, v) in val {
if k == key {
result.append(v)
break
}
}
}
return result.isEmpty ? nil : result
}
}

var dicts: [[String: Any]] = [["key1": "value1", "key2": "value2"], ["key1": "value3", "key2": "value4"]]
dicts.values(of: "key1") // ["value1", "value3"]

or shorter version:

extension Sequence where Iterator.Element == [String: Any] {

func values(of key: String) -> [Any]? {
let result: [Any] = reduce([]) { (result, dict) -> [Any] in
var result = result
result.append(dict.filter{ $0.key == key }.map{ $0.value })
return result
}
return result.isEmpty ? nil : result
}
}

var dicts: [[String: Any]] = [["key1": "value1", "key2": "value2"], ["key1": "value3", "key2": "value4"]]
dicts.values(of: "key1")

and without reduce functionality:

extension Sequence where Iterator.Element == [String: Any] {

func values(of key: String) -> [Any]? {
var result: [Any] = []
forEach {
result.append($0.filter{ $0.key == key }.map{ $0.value })
}
return result.isEmpty ? nil : result
}
}

var dicts: [[String: Any]] = [["key1": "value1", "key2": "value2"], ["key1": "value3", "key2": "value4"]]
dicts.values(of: "key1")

Unable to return nil in generic function

In order to return nil you need to return a Key Optional " Key? "

You can read more about optionals here.

func findKey<Key, Value: Equatable>(for value: Value, in dictionary: [Key: Value]) -> Key? {
for set in dictionary {
if set.value == value {
return set.key
}
}
return nil
}


Related Topics



Leave a reply



Submit