Reputation: 585
I'm new in Swift and I have a problem with filtering NULL values from JSON file and setting it into Dictionary. I getting JSON response from the server with null values and it crashes my app.
Here is JSON response:
"FirstName": "Anvar",
"LastName": "Azizov",
"Website": null,
"About": null,
I will be very appreciated for help to deal with it.
UPD1: At this moment I decided to do it in a next way:
if let jsonResult = responseObject as? [String: AnyObject] {
var jsonCleanDictionary = [String: AnyObject]()
for (key, value) in enumerate(jsonResult) {
if !(value.1 is NSNull) {
jsonCleanDictionary[value.0] = value.1
}
}
}
Upvotes: 35
Views: 41942
Reputation: 3716
Another quick way to do it in case you're not in Swift 5 yet... This will produce the same effect as compactMapValues
.
Same dictionary but with no optionals.
let cleanDictionary = originalDictionary.reduce(into: [String: Any]()) { $0[$1.key] = $1.value }
Quick playground:
let originalDictionary = [
"one": 1,
"two": nil,
"three": 3]
let cleanDictionary = originalDictionary.reduce(into: [String: Any]()) { $0[$1.key] = $1.value }
for element in cleanDictionary {
print(element)
}
output:
["one": 1, "three": 3]
Upvotes: 1
Reputation: 2220
//
// Collection+SF.swift
// PanoAI
//
// Created by Forever Positive on 2021/1/30.
//
import Foundation
extension Array where Element == Optional<Any>{
var trimed:[Any] {
var newArray = [Any]()
for item in self{
if let item = item as? [Any?] {
newArray.append(contentsOf: item.trimed)
}else if item is NSNull || item == nil {
//skip
}else if let item = item{
newArray.append(item)
}
}
return newArray
}
}
extension Dictionary where Key == String, Value == Optional<Any> {
var trimed:[Key: Any] {
return self.compactMapValues({ v in
if let dic = v as? [String:Any?] {
return dic.trimed
}else if let _ = v as? NSNull {
return nil;
}else if let array = v as? [Any?] {
return array.trimed;
}else{
return v;
}
})
}
}
// MARK: - Usage
//let dic:[String:Any?] = ["a":"a","b":nil,"c":NSNull(),"d":["1":"1","2":nil,"3":NSNull(),"4":["a","b",nil,NSNull()]],"e":["a","b",nil,NSNull()]]
//print(dic.trimed)
Upvotes: 0
Reputation: 41
Swift5
Use compactMapValues to create a dictionary extension for future usage
extension Dictionary where Key == String, Value == Optional<Any> {
func discardNil() -> [Key: Any] {
return self.compactMapValues({ $0 })
}
}
How to use
public func toDictionary() -> [String: Any] {
let emailAddress: String? = "[email protected]"
let phoneNumber: String? = "xxx-xxx-xxxxx"
let newPassword: String = "**********"
return [
"emailAddress": emailAddress,
"phoneNumber": phoneNumber,
"passwordNew": newPassword
].discardNil()
}
Upvotes: 4
Reputation: 1159
Try to unwrap it with the expected type of values and check for the nill or empty if yes then remove that value from the dictionary.
This will work if the values in the dictionary is has empty values
Upvotes: 0
Reputation: 15748
Use compactMapValues
:
dictionary.compactMapValues { $0 }
compactMapValues
has been introduced in Swift 5. For more info see Swift proposal SE-0218.
let json = [
"FirstName": "Anvar",
"LastName": "Azizov",
"Website": nil,
"About": nil,
]
let result = json.compactMapValues { $0 }
print(result) // ["FirstName": "Anvar", "LastName": "Azizov"]
let jsonText = """
{
"FirstName": "Anvar",
"LastName": "Azizov",
"Website": null,
"About": null
}
"""
let data = jsonText.data(using: .utf8)!
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let json = json as? [String: Any?] {
let result = json.compactMapValues { $0 }
print(result) // ["FirstName": "Anvar", "LastName": "Azizov"]
}
I would do it by combining filter
with mapValues
:
dictionary.filter { $0.value != nil }.mapValues { $0! }
Use the above examples just replace let result
with
let result = json.filter { $0.value != nil }.mapValues { $0! }
Upvotes: 84
Reputation: 11567
compactMapValues(_:)
Returns a new dictionary containing only the key-value pairs that have non-nil values as the result of transformation by the given closure.
let people = [
"Paul": 38,
"Sophie": 8,
"Charlotte": 5,
"William": nil
]
let knownAges = people.compactMapValues { $0 }
Upvotes: 3
Reputation: 47
Trim Null form NSDictionary to get ride of crash Pass Dictionary to this function and get result as NSMutableDictionary
func trimNullFromDictionaryResponse(dic:NSDictionary) -> NSMutableDictionary {
let allKeys = dic.allKeys
let dicTrimNull = NSMutableDictionary()
for i in 0...allKeys.count - 1 {
let keyValue = dic[allKeys[i]]
if keyValue is NSNull {
dicTrimNull.setValue("", forKey: allKeys[i] as! String)
}
else {
dicTrimNull.setValue(keyValue, forKey: allKeys[i] as! String)
}
}
return dicTrimNull
}
Upvotes: 0
Reputation: 747
You can replace the null values to empty string by following function.
func removeNullFromDict (dict : NSMutableDictionary) -> NSMutableDictionary
{
let dic = dict;
for (key, value) in dict {
let val : NSObject = value as! NSObject;
if(val.isEqual(NSNull()))
{
dic.setValue("", forKey: (key as? String)!)
}
else
{
dic.setValue(value, forKey: key as! String)
}
}
return dic;
}
Upvotes: 2
Reputation: 33967
I ended up with this in Swift 2:
extension Dictionary where Value: AnyObject {
var nullsRemoved: [Key: Value] {
let tup = filter { !($0.1 is NSNull) }
return tup.reduce([Key: Value]()) { (var r, e) in r[e.0] = e.1; return r }
}
}
Same answer but for Swift 3:
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
var nullsRemoved: [Key: Value] {
let tup = filter { !($0.1 is NSNull) }
return tup.reduce([Key: Value]()) { $0.0.updatedValue($0.1.value, forKey: $0.1.key) }
}
}
Things get a lot easier in Swift 4. Just use Dictionary's filter
directly.
jsonResult.filter { !($0.1 is NSNull) }
Or if you don't want to remove the relevant keys, you can do this:
jsonResult.mapValues { $0 is NSNull ? nil : $0 }
Which will replace the NSNull
values with nil
instead of removing the keys.
Upvotes: 8
Reputation: 4521
Since Swift 4 provides method reduce(into:_:)
for class Dictionary
you can remove nil-values from Dictionary
with the following function:
func removeNilValues<K,V>(dict:Dictionary<K,V?>) -> Dictionary<K,V> {
return dict.reduce(into: Dictionary<K,V>()) { (currentResult, currentKV) in
if let val = currentKV.value {
currentResult.updateValue(val, forKey: currentKV.key)
}
}
}
You can test it like that:
let testingDict = removeNilValues(dict: ["1":nil, "2":"b", "3":nil, "4":nil, "5":"e"])
print("test result is \(testingDict)")
Upvotes: 1
Reputation: 72760
You can create an array containing the keys whose corresponding values are nil:
let keysToRemove = dict.keys.array.filter { dict[$0]! == nil }
and next loop through all elements of that array and remove the keys from the dictionary:
for key in keysToRemove {
dict.removeValueForKey(key)
}
Update 2017.01.17
The force unwrapping operator is a bit ugly, although safe, as explained in the comments. There are probably several other ways to achieve the same result, a better-looking way of the same method is:
let keysToRemove = dict.keys.filter {
guard let value = dict[$0] else { return false }
return value == nil
}
Upvotes: 15
Reputation: 629
Following is the solution, when JSON
have sub-dictionaries
.
This will go-through all the dictionaries
, sub-dictionaries
of JSON
and remove NULL(NSNull)
key-value
pair from the JSON
.
extension Dictionary {
func removeNull() -> Dictionary {
let mainDict = NSMutableDictionary.init(dictionary: self)
for _dict in mainDict {
if _dict.value is NSNull {
mainDict.removeObject(forKey: _dict.key)
}
if _dict.value is NSDictionary {
let test1 = (_dict.value as! NSDictionary).filter({ $0.value is NSNull }).map({ $0 })
let mutableDict = NSMutableDictionary.init(dictionary: _dict.value as! NSDictionary)
for test in test1 {
mutableDict.removeObject(forKey: test.key)
}
mainDict.removeObject(forKey: _dict.key)
mainDict.setValue(mutableDict, forKey: _dict.key as? String ?? "")
}
if _dict.value is NSArray {
let mutableArray = NSMutableArray.init(object: _dict.value)
for (index,element) in mutableArray.enumerated() where element is NSDictionary {
let test1 = (element as! NSDictionary).filter({ $0.value is NSNull }).map({ $0 })
let mutableDict = NSMutableDictionary.init(dictionary: element as! NSDictionary)
for test in test1 {
mutableDict.removeObject(forKey: test.key)
}
mutableArray.replaceObject(at: index, with: mutableDict)
}
mainDict.removeObject(forKey: _dict.key)
mainDict.setValue(mutableArray, forKey: _dict.key as? String ?? "")
}
}
return mainDict as! Dictionary<Key, Value>
}
}
Upvotes: 0
Reputation: 304
Suggesting this approach, flattens optional values and also compatible with Swift 3
String
key, optional AnyObject?
value dictionary with nil values:
let nullableValueDict: [String : AnyObject?] = [
"first": 1,
"second": "2",
"third": nil
]
// ["first": {Some 1}, "second": {Some "2"}, "third": nil]
nil values removed and transformed into non optional value dictionary
nullableValueDict.reduce([String : AnyObject]()) { (dict, e) in
guard let value = e.1 else { return dict }
var dict = dict
dict[e.0] = value
return dict
}
// ["first": 1, "second": "2"]
Redeclaration var dict = dict
is needed due to removal of var parameters in swift 3, so for swift 2,1 it could be;
nullableValueDict.reduce([String : AnyObject]()) { (var dict, e) in
guard let value = e.1 else { return dict }
dict[e.0] = value
return dict
}
Swift 4, would be;
let nullableValueDict: [String : Any?] = [
"first": 1,
"second": "2",
"third": nil
]
let dictWithoutNilValues = nullableValueDict.reduce([String : Any]()) { (dict, e) in
guard let value = e.1 else { return dict }
var dict = dict
dict[e.0] = value
return dict
}
Upvotes: 3
Reputation: 4243
I just had to solve this for a general case where NSNulls could be nested in the dictionary at any level, or even be part of an array:
extension Dictionary where Key == String, Value == Any {
func strippingNulls() -> Dictionary<String, Any> {
var temp = self
temp.stripNulls()
return temp
}
mutating func stripNulls() {
for (key, value) in self {
if value is NSNull {
removeValue(forKey: key)
}
if let values = value as? [Any] {
var filtered = values.filter {!($0 is NSNull) }
for (index, element) in filtered.enumerated() {
if var nestedDict = element as? [String: Any] {
nestedDict.stripNulls()
if nestedDict.values.count > 0 {
filtered[index] = nestedDict as Any
} else {
filtered.remove(at: index)
}
}
}
if filtered.count > 0 {
self[key] = filtered
} else {
removeValue(forKey: key)
}
}
if var nestedDict = value as? [String: Any] {
nestedDict.stripNulls()
if nestedDict.values.count > 0 {
self[key] = nestedDict as Any
} else {
self.removeValue(forKey: key)
}
}
}
}
}
I hope this is helpful to others and I welcome improvements!
(Note: this is Swift 4)
Upvotes: 0
Reputation:
Assuming that you just want to filter out any NSNull
values from a dictionary, this is probably one of the better ways of going about it. It's future-proofed against Swift 3, as far as I know for now:
(With thanks to AirspeedVelocity for the extension, translated to Swift 2)
import Foundation
extension Dictionary {
/// Constructs [key:value] from [(key, value)]
init<S: SequenceType
where S.Generator.Element == Element>
(_ seq: S) {
self.init()
self.merge(seq)
}
mutating func merge<S: SequenceType
where S.Generator.Element == Element>
(seq: S) {
var gen = seq.generate()
while let (k, v) = gen.next() {
self[k] = v
}
}
}
let jsonResult:[String: AnyObject] = [
"FirstName": "Anvar",
"LastName" : "Azizov",
"Website" : NSNull(),
"About" : NSNull()]
// using the extension to convert the array returned from flatmap into a dictionary
let clean:[String: AnyObject] = Dictionary(
jsonResult.flatMap(){
// convert NSNull to unset optional
// flatmap filters unset optionals
return ($0.1 is NSNull) ? .None : $0
})
// clean -> ["LastName": "Azizov", "FirstName": "Anvar"]
Upvotes: 3