flatMap a Dictionary of Dictionaries in Swift - ios

I have an NSEnumerator which contains nested key value objects like this:
[ "posts" :
["randonRootKey1" :
["randomChildKey1" : [:] ]
],
["randonRootKey2" :
["randomChildKey2" : [:] ],
["randomChildKey3" : [:] ],
]
]
- posts
-- user1
--- posts
----post
-- user2
-- posts
--- post
I want to extract all posts of all users in one array ... last child and all parents are dictionaries
I want to flatMap it to be :
[
["randomChildKey1" : [:] ],
["randomChildKey2" : [:] ],
["randomChildKey3" : [:] ]
]
Note that I have extracted the objects of each root dictionary.
I have tried :
let sub = snapshot.children.flatMap({$0})
but does not seem to work

let input: [String: [String: [String: Any]]] = ["posts":
[
"randonRootKey1": [
"randomChildKey1": [:],
],
"randonRootKey2": [
"randomChildKey2": [:],
"randomChildKey3": [:],
]
]
]
var output = [String: Any]()
for dictionary in input["posts"]!.values {
for (key, value) in dictionary {
output[key] = value
}
}
print(output)
["randomChildKey3": [:], "randomChildKey2": [:], "randomChildKey1": [:]]

Assuming the input to be in this format
let input: [String: [String: [String: Any]]] = ["posts":
[
"randonRootKey1": [
"randomChildKey1": [:],
],
"randonRootKey2": [
"randomChildKey2": [:],
"randomChildKey3": [:],
]
]
]
Using this
let output = input.flatMap{$0.1}.flatMap{$0.1}
You will get the desired output
[("randomChildKey1", [:]), ("randomChildKey2", [:]), ("randomChildKey3", [:])]
If you want to convert the tuple to dictionary use reduce
let output = input.flatMap{$0.1}.flatMap{$0.1}.reduce([String: Any]())
{
(var dict, tuple) in
dict.append([tuple.0: tuple.1])
return dict
}
[["randomChildKey1": {}], ["randomChildKey2": {}], ["randomChildKey3": {}]]

Related

Swift: Remove keys and values added during array flatMap conversion

Trying to make array of dictionaries with object, which adds keys and values during array flatMap
var parameters: [String: Any] {
var postParameters: [String: Any] = [:]
postParameters["topics"] = categories.flatMap({ toPostParameters($0) })
print(postParameters)
return postParameters
}
private func toPostParameters(_ topics: Topics) -> [String: Any] {
var params: [String: Any] = [:]
params["id"] = topics.id
params["isFound"] = topics.isFound
return params
}
when I print the postParameters value, all I get is like the following. How to remove the key and value strings appending over it.
["topicCategories": [(key: "isFound", value: true), (key: "id", value: "62ef2b63e49a"),
(key: "id", value: "632serer269"), (key: "isFound", value: true)]]
Expected
["topicCategories": [["isFound": true, "id": "62ef2b63e49a"],
["id": "632serer269","isFound": true]]]
You should use map, because flatMap makes everything "flat" and you get a flat array of tuples instead of array of dictionaries.
Example:
class Topics {
var id = 1
var isFound = true
}
var categories = [Topics(), Topics(), Topics()]
var parameters: [String: Any] {
var postParameters: [String: Any] = [:]
postParameters["topics"] = categories.map({ toPostParameters($0) })
return postParameters
}
private func toPostParameters(_ topics: Topics) -> [String: Any] {
var params: [String: Any] = [:]
params["id"] = topics.id
params["isFound"] = topics.isFound
return params
}
print(parameters)
Output:
["topics": [["id": 1, "isFound": true], ["id": 1, "isFound": true], ["isFound": true, "id": 1]]]
P.S:
Good article to understand the difference between map, compactMap and flatMap.

Creating new Dict from values of another

