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
How to Continue Ble Activities Onto Next View Controller
Animate the Fractioncomplete of Uiviewpropertyanimator for Blurring the Background
When Should I Use Optionals and When Should I Use Non-Optionals with Default Values
Realm Mobile Platform, How to Connect While Offline
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
How Is a Return Value of Anyobject! Different from Anyobject
How to Cast an _Nsmallocblock_ to Its Underlying Type in Swift 3
Subclass of Gkgraphnode Costtonode Method Never Getting Called
In Swift, How to Remove a Uiview from Memory Completely
How to Get Unmanaged Object from Realm Query in Swift
How to Determine If a Variable Passed in Is Reference Type or Value Type
Swift Convert Currency String to Double
Separation of Function Declaration and Definition in Swift
Typecasting or Initialization, Which Is Better in Swift
How to Make Nsattributedstring Codable Compliant
How to Create a Static Class in Swift
How to Bind a Variable to Multiple Alternatives in a Switch Statement