Anvar Azizov
Anvar Azizov

Reputation: 585

Swift: How to remove a null value from Dictionary?

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

Answers (15)

Andres Canella
Andres Canella

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

Jagie
Jagie

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

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

Teja Swaroop
Teja Swaroop

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

Mari&#225;n Čern&#253;
Mari&#225;n Čern&#253;

Reputation: 15748

Swift 5

Use compactMapValues:

dictionary.compactMapValues { $0 }

compactMapValues has been introduced in Swift 5. For more info see Swift proposal SE-0218.

Example with dictionary

let json = [
    "FirstName": "Anvar",
    "LastName": "Azizov",
    "Website": nil,
    "About": nil,
]

let result = json.compactMapValues { $0 }
print(result) // ["FirstName": "Anvar", "LastName": "Azizov"]

Example including JSON parsing

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

Swift 4

I would do it by combining filter with mapValues:

dictionary.filter { $0.value != nil }.mapValues { $0! }

Examples

Use the above examples just replace let result with

let result = json.filter { $0.value != nil }.mapValues { $0! }

Upvotes: 84

Saranjith
Saranjith

Reputation: 11567

Swift 5

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

Sandeep Kalia
Sandeep Kalia

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

Anita Nagori
Anita Nagori

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

Daniel T.
Daniel T.

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

Roman Podymov
Roman Podymov

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

Antonio
Antonio

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

Rashesh Bosamiya
Rashesh Bosamiya

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

Eralp Karaduman
Eralp Karaduman

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

buildsucceeded
buildsucceeded

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

user887210
user887210

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

Related Questions