Swift:如何从字典中删除空值?

34

我是Swift的新手,我需要过滤JSON文件中的NULL值并将其设置为字典。我从服务器获取带有空值的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
      }
    }
}

2
你正在使用哪些代码/库来解释JSON字符串?它被传递到NSDictionary或其他容器中吗?一个代码示例将有助于人们更好地理解您正在尝试做什么以及问题所在。 - user887210
1
不要删除,而是应该正确地检测和处理空值。请参考https://dev59.com/w2Ag5IYBdhLWcg3wDHU4。 - Martin R
@Graff,我正在使用AfNetworking,并将数据传递到一个NSDictionary中。我决定枚举值并删除所有Null值。我已经更新了问题,并提供了一个示例。 - Anvar Azizov
正如@MartinR所说,通常最好正确处理空值而不是盲目地过滤它们。你能展示一下导致程序崩溃的代码吗?这样我们就可以帮助解决问题,而不需要过滤数据。 - user887210
15个回答

80

Swift 5

使用compactMapValues

dictionary.compactMapValues { $0 }

compactMapValues方法是在Swift 5中引入的。有关更多信息,请参见Swift提案SE-0218

带字典的示例

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

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

包含JSON解析的示例

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

我会通过将filtermapValues相结合来实现:

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

示例

使用上述示例,只需将let result替换为

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

无法从有效的 JSON 中删除 null 条目,即:{ "something": [ { "name": "One" }, { "name": "Two" }, null ]} - Luiz Dias
问题和我的解决方案是关于从根JSON字典中删除null。它不会删除JSON内部的所有null。您想从根JSON字典内部的值为数组的null中删除null。您可以使用以下代码:if let something = json["something"] as? [Any?] { print(something.compactMap { $0 is NSNull ? nil : $0 }) }。 请注意,JSONSerialization返回nullNSNull对象在转换为Swift数组[Any?]时不会自动转换为nil,因此您需要明确检查NSNull - Marián Černý

15
你可以创建一个包含对应值为nil的键的数组:
let keysToRemove = dict.keys.array.filter { dict[$0]! == nil }

然后循环遍历该数组的所有元素,并从字典中删除键:

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

更新于2017.01.17

虽然强制解包运算符是安全的,但它看起来有些丑陋,正如评论中所解释的那样。可能有其他几种方法可以达到相同的结果,一种更优美的方式是:

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

2
强制解包在这里是个大问题。这只是因为值类型不是可选的才没有崩溃。另外:永远不要强制解包。 - Hogdotmac
3
@Hogdotmac,这种做法现在肯定不够优雅,但要考虑到dict[$0]是一个可能包含另一个可选项的可选项。强制解包是安全的,因为我们正在循环遍历键数组,所以这些键中的每一个都是存在的。 - Antonio
补充@Antonio的观点,强制解包将让编译器避免不必要的分支。 - MMujtabaRoohani
看起来这似乎对一个有效的 JSON 不起作用,例如 { "something": [ { "name": "One" }, { "name": "Two" }, null ]} - Luiz Dias

8

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

同样的答案,但针对 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) }
    }
}

在Swift 4中,事情变得更容易了。直接使用Dictionary的filter函数即可。

jsonResult.filter { !($0.1 is NSNull) }

或者如果您不想删除相关的键,您可以这样做:

jsonResult.mapValues { $0 is NSNull ? nil : $0 }

这将使用nil替换NSNull值,而不是删除键。


与我想到的相似。filterreduce都会迭代数组,因此仅使用reduce可能比同时使用两者要快一些。我喜欢它作为扩展的想法。 - user887210
他们正在Swift 3中弃用car的上下文。 - Daniel T.
没错,我更新了我的答案以考虑到这一点,并避免在每次迭代时创建一个新的字典副本。 - user887210
@DanielT 我正在将上面的代码迁移到Swift4,请帮我解决一下代码问题。 - Thripthi Haridas
@ThripthiHaridas 我把它添加到了答案中。 - Daniel T.
显示剩余3条评论

4

Swift5

使用 compactMapValues 创建一个字典扩展,以便将来使用

extension Dictionary where Key == String, Value == Optional<Any> {
    func discardNil() -> [Key: Any] {
        return self.compactMapValues({ $0 })
    }
}

如何使用

public func toDictionary() -> [String: Any] {
    let emailAddress: String? = "test@mail.com"
    let phoneNumber: String? = "xxx-xxx-xxxxx"
    let newPassword: String = "**********"

    return [
        "emailAddress": emailAddress,
        "phoneNumber": phoneNumber,
        "passwordNew": newPassword
    ].discardNil()
}

3
假设您只想从字典中过滤出任何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 ($0.1 is NSNull) ? .None : $0
  })
// clean -> ["LastName": "Azizov", "FirstName": "Anvar"]

在Swift 2中,jsonResult.filter { !($0.1 is NSNull) } 返回的类型是[(String: AnyObject)]而不是[String: AnyObject]...有什么想法吗? - Daniel T.
是的,这有点棘手。最简单的方法是使用reduce,虽然还有其他方法可以实现。我会编辑答案。 - user887210

3
建议采用这种方法,将可选值展开,并且与 Swift 3 兼容。
带有空值的可选 AnyObject? 值字典,使用 String 键:
let nullableValueDict: [String : AnyObject?] = [
    "first": 1,
    "second": "2",
    "third": nil
]

// ["first": {Some 1}, "second": {Some "2"}, "third": 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"]

由于Swift 3中移除了var参数,因此需要重新声明var dict = dict。所以对于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中,这会是什么样子? - Adrian
1
嘿@adrian,我已经更新了答案,使其与Swift 4兼容。请参见[Swift Sandbox](http://swift.sandbox.bluemix.net/#/repl/5a213dfe3d51ff687fc49fdd) - Eralp Karaduman

3

Swift 5

compactMapValues(_:)

返回一个新的字典,只包含由给定闭包转化而来的非空值的键-值对。

let people = [
    "Paul": 38,
    "Sophie": 8,
    "Charlotte": 5,
    "William": nil
]

let knownAges = people.compactMapValues { $0 }

看起来对于这样一个有效的 JSON { "something": [ { "name": "One" }, { "name": "Two" }, null ]} 它不起作用。 - Luiz Dias

2
您可以使用以下函数将空值替换为空字符串。
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;
}

1
自从Swift 4为Dictionary类提供了方法reduce(into:_:),您可以使用以下函数从Dictionary中删除nil值:
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)")

1

如果你还没有使用Swift 5,另一种快速的方法是...这将产生与compactMapValues相同的效果。

相同的字典但没有可选项。

let cleanDictionary = originalDictionary.reduce(into: [String: Any]()) { $0[$1.key] = $1.value }

快速实验室:

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

输出:

["one": 1, "three": 3]


如何删除空字符串 "four" = ""? - kiran
这超出了问题的范围,我建议单独寻找它。这个问题集中在nil值上 :) - Andres Canella
好的,我会再看一下。 - kiran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接