Swift and JSON questions - arrays

I'm kind of new to Swift currently playing around with Stickers.
I have a JSON file with the following structure:
{
"stickers": [{
"filename": "happy_face",
"description": "Happy Face",
"premium": "false",
"categories": ["blue", "green", "red"],
"genders": ["male", "female"]
},{
"filename": "sad_face",
"description": "Sad Face",
"premium": "false",
"categories": ["blue", "green", "red", "yellow"],
"genders": ["male"]
}]
}
Stickers will have the same filename, but will be separated into folders according to their category and gender.
I can read the JSON data just fine. My problem is when I'm trying to make some use of the JSON data.
My goal is to separate the stickers according to their categories, which could vary depending on the sticker, the user will later be able to switch categories, and the correct stickers will be displayed.
let stickerPack = StickerPack.load()
let allStickers = stickerPack!["stickers"] as? [[AnyHashable : Any]]
func getStickersWithCategory(category: String){
var stickers = [AnyObject]()
for sticker in allStickers! {
let cat = sticker["categories"] as? [String]
for item in cat! {
if item.contains(category){
stickers.append(sticker)
}
}
}
}
The result of this is
"Argument type '[AnyHashable : Any]' does not conform to expected type 'AnyObject'"
Can anyone point me in the right direction? Is it possible with this JSON structure? or is it better to have a different structure, with each category and gender separated? this would lead to a lot of repetition. But maybe I'm creating more problems by trying to keep the JSON structure this way.
All help appreciated!

Be more type specific, this avoids that kind of errors.
This JSON array is [[String:Any]] and never AnyHashable because the keys are required to be string
let allStickers = stickerPack!["stickers"] as? [[String:Any]]
stickers is the same type and never something including AnyObject. All JSON types are value types (Any).
var stickers = [[String:Any]]()
You can filter the array swiftier and it's recommended to safely unwrap all optionals
if let stickerPack = StickerPack.load(),
let allStickers = stickerPack["stickers"] as? [[String:Any]] {
stickers = allStickers.filter({ (sticker) -> Bool in
guard let categories = sticker["categories"] as? [String] else { return false }
return categories.contains(category)
})
...

The issue is that you declared allStickers as let allStickers = stickerPack!["stickers"] as? [[AnyHashable : Any]], which means that its type will be [[AnyHashable:Any]]? or Array<Dictionary<AnyHashable:Any>>. When you are iterating through that array in for sticker in allStickers!, the type of sticker will be Dictionary<AnyHashable:Any>. Dictionary is a struct, so it doesn't conform to AnyObject, and hence you cannot add sticker to stickers, which is declared as an array of AnyObjects.
So changing var stickers = [AnyObject]() to var stickers = [Any](), or more specifically var stickers = [[AnyHashable:Any]]() should solve your issue.
func getStickersWithCategory(category: String){
var stickers = [[AnyHashable:Any]]()
for sticker in allStickers! {
let cat = sticker["categories"] as? [String]
for item in cat! {
if item.contains(category){
stickers.append(sticker)
}
}
}
}
Btw you are encouraged to use Codable in Swift 4 when handling JSON.

Related

Swift, get data from [String: Any] type value

I have a value like this;
["id1": {
name = "iphone 6s";
price = 330;
}, "id2": {
name = iphone7s;
price = 500;
}]
I dont know what the id's are. But I need to get the name and the price data from that data.
EDIT: Here is the getting value code;
database.child("SortedItems").child("byName").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value as? [String: Any] else {
return
}
Assuming your data structure matches what you have quoted at the top, you should be able to cast your first line as:
guard let data = snapshot.value as? [String:[String:Any]]
If not, t would be helpful to see what the console looks like if you print snapshot.value
Then, the following from my previous answer should work:
let ids = data.keys //get just the ids/keys
//iterate through and print the name and price once they're cast to the correct types
data.forEach { (key,value) in
if let name = value["name"] as? String, let price = value["price"] as? Int {
print(name)
print(price)
}
}
//declare a model type that conforms to Codable
struct Phone : Codable {
var name : String
var price : Int
}
data.forEach { (key, value) in
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
//decode the Phone model
let phoneModel = try JSONDecoder().decode(Phone.self, from: jsonData)
print(phoneModel)
} catch {
//handle error
}
}
You may also want to look at the Firebase docs, which give some more detail about getting your data, including converting to custom objects: https://firebase.google.com/docs/firestore/query-data/get-data

Accessing Array outside of the loop [duplicate]