I have JSON like this:
{
"SKzSjdBuOpO49wkPVaxtmLQoR0O2": {
location: "-LB1fn5GQKTaIc-fGNPS"
},
"s0ntSZP7mBQw1a8ua0E5PqazlqJ2": {
location: "-LB23izD7bp2NhO0UuIo"
},
"FEScjelpNSR7vuVz1ha6Z6uSfF43": {
location: "-LB1JLABuJO1NyJ-v2t_"
},
"mW7dGja6cRRB0d9uSIhg4f35PzC3": {
location: "-LB5xqUvIjEgtW_FH_nG"
},
"GCnt0VKcqjT8bl2chw43nsntaZK2": {
location: "-LB1n8ga9C0vCcZXD2Us"
}
}
in my Values[String: Any] dictionary. And i want to Create another dictionary from its values to access location value.
Can you help me to do this?
If the primary goal is just to access the values and the type is fixed to [String: Any], do it like this:
if let info = dictionary["SKzSjdBuOpO49wkPVaxtmLQoR0O2"] as? [String : String] {
let location = info["location"]
}
Try not to use Dictionary in swift, rather for your case you can use [String : [String: String]] type and then loop through you data to get the required values. Try this
let dict = ["SKzSjdBuOpO49wkPVaxtmLQoR0O2": [
"location" : "-LB1fn5GQKTaIc-fGNPS",
], "s0ntSZP7mBQw1a8ua0E5PqazlqJ2": [
"location" : "-LB23izD7bp2NhO0UuIo",
], "FEScjelpNSR7vuVz1ha6Z6uSfF43": [
"location" : "-LB1JLABuJO1NyJ-v2t_",
], "mW7dGja6cRRB0d9uSIhg4f35PzC3": [
"location" : "-LB5xqUvIjEgtW_FH_nG",
], "GCnt0VKcqjT8bl2chw43nsntaZK2": [
"location" : "-LB1n8ga9C0vCcZXD2Us",
]]
var newDict = [String: String]()
for (key, val) in dict {
newDict[key] = val["location"]
}
print(newDict)
Output:
["SKzSjdBuOpO49wkPVaxtmLQoR0O2": "-LB1fn5GQKTaIc-fGNPS",
"s0ntSZP7mBQw1a8ua0E5PqazlqJ2": "-LB23izD7bp2NhO0UuIo",
"FEScjelpNSR7vuVz1ha6Z6uSfF43": "-LB1JLABuJO1NyJ-v2t_",
"mW7dGja6cRRB0d9uSIhg4f35PzC3": "-LB5xqUvIjEgtW_FH_nG",
"GCnt0VKcqjT8bl2chw43nsntaZK2": "-LB1n8ga9C0vCcZXD2Us"]
Edit
For your case as you have dictionary, you can try this:
let dict = ["SKzSjdBuOpO49wkPVaxtmLQoR0O2": [
"location" : "-LB1fn5GQKTaIc-fGNPS",
], "s0ntSZP7mBQw1a8ua0E5PqazlqJ2": [
"location" : "-LB23izD7bp2NhO0UuIo",
], "FEScjelpNSR7vuVz1ha6Z6uSfF43": [
"location" : "-LB1JLABuJO1NyJ-v2t_",
], "mW7dGja6cRRB0d9uSIhg4f35PzC3": [
"location" : "-LB5xqUvIjEgtW_FH_nG",
], "GCnt0VKcqjT8bl2chw43nsntaZK2": [
"location" : "-LB1n8ga9C0vCcZXD2Us",
]] as Dictionary<String, Any>
var newDict = [String: String]()
for (key, val) in dict {
if let value = val as? [String: String] {
newDict[key] = value["location"]
}
}
print(newDict)

how to change value of dictionary of dictionary effectively and dynamically

