I am receiving data from Firestore in the current format;
[(key: "capacity", value: <__NSArrayM 0x600000bd8030>(
32GB,
64GB,
128GB
)
), (key: "condition", value: <__NSArrayM 0x600000bd8180>(
New,
Used
)
)]
Function:
query.getDocuments { snapshot, error in
guard let document = snapshot?.documents else { return }
let flatMap = document.flatMap({ $0.data() })
print(flatMap)
}
I am trying to map this array of [[String: [String]]] into an array of my object such as;
struct Object {
var key: String
var value: [String]
}
How would I go about doing this?
Map method execute a mapping and return an array.
You can think that mapping is an operation to do, in your case you have to transform an element of [String : [String]] in an element of type Object.
Following the map Syntax, you have to apply this transformation only to one element and it will return an array of that elements.
In your case you have just to call the Object init.
let document : [String : [String]] = ["Key1" : ["Value1.0", "Value1.1", "Value1.2"]];
let objects : [Object] = document.map { Object(key: $0.key, value: $0.value) }
print(objects)
Related
I have a data structure in a Dictionary which looks like this:
- device
- type
- isActive
- Data
- Manufacturer
- Build Date
- Power
...
- Example
Now if I create a For Loop it only shows me the Values of the First level which means
type, isActive, Data, Example
for (key, value) in value {
}
All below Data like Build Date or Power is Missing - how can I irritate through this levels also?
Assuming you have Dictionary with type [String:Any], function to flatten it will be something like:
func flatten(_ obj:[String:Any]) -> [String:Any] {
var result = [String:Any]()
for (key, val) in obj {
if let v = val as? [String:Any] {
result = result.merging(flatten(v), uniquingKeysWith: {
$1
})
}
//I also included initial value of internal dictionary
/if you don't need initial value put next line in 'else'
result[key] = val
}
return result
}
To use that:
let d:[String:Any] = [
"test1":1,
"test2":2,
"test3":[
"test3.1":31,
"test3.2":32
]
]
let res = flatten(d)
print(res)
["test2": 2, "test3.2": 32, "test3.1": 31, "test1": 1, "test3":
["test3.2": 32, "test3.1": 31]]
note: dictionaries are not sorted structures
You are dealing with a Recursion problem here.
You could walk the dictionary level by level:
func walk(dictionary: [String: Any]) {
for (key, value) in dictionary {
print("\(key): \(value)")
if let value = value as? [String: Any] {
walk(dictionary: value)
}
}
}
You can change [String: Any] type with your dictionary type (NSDictionary, etc.)
As Tomas says, this can be solved with recursion. His "walk" function simply prints the values on separate lines, but with no keys, and not formatting. Here is code that logs nested dictionaries using the structure you outlined:
//Define the keys we use in our example
enum keys: String {
case device
case type
case isActive
case Data
case Manufacturer
case BuildDate
case Power
case Example
}
//Create a sample dictionary using those keys and some random values
let dict = [keys.device.rawValue:
[keys.type.rawValue: "appliance",
keys.isActive.rawValue: true,
keys.Data.rawValue:
[keys.Manufacturer.rawValue: "GE",
keys.BuildDate.rawValue: "12/23/96",
keys.Power.rawValue: 23,
],
keys.Example.rawValue: "Foo"
]
]
//Create an extension of Dictionary for dictionaries with String Keys.
extension Dictionary where Key == String {
//Define a function to recursively build a description of our dictionary
private func descriptionOfDict(_ aDict: [String: Any], level: Int = 0) -> String {
var output = ""
var indent: String = ""
if level > 0 {
output += "\n"
for _ in 1...level {
indent += " "
}
}
for (key,value) in aDict {
output += indent + key + ": "
if let subDict = value as? [String: Any] {
//If the value for this key is another dictionary, log that recursively
output += descriptionOfDict(subDict, level: level+1)
} else {
if let aString = value as? String {
output += "\"" + aString + "\"\n"
} else {
output += String(describing: value) + "\n"
}
}
}
return output
}
//Add a description property for dictionaries
var description: String {
return descriptionOfDict(self)
}
}
print(dict.description)
That outputs:
device:
isActive: true
Data:
Manufacturer: "GE"
Power: 23
BuildDate: "12/23/96"
Example: "Foo"
type: "appliance"
Edit:
The above, defining a String property description, changes the output when you print a [String:Any] dictionary. If you don't want that, rename the property description to something else like dictDescription
I'm trying to save array added from tableview using this function:
class func saveArray(_ value: [Dictionary<String, AnyObject>], key: String) {
let data = NSKeyedArchiver.archivedData(withRootObject: value)
UserDefaults.standard.set(data, forKey: key)
}
Below is the function where I want to save the array:
func addItemCat(items: [Data]) {
print("ITEM: ", items)
dataSource.myListTableViewController.myListArr = items
self.myListTV.isHidden = false
UserDefaultsHelper.saveArray(items, key: Constants.myList.myList)
}
However, I got this error: Cannot convert value of type '[Data]' to expected argument type '[Dictionary String, AnyObject ]'
Below is my Data model:
data model screencap
I'm new to Swift and I hope someone can explain what is the problem.
Problem is with data types, saveArray function expects value parameter of type array of dictionary [Dictionary<String, AnyObject>], but you are passing array of data model objects which is a type-mismatch error.
To solve this:
First, You should not use pre-defined keywords for creating your custom object. Use DataObject instead:
struct DataObject {
}
Now change your saveArray function as:
class func saveArray(_ value: [DataObject], key: String) {
let data = NSKeyedArchiver.archivedData(withRootObject: value)
UserDefaults.standard.set(data, forKey: key)
}
and addItemCat, function as:
func addItemCat(items: [DataObject]) {
print("ITEM: ", items)
dataSource.myListTableViewController.myListArr = items
self.myListTV.isHidden = false
UserDefaultsHelper.saveArray(items, key: Constants.myList.myList)
}
I have a Firebase Database structured like this:
results: {
A: [
{data}
],
B: [
{data}
],
C: [
{data}
],
....
}
It is a dictionary of values sorted alphabetically.
My question is, what would be the best way to retrieve this data in swift?
Generally, the most common practice for retrieving Firebase data is to cast the snapshot's value to a Swift Dictionary like this:
let myRef = FIRDatabase.database().reference().child("results")
myRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
// handle data not found
return
}
// data found
let myData = snapshot.value as! [String: Any] // the key is almost always a String
// extracting data from the dictionary
let A = myData["A"] as! [String: Any]
let dataInA = A["anyChildOfA"] as! Double
// ...
})
It doesn't really matter if the data is sorted or not on Firebase, retrieval would be of the same complexity
Update
You can simply loop over all the alphabets to extract the data:
var extractedData = [[String: Any]]() // array of Swift Dictionary
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters {
let temp = myData[String(c)] as! [String: Any]
extractedData.append(temp);
}
extractedData[0] would be the data in A, extractedData[25] would be the data in Z. That, of course, is assuming you have a child for each letter.
Create a struct to present "data":
struct Data {
...
}
Create a wrapper:
struct DataWrapper {
let data: [Data]
}
Then, create a new model:
struct ModelName {
results: [DataWrapper]
}
I am trying to filter a dictionary in swift:
var data: [String: String] = [:]
data = data.filter { $0.1 == "Test" }
the filter code above compiles under Swift 2 but yields the following error:
Cannot assign a value of type '[(String, String)]' to a value of type '[String : String]'
is this a bug in the Swift compiler or is this not the right way to filter dictionaries in Swift?
This has been fixed in Swift 4
let data = ["a": 0, "b": 42]
let filtered = data.filter { $0.value > 10 }
print(filtered) // ["b": 42]
In Swift 4, a filtered dictionary returns a dictionary.
Original answer for Swift 2 and 3
The problem is that data is a dictionary but the result of filter is an array, so the error message says that you can't assign the result of the latter to the former.
You could just create a new variable/constant for your resulting array:
let data: [String: String] = [:]
let filtered = data.filter { $0.1 == "Test" }
Here filtered is an array of tuples: [(String, String)].
Once filtered, you can recreate a new dictionary if this is what you need:
var newData = [String:String]()
for result in filtered {
newData[result.0] = result.1
}
If you decide not to use filter you could mutate your original dictionary or a copy of it:
var data = ["a":"Test", "b":"nope"]
for (key, value) in data {
if value != "Test" {
data.removeValueForKey(key)
}
}
print(data) // ["a": "Test"]
Note: in Swift 3, removeValueForKey has been renamed removeValue(forKey:), so in this example it becomes data.removeValue(forKey: key).
data.forEach { if $1 != "Test" { data[$0] = nil } }
Just another approach (a bit simplified) to filter out objects in your dictionary.
Per Apple docs, filter:
Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
https://developer.apple.com/reference/swift/sequence/1641239-filter
I found myself needing to do what the OP was asking about and ended up writing the following extensions (Swift 3):
extension Dictionary
{
func filteredDictionary(_ isIncluded: (Key, Value) -> Bool) -> Dictionary<Key, Value>
{
return self.filter(isIncluded).toDictionary(byTransforming: { $0 })
}
}
extension Array
{
func toDictionary<H:Hashable, T>(byTransforming transformer: (Element) -> (H, T)) -> Dictionary<H, T>
{
var result = Dictionary<H,T>()
self.forEach({ element in
let (key,value) = transformer(element)
result[key] = value
})
return result
}
}
Usage:
let data = ["a":"yes", "b":"nope", "c":"oui", "d":"nyet"]
let filtered = data.filteredDictionary({ $0.1 >= "o" })
// filtered will be a dictionary containing ["a": "yes", "c": "oui"]
I've found this method to be useful after filtering or applying some other transform that results in an array of dictionary elements:
extension Array {
func dictionary<K: Hashable, V>() -> [K: V] where Element == Dictionary<K, V>.Element {
var dictionary = [K: V]()
for element in self {
dictionary[element.key] = element.value
}
return dictionary
}
}
To use it, just say something like:
dictionary = dictionary.filter{ $0.key == "test" }.dictionary()
The advantage of this method is that no argument of any kind needs to be passed to the dictionary() method. Generic type arguments tell the compiler everything it needs to know.
You can arrange ascending order according to dictionary value using filter
let arrOfDict = [{"ABC":24},{"XYZ":21},{"AAA":23}]
let orderedDict = arrOfDict.filter{$0.value < $1.value}
you will get below output:
[
{ "XYZ": 21 },
{ "AAA": 23 },
{ "ABC": 24 }
]
Trying to fill an array with strings from the keys in a dictionary in swift.
var componentArray: [String]
let dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Components", ofType: "plist")!)
componentArray = dict.allKeys
This returns an error of: 'AnyObject' not identical to string
Also tried
componentArray = dict.allKeys as String
but get: 'String' is not convertible to [String]
Swift 3 & Swift 4
componentArray = Array(dict.keys) // for Dictionary
componentArray = dict.allKeys // for NSDictionary
With Swift 3, Dictionary has a keys property. keys has the following declaration:
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
A collection containing just the keys of the dictionary.
Note that LazyMapCollection that can easily be mapped to an Array with Array's init(_:) initializer.
From NSDictionary to [String]
The following iOS AppDelegate class snippet shows how to get an array of strings ([String]) using keys property from a NSDictionary:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let string = Bundle.main.path(forResource: "Components", ofType: "plist")!
if let dict = NSDictionary(contentsOfFile: string) as? [String : Int] {
let lazyMapCollection = dict.keys
let componentArray = Array(lazyMapCollection)
print(componentArray)
// prints: ["Car", "Boat"]
}
return true
}
From [String: Int] to [String]
In a more general way, the following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with string keys and integer values ([String: Int]):
let dictionary = ["Gabrielle": 49, "Bree": 32, "Susan": 12, "Lynette": 7]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection)
print(stringArray)
// prints: ["Bree", "Susan", "Lynette", "Gabrielle"]
From [Int: String] to [String]
The following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with integer keys and string values ([Int: String]):
let dictionary = [49: "Gabrielle", 32: "Bree", 12: "Susan", 7: "Lynette"]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection.map { String($0) })
// let stringArray = Array(lazyMapCollection).map { String($0) } // also works
print(stringArray)
// prints: ["32", "12", "7", "49"]
Array from dictionary keys in Swift
componentArray = [String] (dict.keys)
You can use dictionary.map like this:
let myKeys: [String] = myDictionary.map{String($0.key) }
The explanation:
Map iterates through the myDictionary and accepts each key and value pair as $0. From here you can get $0.key or $0.value. Inside the trailing closure {}, you can transform each element and return that element. Since you want $0 and you want it as a string then you convert using String($0.key). You collect the transformed elements to an array of strings.
dict.allKeys is not a String. It is a [String], exactly as the error message tells you (assuming, of course, that the keys are all strings; this is exactly what you are asserting when you say that).
So, either start by typing componentArray as [AnyObject], because that is how it is typed in the Cocoa API, or else, if you cast dict.allKeys, cast it to [String], because that is how you have typed componentArray.
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
dict.keys.sorted()
that gives [String]
https://developer.apple.com/documentation/swift/array/2945003-sorted
From the official Array Apple documentation:
init(_:) - Creates an array containing the elements of a sequence.
Declaration
Array.init<S>(_ s: S) where Element == S.Element, S : Sequence
Parameters
s - The sequence of elements to turn into an array.
Discussion
You can use this initializer to create an array from any other type that conforms to the Sequence protocol...You can also use this initializer to convert a complex sequence or collection type back to an array. For example, the keys property of a dictionary isn’t an array with its own storage, it’s a collection that maps its elements from the dictionary only when they’re accessed, saving the time and space needed to allocate an array. If you need to pass those keys to a method that takes an array, however, use this initializer to convert that list from its type of LazyMapCollection<Dictionary<String, Int>, Int> to a simple [String].
func cacheImagesWithNames(names: [String]) {
// custom image loading and caching
}
let namedHues: [String: Int] = ["Vermillion": 18, "Magenta": 302,
"Gold": 50, "Cerise": 320]
let colorNames = Array(namedHues.keys)
cacheImagesWithNames(colorNames)
print(colorNames)
// Prints "["Gold", "Cerise", "Magenta", "Vermillion"]"
Swift 5
var dict = ["key1":"Value1", "key2":"Value2"]
let k = dict.keys
var a: [String]()
a.append(contentsOf: k)
This works for me.
NSDictionary is Class(pass by reference)
Dictionary is Structure(pass by value)
====== Array from NSDictionary ======
NSDictionary has allKeys and allValues get properties with
type [Any].
let objesctNSDictionary =
NSDictionary.init(dictionary: ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"])
let objectArrayOfAllKeys:Array = objesctNSDictionary.allKeys
let objectArrayOfAllValues:Array = objesctNSDictionary.allValues
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
====== Array From Dictionary ======
Apple reference for Dictionary's keys and values properties.
let objectDictionary:Dictionary =
["BR": "Brazil", "GH": "Ghana", "JP": "Japan"]
let objectArrayOfAllKeys:Array = Array(objectDictionary.keys)
let objectArrayOfAllValues:Array = Array(objectDictionary.values)
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
This answer will be for swift dictionary w/ String keys. Like this one below.
let dict: [String: Int] = ["hey": 1, "yo": 2, "sup": 3, "hello": 4, "whassup": 5]
Here's the extension I'll use.
extension Dictionary {
func allKeys() -> [String] {
guard self.keys.first is String else {
debugPrint("This function will not return other hashable types. (Only strings)")
return []
}
return self.flatMap { (anEntry) -> String? in
guard let temp = anEntry.key as? String else { return nil }
return temp }
}
}
And I'll get all the keys later using this.
let componentsArray = dict.allKeys()
// Old version (for history)
let keys = dictionary.keys.map { $0 }
let keys = dictionary?.keys.map { $0 } ?? [T]()
// New more explained version for our ducks
extension Dictionary {
var allKeys: [Dictionary.Key] {
return self.keys.map { $0 }
}
}