This question already has an answer here:
Assign value of a Firestore document to a variable
(1 answer)
Closed 2 years ago.
I have an Array where some different UserID's are stored.
Each UserID is connected with their corresponding data in Firestore.
So now I want to fetch all the JSON Data from the UserID's and make them accessible to decode them later.
Therefore the fetched Data (coming as a Dictionary) from each user must be accessible separately.
I tried it with that way:
var fetchedIDs = ["ID1", "ID2", "ID3"]
var finalArray = [[String: Any]]()
for id in fetchedIDs{
let docRef = Firestore.firestore().collection("Data").document(id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let myDict: [String: Any] = document.data() as! [String: Any]
finalArray.append(myDict)
}
}
}
}
But the problem is that the finalArray (like finalArray[0]) is just accessible in the For-Loop.
It should be accessible outside of the loop and look like that:
finalArray[0] should have the Dictionary data from ID1
finalArray[1] should have the Dictionary data from ID2
Maybe I am thinking to complicated for that..
Can someone help me out ?
Is there generally a good source for learning about scopes and how the data should be accessed like in that scenario?
Thanks!
Finally get it working with the following code:
var fetchedIDs = ["ID1", "ID2", "ID3"]
func loadData(com:#escaping( ([[String: Any]]) -> ())){
var myArray = [[String: Any]]()
for id in fetchedIDs{
let refLike = db.collection("Data").document(id)
refLike.getDocument { (document, error) in
if let err = error {
print("Error getting documents: \(err)")
} else {
let myDict: [String: Any] = document?.data() as! [String: Any]
myArray.append(myDict)
}
com(myArray)
}
}
}
loadData(){ arr in
if (arr.count == fetchedIDs.count){
print ("User 1 has the following data: \(arr[0])")
print ("User 2 has the following data: \(arr[1])")
}
}

How to Call URL in API Nested Array

I'm new to REST API and am having some trouble. I'm trying to call data from an array that's nested within a dictionary. While I've been able to pull data from other variables fine, this is giving me some problems. For reference, the URL I need is labeled "front default," inside the "sprites" array. Furthermore, the URL is that of an image, and I am using Alamofire Image to display images. I will include the code I've used thus far to call other variables, along with a screenshot of the API's structure.
Screenshot of API hierarchy
import Foundation
import Alamofire
import AlamofireImage
import SwiftyJSON
import Siesta
class PokeInfo {
var name: String
var id: Int
var abilities: [String]
var types: [String]
// converts dictionary into string
init?(jsonDictionary: [String: Any]) {
//guard used to check if value entered in search bar matches a name in API data
guard let name = jsonDictionary["name"] as? String,
let id = jsonDictionary["id"] as? Int,
//abilities data nested. First must be set to string
let abilities = jsonDictionary["abilities"] as? [[String: Any]],
let types = jsonDictionary["types"] as? [[String: Any]]
else {
return nil
};
var abilityNames: [String] = []
var typeNames: [String] = []
//targets nested data, going inside dictionary to find attributes of that element
for abilityDictionary in abilities {
guard let ability = abilityDictionary["ability"] as? [String: Any], let name = ability["name"] as? String else { break }
abilityNames.append(name)
};
for typeDictionary in types {
guard let type = typeDictionary["type"] as? [String: Any], let name = type["name"] as? String else { break }
typeNames.append(name)
}
self.name = name
self.id = id
self.abilities = abilityNames
self.types = typeNames
}
}
As of Swift 4 you should be using Codable for JSON which is now part of the language. Here is a Playground example:
import PlaygroundSupport
import UIKit
class PokeInfo: Codable {
var name: String
var id: Int
var abilities: [String]
var types: [String]
}
let pokeString = """
{
"name": "test",
"id": 1,
"abilities": ["a", "b", "c"],
"types": ["x", "y", "z"]
}
"""
let pokeData = pokeString.data(using: .utf8)!
let decoder = JSONDecoder()
let pokeInfo = try! decoder.decode(PokeInfo.self, from: pokeData)
print(pokeInfo.abilities)
the output is:
["a", "b", "c"]

Append to array in [String: Any] dictionary structure

