Sort Dictionary Alphabetically Cannot assign value of type '[(key: String, value: AnyObject)]' to type 'Dictionary<String, AnyObject>?' - swift

I have this Dictionary, which I am getting from a web service:
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject>
and now I am trying to sort them alphabetically like so:
self.appDelegate.communityArray = json.sorted(by: {$0.0 < $1.0})
But I get this error:
Cannot assign value of type '[(key: String, value: AnyObject)]' to
type 'Dictionary?'
What am I doing wrong?
This is how I am defining communityArray:
var communityArray: Dictionary<String, AnyObject>?

As mentioned in the comments a dictionary – a collection type containing key-value pairs – is unordered by definition, it cannot be sorted.
The sorted function of collection applied to a dictionary treats the dictionary for example
["foo" : 1, "bar" : false]
as an array of tuples
[(key : "foo", value : 1), (key : "bar", value : false)]
and sorts them by key (.0) or value (.1) (I suspect sorting by value Any will raise a compiler error)
The error occurs because you cannot assign an array of tuples to a variable declared as dictionary. That's almost the literal error message.
As a compromise I recommend to map the dictionary to a custom struct (similar to a tuple but better to handle)
struct Community {
let key : String
let value : Any
}
Your variable name already implies that you want a real array
var communityArray = [Community]()
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Any>
communityArray = json.map { Community(key: $0.0, value: $0.1 }.sorted { $0.key < $1.key }

sorted(by:) Returns the elements of the sequence, sorted using the given predicate as the comparison between elements, so you can not use as a dictionary.
For more info about this function you may read sorted(by:) documentation

Related

How to add another dictionary entry to an existing dictionary to form a new dictionary (i.e. not append)

I want to add another dictionary entry to a dictionary in swift e.g.
let a: [String: Any] = ["Test": 1, "good":false]
let b = a + ["hello": "there"]
print(b)
(Sorry if + looks crazy here, as that's how Kotlin achieves this. I'm more familiar with Kotlin than Swift.)
But I get the error Binary operator '+' cannot be applied to two '[String : Any]' operands
I can't use updateValue too
let a: [String: Any] = ["Test": 1, "good":false]
let b = a.updateValue("hello": "there")
print(b)
It will error stating Cannot use mutating member on immutable value: 'a' is a 'let' constant
I can do it by an extension function as per proposed https://stackoverflow.com/a/26728685/3286489
but it looks overkill.
let b = a.merge(dict: ["hello": "there"])
print(b)
extension Dictionary {
func merge(dict: Dictionary<Key,Value>) -> Dictionary<Key,Value> {
var mutableCopy = self
for (key, value) in dict {
// If both dictionaries have a value for same key, the value of the other dictionary is used.
mutableCopy[key] = value
}
return mutableCopy
}
}
Is there a simple operator I can just add another entry to it to for a new dictionary?
Note: I'm not referring to append as per How to append elements into a dictionary in Swift?, as I receive an immutable dictionary let that I need to add another entry to it.
There is a built-in merging(_:uniquingKeysWith:) function on Dictionary that does exactly what you need.
let dictionary = [1:1]
let otherDictionary = [1:2, 2:3]
// This will take the values from `otherDictionary` if the same key exists in both
// You can use `$0` if you want to take the value from `dictionary` instead
let mergedDict = dictionary.merging(otherDictionary, uniquingKeysWith: { $1 })
If you want, you can easily define a + operator for Dictionary that uses the above function.
extension Dictionary {
static func + (lhs: Self, rhs: Self) -> Self {
lhs.merging(rhs, uniquingKeysWith: { $1 })
}
}
let addedDict = dictionary + otherDictionary

Swift: Dictionary of Dicitionaries, cannot get subscript

I've looked at other subscript issues here and I don't think they match my problem. I have a dictionary of dictionaries - Dictionary[String:Dictionary[String:String]]
In an extension I want to loop through all the values (Dictionary[String:String] and retrieve one of the values.
So I wrote this:
for dictNEO in Array(self.values) {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
and am getting this error on the last print line: Value of type 'Value' has no subscripts
Here's the first two print lines:
["nominalDist": "\"13.58 ", "approachDate": "\"2020-Feb-01 08:18 ± < 00:01\"", "minimumDist": "\"13.58 ", "diameter": "\"92 m - 210 m\"", "name": "\"(2017 AE5)\""]
Dictionary<String, String>
So I am confused as to why it is telling me it has no subscripts when it sees the type of as a Dictionary.
You have written this as an extension to Dictionary if I understand you correctly and that means that self is generic and defined as Dictionary<Key, Value> and not to you specific type so in your for loop you are looping over an array of [Value].
So you need to typecast Value before accessing it as a dictionary
if let dictionary = dictNEO as? [String: String] {
print(dictNEO["approachDate"])
}
but since it makes little sense to have an extension to Dictionary where you access a specific key it would be better to write it as a function. Since the dictionary is well defined now there is no issue with the last print
func printValuesForSubKey(_ key: String, _ dict: [String: [String: String]]) {
for (dictNEO) in dict.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO[key])
}
}
Note, I don't have an explanation why type(of:) recognises it as [String: String]
The code snippet doesn't work because values property is a collection of collections and with Array(values) you create a collection of collection of collections. In short, instead going down the code goes up and creates new collection level.
Solution with a Higher order function map:
self.values.map { print(type(of: $0)); $0["approachDate"] }
Solution with For-In Loop
for dictNEO in self.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}

