Determining if Swift dictionary contains key and obtaining any of its values
Asked Answered
T

8

348

I am currently using the following (clumsy) pieces of code for determining if a (non-empty) Swift dictionary contains a given key and for obtaining one (any) value from the same dictionary.

How can one put this more elegantly in Swift?

// excerpt from method that determines if dict contains key
if let _ = dict[key] {
    return true
}
else {
    return false
}

// excerpt from method that obtains first value from dict
for (_, value) in dict {
    return value
}
Toner answered 24/1, 2015 at 19:30 Comment(4)
You can use indexForKey if you feel it is clearer and more explicit; https://mcmap.net/q/94194/-check-if-key-exists-in-dictionary-of-type-type-typeIey
Very often what you want is basically: cityName:String = dict["city"] ?? "" The ?? "" here means basically "if there's no such key, return a blank".Iey
Dictionary.keys.contains() is O(1) for practical purposes (O(n) is worst case.). And more readable than the alternatives here, and handles optional values correctly. See here.Cropper
It looks like if the SwiftData 'isAutosaveEnabled` is disabled, the deleteRule does not work as it should. I disabled it and manually saved the context every time I added or edited any of the model objects and somehow the deleteRule does not apply correctly. Also the views does not update correctly. If I enable it, the view updates correctly (but I still have an issue) and the deleteRule apply as expected.Infra
A
582

You don't need any special code to do this, because it is what a dictionary already does. When you fetch dict[key] you know whether the dictionary contains the key, because the Optional that you get back is not nil (and it contains the value).

So, if you just want to answer the question whether the dictionary contains the key, ask:

let keyExists = dict[key] != nil

If you want the value and you know the dictionary contains the key, say:

let val = dict[key]!

But if, as usually happens, you don't know it contains the key - you want to fetch it and use it, but only if it exists - then use something like if let:

if let val = dict[key] {
    // now val is not nil and the Optional has been unwrapped, so use it
}
Archaism answered 24/1, 2015 at 19:40 Comment(17)
I don't follow. I just did obtain a value (twice). What more do you want?Archaism
There is no such thing as "the first value"; a dictionary is unordered. If you just want the values, without the keys, say dict.values.Archaism
Here's the part of my tutorial where I explain about dictionaries: apeth.com/swiftBook/ch04.html#_dictionaryArchaism
My mistake (typo): I meant any value. dict.values is it then. Thx.Toner
Here are the parts where I explain about Optionals, which I often illustrate with dictionaries because they return an Optional: apeth.com/swiftBook/ch03.html#_optional apeth.com/swiftBook/ch05.html#_flow_controlArchaism
Be careful because dict.values is opaque. You can cycle thru it but that's all. (Okay, it's not all, but humor me.) If you want it reified to an Array, take dict.values.array.Archaism
You can iterate through the dictionary with a tuple as: for (key,value) in dict { }. Also FYI if you want to test for a value and default it you can use the ?? nil coalescing operator: e.g. var val : String = dict["foo"] ?? "no value".Champac
@Archaism if let val = dict[key], dict[key] is not an Optional value so if let won't work on itSinglecross
@Archaism Array(dict.keys).contains(key)Singlecross
@Singlecross Try this: let d = ["hey":"ho"]; let s = d["hey"]; print(s) It's an Optional, just like it's always been.Archaism
ok i guess i misunderstood the errors messages then, thank youSinglecross
sometimes, if the key exists you want to do nothing but if it does not exist you want to add the value with respect to that key. As of swift2.0 it will result in us never using the val (as per ur if) variable. Resulting in a warning that it was never used. Cant we have a inverse of this 'if'.. 'if' this value does not exist.. like explicitly go inside 'if' statement if the val is nillHousehold
@RanaTallal The inverse of == is !=.Archaism
You can use _ rather than val for the if conditionSixtieth
What happens if there is a dictionary of [String: Any], for what i read if the object is nil when assigned to Any it would return false always forums.swift.org/t/any-should-never-be-nil/11636, is there a way to validate key exist, even if we don't know the type, or do we always need to perform the cast to avoid that Any == nil false condition ?Apiece
@Apiece That is a very profound question but therefore please ask it separately.Archaism
Just a note to use index(forKey: k) != nil if you need to deal with @Bergy's concernInterrogatory
N
96

Why not simply check for dict.keys.contains(key)? Checking for dict[key] != nil will not work in cases where the value is nil. As with a dictionary [String: String?] for example.