Assembling a data payload passed to GRMustache.swift for rendering mustache templates, I'm in a scenario where I need to append data to an array previously defined in the dictionary.
My data structure starts off as:
var data: [String: Any] = [
"key1": "example value 1",
"key2": "example value 2",
"items": [
// I need to append here later
]
]
The items key pair is a collection I need to append later within a loop.
To add to the data["items"] array, I'm trying something like:
for index in 1...3 {
let item: [String: Any] = [
"key": "new value"
]
data["items"].append(item)
}
This errors, as value of type Any? has no member append, and binary operator += cannot be applied to operands of type Any? and [String : Any].
This makes sense, as I need to cast the value to append; however, I can't mutate the array.
Casting to array, whether forcing downcast gives the error:
(data["items"] as! Array).append(item)
'Any?' is not convertible to 'Array<_>'; did you mean to use 'as!' to force downcast?
Cannot use mutating member on immutable value of type 'Array<_>'
Seems like my cast is wrong; or, perhaps I'm going about this in the wrong way.
Any recommendation on how to fill data["items"] iteratively over time?
The type of data[Items] isn't Array but actually Array<[String: Any]>.
You could probably squeeze this into fewer steps, but I prefer the clarity of multiple steps:
var data: [String: Any] = [
"key1": "example value 1",
"key2": "example value 2",
"items": []
]
for index in 1...3 {
let item: [String: Any] = [
"key": "new value"
]
// get existing items, or create new array if doesn't exist
var existingItems = data["items"] as? [[String: Any]] ?? [[String: Any]]()
// append the item
existingItems.append(item)
// replace back into `data`
data["items"] = existingItems
}
If you prefer brevity over clarity, you can perform this operation in a single line, making use of the nil coalescing operator and the + operator for RangeReplaceableCollection (to which Array conforms), the latter used for the "append" step (in fact constructing a new collection which will the replace the existing one when replacing the value of data["items"]).
// example setup
var data: [String: Any] = [
"key1": "example value 1",
"key2": "example value 2",
"items": []
]
// copy-mutate-replace the "items" array inline, adding a new dictionary
data["items"] = (data["items"] as? [[String: Any]] ?? []) + [["key": "new value"]]
print(data)
/* ["key2": "example value 2",
"items": [["key": "new value"]],
"key1": "example value 1"] */
// add another dictionary to the "items" array
data["items"] = (data["items"] as? [[String: Any]] ?? []) + [["key": "new value"]]
print(data)
/* ["key2": "example value 2",
"items": [["key": "new value"], ["key": "new value"]],
"key1": "example value 1"] */
subscript with Dictionary always return optional(?) instance because it may possible that key is not exist in Dictionary. Now you have declare your dictionary as [String:Any] means type of key is String and value is Any.
Now when you write data["item"] it will return you Any? that is not Array so you can not called append and it will return immutable value so you can't directly mutate it.
So you need to type cast it to Array of Dictionary and stored it to instance and append that instance after that replace that object in your dictionary using reassigning it.
for index in 1...3 {
let item: [String: Any] = [
"key": "new value"
]
var array = data["items"] as? [[String:Any]] ?? [[String:Any]]()
array.append(item)
data["items"] = array
}
Now thing is got lot easier if you have Dictionary like [String: [[String:Any]]] for eg.
var data: [String: [[String:Any]]] = [
"items": []
]
for index in 1...3 {
let item: [String: Any] = [
"key": "new value"
]
data["items"]?.append(item)
}
I'm not entirely sure if this works on the [String: Any] type of dictionary but I also highly recommend you create a struct instead of using "Any" for your dictionary. It'll help run things a lot smoother.
On the other hand, I wanted to mention, you can try something like:
yourDictionary["Key", default: []].append(item)
This will initialize an array for that "Key" that will be used in the dictionary if there isn't already an array initialized. Without this, you'd have to check and initialize the array yourself which ends up being some repetitive code.
Hope this helps.
Give you have an Array of items:[String]
He can also add this to the json
var items = ["string1", "Info 2","Text 4"]
data["items"] = items

How we can find an element from [AnyObject] type array in swift

I have [AnyObject] array
var updatedPos = [AnyObject]()
I am setting data in that according to my requirement like!
let para:NSMutableDictionary = NSMutableDictionary()
para.setValue(posId, forKey: "id")
para.setValue(posName, forKey: "job")
let jsonData = try! NSJSONSerialization.dataWithJSONObject(para, options: NSJSONWritingOptions())
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding) as! String
self.updatedPos.append(jsonString)
Now in my code i have some requirement to remove the object from this array where id getting matched according to requirement Here is the code which i am trying to implement
for var i = 0; i < updatedPos.count; i++
{
let posItem = updatedPos[i]
print("Id=\(posItem)")
let pId = posItem["id"] as? String
print("secRId=\(pId)")
if removeId! == pId!
{
updatedPos.removeAtIndex(i)
}
}
Here print("Id=\(posItem)") give me output asId={"id":"51","job":"Programmer"} but here i am not able to access id from this object. here print("secRId=\(pId)") give me nil
First of all use native Swift collection types.
Second of all use types as specific as possible.
For example your [AnyObject] array can be also declared as an array of dictionaries [[String:AnyObject]]
var updatedPos = [[String:AnyObject]]()
Now create the dictionaries and add them to the array (in your example the dictionary is actually [String:String] but I keep the AnyObject values).
let para1 : [String:AnyObject] = ["id" : "51", "job" : "Programmer"]
let para2 : [String:AnyObject] = ["id" : "12", "job" : "Designer"]
updatedPos.append(para1)
updatedPos.append(para2)
If you want to remove an item by id use the filter function
let removeId = "12"
updatedPos = updatedPos.filter { $0["id"] as? String != removeId }
or alternatively
if let indexToDelete = updatedPos.indexOf{ $0["id"] as? String == removeId} {
updatedPos.removeAtIndex(indexToDelete)
}
The JSON serialization is not needed for the code you provided.
PS: Never write valueForKey: and setValue:forKey: unless you know exactly what it's doing.
After some little bit stuffs I have found the very easy and best solution for my question. And I want to do special thanks to #vadian. Because he teach me new thing here. Hey Thank you very much #vadian
Finally the answer is I had covert posItem in json Format for finding the id from Id={"id":"51","job":"Programmer"} this string
And the way is
let data = posItem.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
if let dict = json as? [String: AnyObject] {
let id = dict["id"]
if removeId! == id! as! String
{
updatedLoc.removeAtIndex(i)
}
}
}
catch {
print(error)
}

Resources