Jumping into swift - trouble with array of dictionary from json - swift

json comes in and is an array of dictionary:
let dict = try JSONSerialization.dictionary(data: data, options: .allowFragments)
(key: contact_383348580, value: {
email = "r#c.com";
"first_name" = Jon;
"last_name" = B;
tags = "";
})
(key: contact_445575065, value: {
email = "n.w#s.com";
"first_name" = "<null>";
"last_name" = "<null>";
tags = "";
})
Trying to map this to a User class (and then sort alpha by first name(?)) and then populate a tableview.
I'm all obj-c but trying to add this feature in my objc project through swift to improve my skills incrementally. But googling returns all different ways with different swift versions that don't seem to work (or frankly make sense to me anyway but I'll get there).
Currently I can create users but the names are empty.
for objects in dict {
print(objects)
let first = dict["first_name"] as? String
let last = dict["last_name"] as? String
let name = "\(first ?? "asas") \(last ?? "sdasd")"
let object = User(username: name)
contacts.append(object!)
}
print(" contacts \(contacts)")

You say you have an array of dictionaries and if that is correct then you have fooled yourself with the naming of your variables because dict is an array and objects a dictionary. I assume your code doesn't compile?
What about this
let array = try JSONSerialization.dictionary(data: data, options: .allowFragments)
for dict in array {
let first = dict["first_name"] as? String ?? "asas"
let last = dict["last_name"] as? String ?? "sdasd"
let name = "\(first) \(last)"
let object = User(username: name)
contacts.append(object)
}

Related

SwiftUI, Firestore get array of object within object

I am working on a recipe-app connected to firestore and have trouble reading the data saved in the database. I save a recipe that consists of title, id etc but it also contains an array of ingredients. This array is a struct containing id, name and amount. I am able to get the recipe object but the array of ingredients is empty. This is how is get the recipe
private func listenForRecipes() {
db.collection("recipe").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.recipes = documents.map { queryDocumentSnapshot -> RecipePost in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let steps = data["steps"] as? [Step] ?? []
let ingredients = data["ingredients"] as? [Ingredient] ?? []
let serves = data["serves"] as? Int ?? 0
let author = data["author"] as? String ?? ""
let authorId = data["authorId"] as? String ?? ""
let category = data["category"] as? String ?? ""
let image = data["image"] as? String ?? ""
print("\(ingredients)")
return RecipePost(title: title, steps: steps, ingredients: ingredients, serves: serves, author: author, authorId: authorId, category: category, image: image)
}
}
}
Thankful for any help.
The data that you're getting from Firebase is coming back to you in the form of a [String:Any] dictionary. Your current code is taking those dictionary keys (title, author, etc) and doing optional casts (the as?), telling the system "if this data is actually a String, then set my variable to that value. If not (??), here's the default value to use instead"
The problem comes when you introduce custom types. The system doesn't inherently know that your item is an Ingredient or Step. So, the cast fails, and you get the default value of [].
You have two options:
Use a custom type for your entire document (see Firebase documentation on this here: https://firebase.google.com/docs/firestore/query-data/get-data#swift_3). This SO question is also relevant: How to convert document to a custom object in Swift 5?
Convert the [String:Any] dictionary (or array of dictionaries as it may be in this case) yourself. First step might be to print data["ingredients"] to the console to see what it really has in it. Without being able to see what you actually have in Firestore, Let's assume it is a [[String:Any]] (an array of dictionaries). Then your conversion might look like this:
let ingredients = (data["ingredients"] as? [[String:Any]]).map { item in
return Ingredient(id: item["id"] as? String ?? "", name: item["name"] as? String ?? "", amount: item["amount"] as? String ?? "")
}
You can also experiment with using Codable, which could allow you to automate some of this process, say with JSONDecoder to do some of the work for you. Relevant SO: How can I use Swift’s Codable to encode into a dictionary?

Type 'Any' has no subscript members when accessing dictionary in UserDefaults

I am trying to access the values of a dictionary which I passed into UserDefaults, however I get this error:
Type 'Any' has no subscript members.
I have looked around on StackOverflow for answers but everything I tried is failing.
This is the Dictionary object that I stored in UserDefaults and I am trying to access Authorities.levelAcess:
{
accountEnabled = 0;
accountLocked = 0;
Authorities = {
levelAccess = "tier2";
accessType = "3rd party";
};
message = "Logged in Successfully";
profile = "trust";
roles = (
{
authority = "admin";
}
);
status = 1;
}
This the code for UserDefaults:
let keyDict: Dictionary<String, AnyObject> = json
UserDefaults.standard.set(json, forKey: "dict")
This is how I am trying to access the dictionary:
The reason of showing this error in your code is: the compiler cannot recognize result as dictionary, if you tried to option and click on result you would see that its type is Any. You have to cast it first as [String: AnyObject] and then get "Authorities" form it.
It should be like this:
if let result = UserDefaults.standard.dictionary(forKey: "dict") {
print(result)
}
That's how you should "optional binding" the dictionary, thus (assumeing that "Authorities" is a [String: String]):
if let addresses = result["Authorities"] as? [String: String] {
print(addresses)
}
You could also do it as one step:
if let result = UserDefaults.standard.dictionary(forKey: "dict"),
let addresses = result["Authorities"] as? [String: String] {
print(result)
print(addresses)
let levelAccess = addresses["levelAccess"]
print(levelAccess) // optional
}
Finally, you could get the levelAcess from addresses as:
let levelAccess = addresses["levelAccess"]
Again, note that levelAccess would be an optional string (String?), which means you should also handle it.
For fetching a dictionary from UserDefaults, use
dictionary(forKey: key)
In your case:
var result = UserDefaults.standard.dictionary(forKey: "dict")

How to access nested dictionary?

I can print this in the debugger:
(lldb) print params["message"]!
([String : String]) $R5 = 2 key/value pairs {
[0] = (key = "body", value = "iPadUser has started a new stream")
[1] = (key = "title", value = "Stream started")
}
But I am trying to figure out how to access the body and title separately.
I construct params in this way:
let recipients = ["custom_ids":[recips]]
let notificationDetails = "hello there"
let content = [
"title":title,
"body":details
]
let params: [String:Any] = [
"group_id":"stream_requested",
"recipients": recipients,
"message": content
]
print((params["message"] as! [String: Any])["title"] as! String)
You need to cast the Dictionary value as specific type, since the compiler doesn't know what to expect. (Please mind that you mustn't use force unwrap in other way than example code.)
Considering you need to fetch array values when recipients dictionary looks like this:
let recipients = ["custom_ids":["recipe1", "recipe2", "etc"]]
get to the ids like this:
guard let recipients = params["recipients"] as? [String: Any],
let customIDs = recipients["custom_ids"] as? [String]
else { return }
for id in customIDs {
print(id) // Gets your String value
}