Needs answered 20/6, 2017 at 14:52 Comment(13)
Or instead of just writing if let val = dict[key] you can use if let val = dict[key] as? String in this case.Vocabulary
.keys.contains does not help to distinguish whether key exists, or if value is nil. Maybe in earlier swift versions? Doesn't work for me in swift 4.Skiff
This is potentially very wasteful, because (1) you call dict.keys that creates an array with all the keys, then (2) checks every key in order until you find one (or none!). I realize you are trying to address the case of a [String: String?], but would be very careful with that solution...Leija
As @Leija mentioned, this would eliminate the fast lookup benefit that using a dictionary provides so I'd advise against this one, but still definitely a valid option.Hello
See also comment from @jedwidz below: #28129901 Checking == nil does work, even if the dictionary has optional values, so there is really no reason to do the above.Leija
You should never use this method, in has complexity O(n) instead of theoretical (depends on hash function implementation) O(1) of direct dictionary access.Quip
@Leija I don't see anywhere in the docs for keys that it says it creates an array or that contains is O(n). It says it's a view, but doesn't seem to indicate its performance characteristics. Hopefully, it doesn't make a copy and is O(1).Lalo
@EdwardBrey Good point, I took the worst case implementation (completely misinformed by the ObjC version that returns NSArray), but who knows, it might be optimized for that use. Or not, of course :) It would be interesting to look at the implementation, since it's open source...Leija
@EdwardBrey Ah, we can get the answer straight from the documentation, in fact, where it says that contains is O(n), yikes: developer.apple.com/documentation/swift/dictionary/keys/…Leija
@Leija so contains having O(n) lookup is correct (its just the collection conformance implementation), but allocating a new array just for accessing "keys" doesn't appear correct. Can you fix that?Amylolysis
Dictionary.keys is a custom sequence, and for practical considerationsDictionary.keys.contains is O(1). The documentation is giving the worst case value of O(n). See here.Cropper
.contains(where:) is a method of Sequence protocol, which provides linear access to elements. Average access time is O(n), there is no way around it. For proper performance you need to use some method of Dictionary itself. But Swift being Swift, I see no obvious way to distinguish between nil because of no key and nil because key is present, but value is optional and nilQuip
It might be an Apple Doc oversight, but per testing it has amortized O(1) time complexity. Source: forums.swift.org/t/… and github.com/apple/swift-evolution/blob/main/proposals/…Dominations
I
85

The accepted answer let keyExists = dict[key] != nil will not work if the Dictionary contains the key but has a value of nil.

If you want to be sure the Dictionary does not contain the key at all use this (tested in Swift 4).

if dict.keys.contains(key) {
  // contains key
} else { 
  // does not contain key
}
Irby answered 12/1, 2018 at 14:24 Comment(9)
This is the same as Han's answer.Ferne
Checking == nil does work, even if the dictionary has optional values. This is because the lookup result is wrapped as an Optional. On the other hand, to check if the looked up value is really nil rather than absent, you can use == .some(nil).Sorci
I would add the efficiency of using this method compared to the O(1) lookup of the other methods. I believe it’s O(n) lookupTetherball
@AlbertoVega-MSFT It's O(1). See also: github.com/apple/swift-evolution/blob/master/proposals/… What made you think it was O(n)?Liquate
@Liquate and what is there about contains and O(1) complexity? Btw, in docs, about contains: O(n), where n is the length of the sequence.Cacciatore
@Cacciatore Read the whole thing. It talks about how keys used to be implemented as a LazyMapCollection (basically just dict.lazy.map { key, value in key } that had no way of efficiently fowardining "contains" checks to the referenced dictionary. Instead, they implemented a new type Dictionary.Keys, which presents a Collection-conforming view onto the underlying dictionary's keys, with O(1) contains checks.Liquate
The documentation for the Dictionary.Keys.contains(...) function here says it has O(n) complexity, where n is the length of the sequence.Mayoralty
@Sorci as you mention == nil works but not for [String: Any] where any can't be nil forums.swift.org/t/any-should-never-be-nil/11636Apiece
Dictionary.keys is a custom sequence, and for practical considerationsDictionary.keys.contains is O(1). The documentation is giving the worst case value of O(n). See here.Cropper
O
5

Looks like you got what you need from @matt, but if you want a quick way to get a value for a key, or just the first value if that key doesn’t exist:

extension Dictionary {
    func keyedOrFirstValue(key: Key) -> Value? {
        // if key not found, replace the nil with 
        // the first element of the values collection
        return self[key] ?? first(self.values)
        // note, this is still an optional (because the
        // dictionary could be empty)
    }
}

let d = ["one":"red", "two":"blue"]

d.keyedOrFirstValue("one")  // {Some "red"}
d.keyedOrFirstValue("two")  // {Some "blue"}
d.keyedOrFirstValue("three")  // {Some "red”}

Note, no guarantees what you'll actually get as the first value, it just happens in this case to return “red”.

Orogeny answered 24/1, 2015 at 20:2 Comment(1)
good use of the ?? operator took my convenience solution from me, but as an extension that default value could present data insecurities or unexpected behaviors. That sounds like something a concrete subclass of Dictionary ought to employ. How often do you need the first value in case of a nil return excluding situation specific functionality?Chiastolite
K
1

My solution for a cache implementation that stores optional NSAttributedString:

public static var attributedMessageTextCache    = [String: NSAttributedString?]()

    if attributedMessageTextCache.index(forKey: "key") != nil
    {
        if let attributedMessageText = TextChatCache.attributedMessageTextCache["key"]
        {
            return attributedMessageText
        }
        return nil
    }

    TextChatCache.attributedMessageTextCache["key"] = .some(.none)
    return nil
Kelso answered 27/7, 2017 at 12:42 Comment(0)
C
0

If you want to return the value of the key you can use this extension

extension Dictionary {
    func containsKey(_ key: Key) -> Value? {
        if let index = index(forKey: key){
            return self.values[index]
        }
        return nil
    }
}
Cystocarp answered 28/1, 2022 at 6:49 Comment(1)
How is this any different from doing dictionary["key"]?Coccidiosis
C
-1

If you are dealing with dictionary that may contain nil value for a key then you can check existence of key by:

dictionay.index(forKey: item.key) != nil

For getting first value in dictionary:

dictionay.first?.value // optional since dictionary might be empty
Chanteuse answered 30/5, 2022 at 14:1 Comment(0)
T
-2
if dictionayTemp["quantity"] != nil
    {

  //write your code
    }
Torhert answered 6/9, 2017 at 21:15 Comment(1)
This is often what you want. It means it DOES exist >>>>AND<<<<< the value is NOT nilIey

© 2022 - 2024 — McMap. All rights reserved.