Swift: как удалить значение null из словаря?

Я новичок в Swift, и у меня проблема с фильтрацией нулевых значений из файла JSON и установкой его в словарь. Я получаю ответ JSON с сервера с нулевыми значениями, и он аварийно завершает работу моего приложения.

вот ответ JSON:

"FirstName": "Anvar",
"LastName": "Azizov",
"Website": null,
"About": null,

Я буду очень признателен за помощь, чтобы справиться с ней.

UPD1: в этот момент я решил сделать это следующим образом:

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

9 ответов


вы можете создать массив, содержащий ключи, соответствующие значения которых равны нулю:

let keysToRemove = dict.keys.array.filter { dict[]! == nil }

и следующий цикл через все элементы этого массива и удалить ключи из словаря:

for key in keysToRemove {
    dict.removeValueForKey(key)
}

обновление 2017.01.17

оператор разворачивания силы немного уродлив, хотя и безопасен, как объясняется в комментариях. Вероятно, есть несколько других способов достичь того же результата, более красивый способ того же метода есть:

let keysToRemove = dict.keys.filter {
  guard let value = dict[] else { return false }
  return value == nil
}

Я закончил с этим в Swift 2:

extension Dictionary where Value: AnyObject {

    var nullsRemoved: [Key: Value] {
        let tup = filter { !(.1 is NSNull) }
        return tup.reduce([Key: Value]()) { (var r, e) in r[e.0] = e.1; return r }
    }
}

тот же ответ, но для 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 { !(.1 is NSNull) }
        return tup.reduce([Key: Value]()) { .0.updatedValue(.1.value, forKey: .1.key) }
    }
}

все становится намного проще в Swift 4. Просто используйте словарь filter напрямую.

jsonResult.filter { !(.1 is NSNull) }

или если вы не хотите удалять соответствующие ключи, вы можете сделать это:

jsonResult.mapValues {  is NSNull ? nil :  }

который заменит NSNull значения nil вместо удаления ключей.


предполагая, что вы просто хотите отфильтровать любой NSNull значения из словаря, это, вероятно, один из лучших способов сделать это. Это будущее-защищено от Swift 3, Насколько я знаю на данный момент:

(С спасибо AirspeedVelocity для расширения, переведенного на 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 (.1 is NSNull) ? .None : 
  })
// clean -> ["LastName": "Azizov", "FirstName": "Anvar"]

предлагая этот подход, сглаживает необязательные значения, а также совместим с Swift 3

String ключ, дополнительно AnyObject? словарь значений с нулевыми значениями:

let nullableValueDict: [String : AnyObject?] = [
    "first": 1,
    "second": "2",
    "third": nil
]

// ["first": {Some 1}, "second": {Some "2"}, "third": nil]

значения nil удалены и преобразованы в необязательный словарь значений

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 необходимо из-за удаления параметров var в swift 3, поэтому для swift 2,1 это может быть;

nullableValueDict.reduce([String : AnyObject]()) { (var dict, e) in
    guard let value = e.1 else { return dict }
    dict[e.0] = value
    return dict
}

Swift 4, было бы;

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
}

поскольку Swift 4 предоставляет метод reduce(into:_:) класс Dictionary вы можете удалить нулевые значения из Dictionary со следующей функцией:

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)
        }
    }
}

вы можете проверить это так:

let testingDict = removeNilValues(dict: ["1":nil, "2":"b", "3":nil, "4":nil, "5":"e"])
print("test result is \(testingDict)")

Swift 4

Я бы сделал это, объединив filter С mapValues:

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

полный пример:

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

let result = json.filter { .value != nil }.mapValues { ! }
print(result) // ["FirstName": "Anvar", "LastName": "Azizov"]

Мне просто нужно было решить это для общего случая, когда NSNulls могут быть вложены в словарь на любом уровне или даже быть частью массива:

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 {!( 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)
            }
        }
    }
}

}

Я надеюсь, что это поможет другим и приветствую улучшений!

(Примечание: это Swift 4)


следующее решение, когда JSON есть sub-dictionaries. Это пройдет-через все dictionaries, sub-dictionaries of JSON и удалить NULL(NSNull) key-value пара от 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({ .value is NSNull }).map({  })
                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({ .value is NSNull }).map({  })
                    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>
    }
 }

вы можете заменить значения null на пустую строку следующей функцией.

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;
}