Swift Array to Set : Showing Recent Messages Command failed due to signal: Segmentation fault: 11

I am using firebase to retrieve a list of data then convert it to an NSDictonary array. I want to parse the data by a property e.g name
func getAllMyModels() {
if let e = email {
_ = ref.child("childName").queryOrdered(byChild: "email").queryEqual(toValue: e).observe(.value) { snapshot in
var dictionary = [NSDictionary]()
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
dictionary.append(NSDictionary(dictionary: value as! [String: Any]))
}
let names = dictionary.flatMap {$0["name"]} // names print correct values
let set = Set(names)
print(set)
}
}
}
This code can't be complied the error is:
Showing Recent Messages
Command failed due to signal: Segmentation fault: 11
If i removed this line:
let set = Set(Array(names))
all works fine.
I also tested by replace it by this block
let ar = ["name1","name2"].flatMap { return $0 }
Set(ar)
No errors.
Not sure why? Who can tell, thanks!
EDIT: Even though the element in the array is String type but the names array is [Any], so the solution is
let names = dictionary.flatMap {$0["name"]} as! [String]
I think this errors occurs because the Array you generate from the dictionary with flatMap is an Array of Any and not a String Array, try to cast to a String like this:
...
let names = dictionary.flatMap {$0["name"] as? String}
let set = Set(Array(names))
...
Hope this help you
Cast names to [String]:
let names = dictionary.flatMap {$0["name"]} as! [String]

Sorting plist data

I've got some repeating data in a plist, I then extract it into a dictionary and display it in my app. The only problem is that it needs to be in the same order i put it in the plist, but obviously, dictionary's can't be sorted and it comes out unsorted. So how would i achieve this?
My plist data repeats like this
I then convert that into a dictionary of type [Int : ItemType], ItemType is my data protocol, like this:
class ExhibitionUnarchiver {
class func exhibitionsFromDictionary(_ dictionary: [String: AnyObject]) throws -> [Int : ItemType] {
var inventory: [Int : ItemType] = [:]
var i = 0;
print(dictionary)
for (key, value) in dictionary {
if let itemDict = value as? [String : String],
let title = itemDict["title"],
let audio = itemDict["audio"],
let image = itemDict["image"],
let description = itemDict["description"]{
let item = ExhibitionItem(title: title, image: image, audio: audio, description: description)
inventory.updateValue(item, forKey: i);
i += 1;
}
}
return inventory
}
}
Which results in a dictionary like this:
[12: App.ExhibitionItem(title: "Water Bonsai", image: "waterbonsai.jpg", audio: "exhibit-audio-1", description: "blah blah blah"), 17: App.ExhibitionItem.....
I was hoping that since i made the key's Int's i could sort it but so far i'm having no luck. You might be able to tell i'm fairly new to swift, so please provide any info you think would be relevant. Thanks!
A Dictionary has no order. If you need a specific order, make root of type Array:
or sort it by the key manually:
var root = [Int:[String:String]]()
root[1] = ["title":"Hi"]
root[2] = ["title":"Ho"]
let result = root.sorted { $0.0 < $1.0 }
print(result)
prints:
[(1, ["title": "Hi"]), (2, ["title": "Ho"])]