accessing keys from a returned sorted array of dictionaries in Swift 4

I have an array of dictionaries called groupedDictionary defined below:
// The type is [String : [SingleRepository]]
let groupedDictionary = Dictionary(grouping: finalArrayUnwrapped) { (object) -> String in
var language = "Not Known to GitHub"
if let languageUnwrapped = object.language {
language = languageUnwrapped
}
return language
}
I can easily get all the keys as follows:
let keys = groupedDictionary.keys
However, when I try to sort this array using sorted(by:) in Swift 4, I successfully get the sorted array back with the type [(key: String, value: [SingleRepository])].
// the type is [(key: String, value: [SingleRepository])]
let sortedGroupedDictionary = groupedDictionary.sorted(by: { ($0.value.count) > ($1.value.count) })
How can I get all of the keys from sortedGroupedDictionary?
It is not possible to call ".keys" on sortedGroupedDictionary, since it has a different type.
Please note: I'm not trying to sort the array based on the keys. I did sort the array that consists of dictionaries, based on a predicate which is size of the array containing each value, now I just want to extract the keys.
The method Dictionary.sorted(by:) returns the keys and values of your original dictionary as an array of key-value pairs, sorted by the predicate you pass as an argument. That means that the first element of each tuple is the key you're looking for.
You can go through the result like this:
for (key, value) in sortedGroupedDictionary {
// handle this key-value-pair
}
If all you need is an array of the sorted keys, you can get that using
sortedGroupedDictionary.map { $0.key }

Accessing values in a dictionary containing AnyObject

I have some data I stored into a dictionary which is defined as:
let data = Dictionary<String, AnyObject>()
In this dictionary the value is always a string, but the value can be an array or integer or string. But when I try to access an item in a array in this dictionary, like:
let item = data["key"][0]
It gives me this error:
Cannot subscript value of type "AnyObject"
How should I access that item?
You need to tell the compiler that you're expecting an array:
if let array = data["key"] as? [Int] {
let item = array[0]
}
Without that, the compiler only knows that there MAY be an AnyObject in data["key"] (it might also be nil).

Swift filter function

I am trying to remove null value for key in dictionary
so I have this kind of data:
let dic = ["FirstName": "Anvar", "LastName": "Azizov", "Website": NSNull(),"About": NSNull()]
let array = [dic,2,3,4]
let jsonResult:[String: AnyObject] = ["FirstName": "Anvar", "LastName": "Azizov", "Website": array,"About": NSNull()]
let jsonCleanDictionary = filter(jsonResult, {!($0.1 is NSNull)})
can not understand syntax of above filter function
Do not use NSNull() in swift instead prefer using nil. Further, since its a dictionary adding keys with a null value is pretty useless since dictionaries will return nil if the key doesn't exist. So when checking for null all you have to do is
if let some = dic["key"] as? Value {
// some now contains the value inside dic's key as a value type of Value.
}
Also the filter function works by taking a block which returns a bool so:
dict.filter { (key, value) -> Bool in
// Do stuff to check key and value and return a
// bool which is true if you want that key, value pair to
// appear in the filtered result.
}
In swift closure arguments can get anonymous names if not explicitly return. These names are of the format $0, $1, etc. Now, the filter function takes only parameter specifically the Self.Generator.Element from the CollectionType protocol. For dictionaries this is a tuple containing the key and the value. To access members of unnamed tuples you use .0, .1, .2, etc. depending on the index of the tuple member. So for dictionaries Self.Generator.Element is a tuple containing the key and the value. So $0.1 refers to the value of the key,value pair. Hope this clears this weird syntax up a little bit.