If I have a dictionary "dic" like this:
{
"a": {
"b": Any
}
"c": {
"d": Any
}
}
If I want to change the value of key "b", I know I can do it like this:
dic["a"]["b"] = somethingNew
But If the key path is varible, how can I change the value according to the key path? is there some api like this:
dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)
Or the idea to achieve this purpose , thanks ~
I recently answered a Q&A that wanted to remove (rather than update) a nested value in a dictionary of nested dictionaries.
Remove nested key from dictionary
We can use a similar approach here: using a recursive method which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves. The only restriction here is that the Key types much be the same for all dictionaries within the nested dictionary.
Implementation
Recursive "core" function updateValue(_:, inDict:, forKeyPath:) and public updateValue(_:, forKeyPath:) method form key paths on for [Key] (e.g. ["a", "b"] applied to your example):
/* general "key path" extension */
public extension Dictionary {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: [Key])
-> Value? {
let (valInd, newDict) = updateValue(value, inDict: self,
forKeyPath: Array(keyPath.reversed()))
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
fileprivate func updateValue(_ value: Value, inDict dict: [Key: Any],
forKeyPath keyPath: [Key]) -> (Value?, [Key: Any]) {
guard let key = keyPath.last else { return (value, dict) }
var dict = dict
if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
let (val, newSubDict) = updateValue(value, inDict: subDict,
forKeyPath: Array(keyPath.dropLast()))
dict[key] = newSubDict
return (val, dict)
}
let val = dict.updateValue(value, forKey: key) as? Value
return (val, dict)
}
}
Less general public updateValue(_:, forKeyPath:) method (using the core function above) for keys that conforms to ExpressibleByStringLiteral; key paths on form my.key.path (e.g. "a.b" applied to your example):
/* String literal specific "key path" extension */
public extension Dictionary where Key: ExpressibleByStringLiteral {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: String)
-> Value? {
let keyPathArr = keyPath.components(separatedBy: ".")
.reversed().flatMap { $0 as? Key }
if keyPathArr.isEmpty { return self.updateValue(value, forKey: "") }
let (valInd, newDict) = updateValue(value,
inDict: self, forKeyPath:keyPathArr)
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
}
Example usage
We'll apply the methods above to the example from the linked thread.
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Using the ExpressibleByStringLiteral key path method to update the value of an existing key-value pair:
if let oldValue = dict.updateValue("nihongo",
forKeyPath: "countries.japan.language") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Removed old value: japanese
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
The same method used to add a new key-value pair at a given key path dictionary:
if let oldValue = dict.updateValue("asia",
forKeyPath: "countries.japan.continent") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Added new key-value pair
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo",
"continent": "asia"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
We would get the same result as in the examples above if we used the general [Key] as key path method instead of the ExpressibleByStringLiteral one used above. Using the former, the calls would be changed into:
... = dict.updateValue("nihongo",
forKeyPath: ["countries", "japan", "language"]
... = dict.updateValue("asia",
forKeyPath: ["countries", "japan", "continent"]
Finally note that a call to updateValue using the [Key] as key path method will return nil also in case of an empty array being passed as argument ([]). This could possibly be changed to a throwing case, as a nil return above should tell us that a new key-value pair was added (as inspired by the updateValue(_:, forKey:) method in stdlib).
Performance?
The methods above will make use of some (sub-)dictionary copying along the way, but unless you're working with huge dictionaries, this shouldn't be an issue. Anyhow, no need to worry about performance until a profiler tells you have a bottleneck.

How to create Mixed Pattern Dictionaries in Swift

I am trying to create a mixed pattern dictionary in Swift which contains either String:String or String:Dictionary as below. Can anyone help?
var tempDict = ["Electronics":"TV"],["Home":["Kitchen":"Utensils"],["BedRoom":"Bed "],["DiningRoom":"Dining"]]
I may be missing what you really want to do, but you can declare a dictionary like this:
var tempDict = ["Electronics":"TV","Home":["Kitchen":"Utensils"],"BedRoom":"Bed ","DiningRoom":"Dining"]
Swift (as of 2.2.1) infers the type of tempDict as [String: NSObject], so you can use the values with casting:
if let electronics = tempDict["Electronics"] as? String {
print(electronics)
}
if let home = tempDict["Home"] as? [String: String] {
print(home)
}
Use AnyObject:
var tempDict: [String: AnyObject] = ["Electronics":"TV"]
tempDict = ["Home":["Kitchen":"Utensils"]
["BedRoom":"Bed "],
["DiningRoom":"Dining"]]
var tempDict = [
"ElectronicsString" : "TV",
"HomeDic" : [
[
"Kitchen" : "Utensils",
"BedRoom":"Bed ",
"DiningRoom":"Dining",
]
],
"BetArray" : [
"cat",
"dog"
]
]
By the way, if a dictionary is a big compounded dictionary, the complier of Swift will have error. So you can solve it like this:
var tempDict: [String: AnyObject] = [
"ElectronicsString" : "TV"
]
tempDict["HomeDic"] = [
[
"Kitchen" : "Utensils",
"BedRoom":"Bed ",
"DiningRoom":"Dining",
]
]
tempDict["BetArray"] = [
"cat",
"dog"
]

SwiftyJSON and adding to existing [String: AnyObject] dictionary

This is the JSON I am looking to parse with the SwiftyJSON library:
{
array: [
{ a: "something", b: "something" },
{ a: "something", b: "something" },
{ a: "something", b: "something" }
]
}
The catch is that the array count will have variable amount of objects. Normally I would just create a Dictionary with [String: AnyObject], but can't do a for loop inside it.
var parameters: [String: AnyObject] = [
"array": [
]
]
Is there any way I can append/add [String: AnyObject] elements to an existing Dictionary ("array" dictionary)?
I would then convert it to a JSON with the SwiftyJSON lib.
I would suggest constructing the inner array first. It is possible to reach into your nested data structure and modify things, but tricky because you have to be very careful about types.
var innerArray: [AnyObject] = []
for i in 0...2 {
innerArray.append(["a":"something","b":"something"])
}
var parameters: [String: AnyObject] = ["array": innerArray]

Resources