Let's say I have a rather complex dictionary, like this one:
let dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
I can access all fields with if let .. blocks optionally casting to something that I can work with, when reading.
However, I am currently writing unit tests where I need to selectively break dictionaries in multiple ways.
But I don't know how to elegantly remove keys from the dictionary.
For example I want to remove the key "japan" in one test, in the next "lat" should be nil.
Here's my current implementation for removing "lat":
if var countries = dict["countries"] as? [String: Any],
var japan = countries["japan"] as? [String: Any],
var capital = japan["capital"] as? [String: Any]
{
capital.removeValue(forKey: "lat")
japan["capital"] = capital
countries["japan"] = japan
dictWithoutLat["countries"] = countries
}
Surely there must be a more elegant way?
Ideally I'd write a test helper that takes a KVC string and has a signature like:
func dictWithoutKeyPath(_ path: String) -> [String: Any]
In the "lat" case I'd call it with dictWithoutKeyPath("countries.japan.capital.lat").
When working with a subscript, if the subscript is get/set and the variable is mutable, then the entire expression is mutable. However, due to the type cast the expression "loses" the mutability. (It's not an l-value anymore).
The shortest way to solve this is by creating a subscript that is get/set and does the conversion for you.
extension Dictionary {
subscript(jsonDict key: Key) -> [String:Any]? {
get {
return self[key] as? [String:Any]
}
set {
self[key] = newValue as? Value
}
}
}
Now you can write the following:
dict[jsonDict: "countries"]?[jsonDict: "japan"]?[jsonDict: "capital"]?["name"] = "berlin"
We liked this question so much that we decided to make a (public) Swift Talk episode about it: mutating untyped dictionaries
I'd to like to follow up on my previous answer with another solution. This one extends Swift's Dictionary type with a new subscript that takes a key path.
I first introduce a new type named KeyPath to represent a key path. It's not strictly necessary, but it makes working with key paths much easier because it lets us wrap the logic of splitting a key path into its components.
import Foundation
/// Represents a key path.
/// Can be initialized with a string of the form "this.is.a.keypath"
///
/// We can't use Swift's #keyPath syntax because it checks at compilet time
/// if the key path exists.
struct KeyPath {
var elements: [String]
var isEmpty: Bool { return elements.isEmpty }
var count: Int { return elements.count }
var path: String {
return elements.joined(separator: ".")
}
func headAndTail() -> (String, KeyPath)? {
guard !isEmpty else { return nil }
var tail = elements
let head = tail.removeFirst()
return (head, KeyPath(elements: tail))
}
}
extension KeyPath {
init(_ string: String) {
elements = string.components(separatedBy: ".")
}
}
extension KeyPath: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(value)
}
init(unicodeScalarLiteral value: String) {
self.init(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(value)
}
}
Next I create a dummy protocol named StringProtocol that we later need to constrain our Dictionary extension. Swift 3.0 doesn't yet support extensions on generic types that constrain a generic parameter to a concrete type (such as extension Dictionary where Key == String). Support for this is planned for Swift 4.0, but until then, we need this little workaround:
// We need this because Swift 3.0 doesn't support extension Dictionary where Key == String
protocol StringProtocol {
init(string s: String)
}
extension String: StringProtocol {
init(string s: String) {
self = s
}
}
Now we can write the new subscripts. The implementation for the getter and setter are fairly long, but they should be straightforward: we traverse the key path from beginning to end and then get/set the value at that position:
// We want extension Dictionary where Key == String, but that's not supported yet,
// so work around it with Key: StringProtocol.
extension Dictionary where Key: StringProtocol {
subscript(keyPath keyPath: KeyPath) -> Any? {
get {
guard let (head, remainingKeyPath) = keyPath.headAndTail() else {
return nil
}
let key = Key(string: head)
let value = self[key]
switch remainingKeyPath.isEmpty {
case true:
// Reached the end of the key path
return value
case false:
// Key path has a tail we need to traverse
switch value {
case let nestedDict as [Key: Any]:
// Next nest level is a dictionary
return nestedDict[keyPath: remainingKeyPath]
default:
// Next nest level isn't a dictionary: invalid key path, abort
return nil
}
}
}
set {
guard let (head, remainingKeyPath) = keyPath.headAndTail() else {
return
}
let key = Key(string: head)
// Assign new value if we reached the end of the key path
guard !remainingKeyPath.isEmpty else {
self[key] = newValue as? Value
return
}
let value = self[key]
switch value {
case var nestedDict as [Key: Any]:
// Key path has a tail we need to traverse
nestedDict[keyPath: remainingKeyPath] = newValue
self[key] = nestedDict as? Value
default:
// Invalid keyPath
return
}
}
}
}
And this is how it looks in use:
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
dict[keyPath: "countries.japan"] // ["language": "japanese", "capital": ["lat": "35.6895", "name": "tokyo", "lon": "139.6917"]]
dict[keyPath: "countries.someothercountry"] // nil
dict[keyPath: "countries.japan.capital"] // ["lat": "35.6895", "name": "tokyo", "lon": "139.6917"]
dict[keyPath: "countries.japan.capital.name"] // "tokyo"
dict[keyPath: "countries.japan.capital.name"] = "Edo"
dict[keyPath: "countries.japan.capital.name"] // "Edo"
dict[keyPath: "countries.japan.capital"] // ["lat": "35.6895", "name": "Edo", "lon": "139.6917"]
I really like this solution. It's quite a lot of code, but you only have to write it once and I think it looks very nice in use.
You could construct recursive methods (read/write) which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves. Moreover, allow public access to these methods via a new subscript.
Note that you might have to explicitly import Foundation for access to the components(separatedBy:) method of String (bridged).
extension Dictionary {
subscript(keyPath keyPath: String) -> Any? {
get {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
else { return nil }
return getValue(forKeyPath: keyPath)
}
set {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
let newValue = newValue else { return }
self.setValue(newValue, forKeyPath: keyPath)
}
}
static private func keyPathKeys(forKeyPath: String) -> [Key]? {
let keys = forKeyPath.components(separatedBy: ".")
.reversed().flatMap({ $0 as? Key })
return keys.isEmpty ? nil : keys
}
// recursively (attempt to) access queried subdictionaries
// (keyPath will never be empty here; the explicit unwrapping is safe)
private func getValue(forKeyPath keyPath: [Key]) -> Any? {
guard let value = self[keyPath.last!] else { return nil }
return keyPath.count == 1 ? value : (value as? [Key: Any])
.flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
}
// recursively (attempt to) access the queried subdictionaries to
// finally replace the "inner value", given that the key path is valid
private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
guard self[keyPath.last!] != nil else { return }
if keyPath.count == 1 {
(value as? Value).map { self[keyPath.last!] = $0 }
}
else if var subDict = self[keyPath.last!] as? [Key: Value] {
subDict.setValue(value, forKeyPath: Array(keyPath.dropLast()))
(subDict as? Value).map { self[keyPath.last!] = $0 }
}
}
}
Example setup
// your example dictionary
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Example usage:
// read value for a given key path
let isNil: Any = "nil"
print(dict[keyPath: "countries.japan.capital.name"] ?? isNil) // tokyo
print(dict[keyPath: "airports"] ?? isNil) // ["germany": ["FRA", "MUC", "HAM", "TXL"]]
print(dict[keyPath: "this.is.not.a.valid.key.path"] ?? isNil) // nil
// write value for a given key path
dict[keyPath: "countries.japan.language"] = "nihongo"
print(dict[keyPath: "countries.japan.language"] ?? isNil) // nihongo
dict[keyPath: "airports.germany"] =
(dict[keyPath: "airports.germany"] as? [Any] ?? []) + ["FOO"]
dict[keyPath: "this.is.not.a.valid.key.path"] = "notAdded"
print(dict)
/* [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917",
"lat": "35.6895"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL", "FOO"]
]
] */
Note that if a supplied key path does not exist for an assignment (using setter), this will not result in the construction of the equivalent nested dictionary, but simply result in no mutation of the dictionary.
Interesting question. The problem seems to be that Swift's optional chaining mechanism, which is normally capable of mutating nested dictionaries, trips over the necessary type casts from Any to [String:Any]. So while accessing a nested element becomes only unreadable (because of the typecasts):
// E.g. Accessing countries.japan.capital
((dict["countries"] as? [String:Any])?["japan"] as? [String:Any])?["capital"]
… mutating a nested element doesn't even work:
// Want to mutate countries.japan.capital.name.
// The typecasts destroy the mutating optional chaining.
((((dict["countries"] as? [String:Any])?["japan"] as? [String:Any])?["capital"] as? [String:Any])?["name"] as? String) = "Edo"
// Error: Cannot assign to immutable expression
Possible Solution
The idea is to get rid of the untyped dictionary and convert it into a strongly typed structure where each element has the same type. I admit that this is a heavy-handed solution, but it works quite well in the end.
An enum with associated values would work well for our custom type that replaces the untyped dictionary:
enum KeyValueStore {
case dict([String: KeyValueStore])
case array([KeyValueStore])
case string(String)
// Add more cases for Double, Int, etc.
}
The enum has one case for each expected element type. The three cases cover your example, but it could easily be expanded to cover more types.
Next, we define two subscripts, one for keyed access to a dictionary (with strings) and one for indexed access to an array (with integers). The subscripts check if self is a .dict or .array respectively and if so return the value at the given key/index. They return nil if the type doesn't match, e.g. if you tried to access a key of a .string value. The subscripts also have setters. This is key to make chained mutation work:
extension KeyValueStore {
subscript(_ key: String) -> KeyValueStore? {
// If self is a .dict, return the value at key, otherwise return nil.
get {
switch self {
case .dict(let d):
return d[key]
default:
return nil
}
}
// If self is a .dict, mutate the value at key, otherwise ignore.
set {
switch self {
case .dict(var d):
d[key] = newValue
self = .dict(d)
default:
break
}
}
}
subscript(_ index: Int) -> KeyValueStore? {
// If self is an array, return the element at index, otherwise return nil.
get {
switch self {
case .array(let a):
return a[index]
default:
return nil
}
}
// If self is an array, mutate the element at index, otherwise return nil.
set {
switch self {
case .array(var a):
if let v = newValue {
a[index] = v
} else {
a.remove(at: index)
}
self = .array(a)
default:
break
}
}
}
}
Lastly, we add some convenience initializers for initializing our type with dictionary, array or string literals. These are not strictly necessary, but make working with the type easier:
extension KeyValueStore: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, KeyValueStore)...) {
var dict: [String: KeyValueStore] = [:]
for (key, value) in elements {
dict[key] = value
}
self = .dict(dict)
}
}
extension KeyValueStore: ExpressibleByArrayLiteral {
init(arrayLiteral elements: KeyValueStore...) {
self = .array(elements)
}
}
extension KeyValueStore: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self = .string(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self = .string(value)
}
init(unicodeScalarLiteral value: String) {
self = .string(value)
}
}
And here's the example:
var keyValueStore: KeyValueStore = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
// Now optional chaining works:
keyValueStore["countries"]?["japan"]?["capital"]?["name"] // .some(.string("tokyo"))
keyValueStore["countries"]?["japan"]?["capital"]?["name"] = "Edo"
keyValueStore["countries"]?["japan"]?["capital"]?["name"] // .some(.string("Edo"))
keyValueStore["airports"]?["germany"]?[1] // .some(.string("MUC"))
keyValueStore["airports"]?["germany"]?[1] = "BER"
keyValueStore["airports"]?["germany"]?[1] // .some(.string("BER"))
// Remove value from array by assigning nil. I'm not sure if this makes sense.
keyValueStore["airports"]?["germany"]?[1] = nil
keyValueStore["airports"]?["germany"] // .some(array([.string("FRA"), .string("HAM"), .string("TXL")]))
Pass your dictionary to this function, It will return you a flat dictionary, without any nested dict incorporated .
//SWIFT 3.0
func concatDict( dict: [String: Any])-> [String: Any]{
var dict = dict
for (parentKey, parentValue) in dict{
if let insideDict = parentValue as? [String: Any]{
let keys = insideDict.keys.map{
return parentKey + $0
}
for (key, value) in zip(keys, insideDict.values) {
dict[key] = value
}
dict[parentKey] = nil
dict = concatDict(dict: dict)
}
}
return dict
}
Related
I don't know if there is a quicker way or a function that's already implemented for my issue. I have a nested dynamic JSON with unique keys.
{
"Key1": true,
"Key2": false,
"Key3": "just a string",
"Key4": {
"Key5": "just a string",
"Key6": 51,
"Key7": {
"Key8": "this value has to be changed"
}
}
}
Is there any Swift function or a package that could do this easily like that:
let updatedJSON = originalJSON.update(key: "Key8", with: "---")
So the updatedJSON will be the same only "Key8" will be updated. Take into consideration that I don't know the hierarchy. The JSON key positions may wary. Sometimes Key8 can be at the root of the object.
Not knowing the structure of your JSON complicates this problem a bit, but it can still be solved. I've come up with an idea to recursively search through a Dictionary
extension Dictionary
{
mutating func updateValueRecursively(forKey key : Key, newValue: Value) -> Bool
{
if keys.contains(key)
{
self[key] = newValue
return true
}
for (_, value) in enumerated()
{
if var dict = value.value as? Dictionary<Key, Value> {
if dict.updateValueRecursively(forKey: value.key, newValue: newValue), let newDict = dict as? Value {
self[value.key] = newDict
return true
}
}
}
return false
}
}
Of course you will first have to turn your JSON string to a Dictionary
var dict : Dictionary<String : Any>
if let data = jsonString.data(using: .utf8) {
do {
dict= try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
If I have a dictionary "dic" like this:
{
"a": {
"b": Any
}
"c": {
"d": Any
}
}
If I want to change the value of key "b", I know I can do it like this:
dic["a"]["b"] = somethingNew
But If the key path is varible, how can I change the value according to the key path? is there some api like this:
dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)
Or the idea to achieve this purpose , thanks ~
I recently answered a Q&A that wanted to remove (rather than update) a nested value in a dictionary of nested dictionaries.
Remove nested key from dictionary
We can use a similar approach here: using a recursive method which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves. The only restriction here is that the Key types much be the same for all dictionaries within the nested dictionary.
Implementation
Recursive "core" function updateValue(_:, inDict:, forKeyPath:) and public updateValue(_:, forKeyPath:) method form key paths on for [Key] (e.g. ["a", "b"] applied to your example):
/* general "key path" extension */
public extension Dictionary {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: [Key])
-> Value? {
let (valInd, newDict) = updateValue(value, inDict: self,
forKeyPath: Array(keyPath.reversed()))
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
fileprivate func updateValue(_ value: Value, inDict dict: [Key: Any],
forKeyPath keyPath: [Key]) -> (Value?, [Key: Any]) {
guard let key = keyPath.last else { return (value, dict) }
var dict = dict
if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
let (val, newSubDict) = updateValue(value, inDict: subDict,
forKeyPath: Array(keyPath.dropLast()))
dict[key] = newSubDict
return (val, dict)
}
let val = dict.updateValue(value, forKey: key) as? Value
return (val, dict)
}
}
Less general public updateValue(_:, forKeyPath:) method (using the core function above) for keys that conforms to ExpressibleByStringLiteral; key paths on form my.key.path (e.g. "a.b" applied to your example):
/* String literal specific "key path" extension */
public extension Dictionary where Key: ExpressibleByStringLiteral {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: String)
-> Value? {
let keyPathArr = keyPath.components(separatedBy: ".")
.reversed().flatMap { $0 as? Key }
if keyPathArr.isEmpty { return self.updateValue(value, forKey: "") }
let (valInd, newDict) = updateValue(value,
inDict: self, forKeyPath:keyPathArr)
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
}
Example usage
We'll apply the methods above to the example from the linked thread.
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Using the ExpressibleByStringLiteral key path method to update the value of an existing key-value pair:
if let oldValue = dict.updateValue("nihongo",
forKeyPath: "countries.japan.language") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Removed old value: japanese
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
The same method used to add a new key-value pair at a given key path dictionary:
if let oldValue = dict.updateValue("asia",
forKeyPath: "countries.japan.continent") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Added new key-value pair
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo",
"continent": "asia"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
We would get the same result as in the examples above if we used the general [Key] as key path method instead of the ExpressibleByStringLiteral one used above. Using the former, the calls would be changed into:
... = dict.updateValue("nihongo",
forKeyPath: ["countries", "japan", "language"]
... = dict.updateValue("asia",
forKeyPath: ["countries", "japan", "continent"]
Finally note that a call to updateValue using the [Key] as key path method will return nil also in case of an empty array being passed as argument ([]). This could possibly be changed to a throwing case, as a nil return above should tell us that a new key-value pair was added (as inspired by the updateValue(_:, forKey:) method in stdlib).
Performance?
The methods above will make use of some (sub-)dictionary copying along the way, but unless you're working with huge dictionaries, this shouldn't be an issue. Anyhow, no need to worry about performance until a profiler tells you have a bottleneck.
This is on XCode 7.3.
If I build the code with optimization flag -O, it will be crashed. It is working properly without optimization (flag -Onone). However I want to have my code built with optimization.
Is it Swift compiler bug?
This is the code.
import Foundation
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
func multipartFormParams(preFix preFix: String = "") -> [String: AnyObject] {
var params: [String: AnyObject] = [:]
for (key, value) in self {
let subKey = preFix != "" ? "\(preFix)[\(key)]" : String(key)
if let value = value as? [String: AnyObject] {
// params += value.multipartFormParams(preFix: subKey) // this is my original code, however remove the operation extension += to focus on the bug.
value.multipartFormParams(preFix: subKey) // It crash here.
} else {
params[subKey] = value
}
}
return params
}
}
let parameters: [String: AnyObject] = [
"user": [
"first_name": "first",
"last_name": "last"
]
]
parameters.multipartFormParams()
After a while, I found that this is related to this bug: https://bugs.swift.org/browse/SR-1114, change for (key, value) in self { to forEach { key, value in solved the problem.
Full working version:
import Foundation
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
func multipartFormParams(preFix preFix: String = "") -> [String: AnyObject] {
var params: [String: AnyObject] = [:]
forEach { key, value in
let subKey = preFix != "" ? "\(preFix)[\(key)]" : String(key)
if let value = value as? [String: AnyObject] {
// params += value.multipartFormParams(preFix: subKey) // this is my original code, however remove the operation extension += to focus on the bug.
value.multipartFormParams(preFix: subKey) // It crash here.
} else {
params[subKey] = value
}
}
return params
}
}
let parameters: [String: AnyObject] = [
"user": [
"first_name": "first",
"last_name": "last"
]
]
parameters.multipartFormParams()
I have a Dictionary that contains values in such a way that some values are empty string.
let fbPostDic: [String: AnyObject] = [
"title":"",
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with":"",
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
I want to filter this dictionary such a way that new dictionary should just contains elements without empty strings.
let fbPostDic: [String: AnyObject] = [
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
There are present ways like
let keysToRemove = dict.keys.array.filter { dict[$0]! == nil }
But its support iOS9.0 or above. I want to give support of iOS8.0 as well.
Any suggestions?
Because the above dict is a constant, add an extra init method in Dictionary extension can simplify the process:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
init(_ elements: [Element]){
self.init()
for (key, value) in elements {
self[key] = value
}
}
}
print(Dictionary(dict.filter { $1 as? String != "" }))
However, if above dict can be declared as a variable. Probably try the one below without above extra Dictionary extension:
var dict: [String : AnyObject] = [...]
dict.forEach { if $1 as? String == "" { dict.removeValueForKey($0) } }
print(dict)
This solution will also work; But Allen's solution is more precise.
public class func filterDictionaryFromEmptyString(var dictionary:[String: AnyObject]) -> [String: AnyObject]
{
print(dictionary.keys.count)
for key:String in dictionary.keys
{
print(key)
print(dictionary[key]!)
if String(dictionary[key]!) == ""
{
dictionary.removeValueForKey(key)
}
}
print(dictionary.keys.count)
return dictionary
}
I have following code for dictionary
var dic : [String: AnyObject] = ["FirstName": "Anvar", "LastName": "Azizov", "Website": NSNull(),"About": NSNull()]
I already remove key which have null value using below code
var keys = dic.keys.array.filter({dic[$0] is NSNull})
for key in keys {
dic.removeValueForKey(key)
}
It works for static dictionary,But I want do it dynamically,I want to done it using function but whenever I pass dictionary as a argument it works as a let means constant so can not remove null key
I make below code for that
func nullKeyRemoval(dic : [String: AnyObject]) -> [String: AnyObject]{
var keysToRemove = dic.keys.array.filter({dic[$0] is NSNull})
for key in keysToRemove {
dic.removeValueForKey(key)
}
return dic
}
please tell me solution for this
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()
Swift 5 adds compactMapValues(_:), which would let you do
let filteredDict = dict.compactMapValues { $0 is NSNull ? nil : $0 }
For Swift 3.0 / 3.1 this could be helpful. Also removes NSNull objects recursive:
extension Dictionary {
func nullKeyRemoval() -> [AnyHashable: Any] {
var dict: [AnyHashable: Any] = self
let keysToRemove = dict.keys.filter { dict[$0] is NSNull }
let keysToCheck = dict.keys.filter({ dict[$0] is Dictionary })
for key in keysToRemove {
dict.removeValue(forKey: key)
}
for key in keysToCheck {
if let valueDict = dict[key] as? [AnyHashable: Any] {
dict.updateValue(valueDict.nullKeyRemoval(), forKey: key)
}
}
return dict
}
}
Swift 3+: Remove null from dictionary
func removeNSNull(from dict: [String: Any]) -> [String: Any] {
var mutableDict = dict
let keysWithEmptString = dict.filter { $0.1 is NSNull }.map { $0.0 }
for key in keysWithEmptString {
mutableDict[key] = ""
}
return mutableDict
}
Use:
let outputDict = removeNSNull(from: ["name": "Foo", "address": NSNull(), "id": "12"])
Output: ["name": "Foo", "address": "", "id": "12"]
Nested NSNull supported
To remove any NSNull appearance in any nested level (including arrays and dictionaries), try this:
extension Dictionary where Key == String {
func removeNullsFromDictionary() -> Self {
var destination = Self()
for key in self.keys {
guard !(self[key] is NSNull) else { destination[key] = nil; continue }
guard !(self[key] is Self) else { destination[key] = (self[key] as! Self).removeNullsFromDictionary() as? Value; continue }
guard self[key] is [Value] else { destination[key] = self[key]; continue }
let orgArray = self[key] as! [Value]
var destArray: [Value] = []
for item in orgArray {
guard let this = item as? Self else { destArray.append(item); continue }
destArray.append(this.removeNullsFromDictionary() as! Value)
}
destination[key] = destArray as? Value
}
return destination
}
}
Swift 4
A little more efficient than the other solutions. Uses only O(n) complexity.
extension Dictionary where Key == String, Value == Any? {
var trimmingNullValues: [String: Any] {
var copy = self
forEach { (key, value) in
if value == nil {
copy.removeValue(forKey: key)
}
}
return copy as [Key: ImplicitlyUnwrappedOptional<Value>]
}
}
Usage: ["ok": nil, "now": "k", "foo": nil].trimmingNullValues // =
["now": "k"]
If your dictionary is mutable you could do this in place and prevent the inefficient copying:
extension Dictionary where Key == String, Value == Any? {
mutating func trimNullValues() {
forEach { (key, value) in
if value == nil {
removeValue(forKey: key)
}
}
}
}
Usage:
var dict: [String: Any?] = ["ok": nil, "now": "k", "foo": nil]
dict.trimNullValues() // dict now: = ["now": "k"]
The cleanest way to do it, just 1 line
extension Dictionary {
func filterNil() -> Dictionary {
return self.filter { !($0.value is NSNull) }
}
}
Rather than using a global function (or a method), why not making it a method of Dictionary, using an extension?
extension NSDictionary
{
func RemoveNullValueFromDic()-> NSDictionary
{
let mutableDictionary:NSMutableDictionary = NSMutableDictionary(dictionary: self)
for key in mutableDictionary.allKeys
{
if("\(mutableDictionary.objectForKey("\(key)")!)" == "<null>")
{
mutableDictionary.setValue("", forKey: key as! String)
}
else if(mutableDictionary.objectForKey("\(key)")!.isKindOfClass(NSNull))
{
mutableDictionary.setValue("", forKey: key as! String)
}
else if(mutableDictionary.objectForKey("\(key)")!.isKindOfClass(NSDictionary))
{
mutableDictionary.setValue(mutableDictionary.objectForKey("\(key)")!.RemoveNullValueFromDic(), forKey: key as! String)
}
}
return mutableDictionary
}
}
Swift 4 example using reduce
let dictionary = [
"Value": "Value",
"Nil": nil
]
dictionary.reduce([String: String]()) { (dict, item) in
guard let value = item.value else {
return dict
}
var dict = dict
dict[item.key] = value
return dict
}