How to set value to deep depth of Dictionary? - swift

I use Swift.
I have a dictionary variable.
The dictionary looks like JSON structure.
So, key:value type is [String: AnyObject].
I want to set a value in deep depth of the dictionary.
var dic1: [String: AnyObject] = [String: AnyObject]()
And, if print dic1, show below.
{
"a-1": 1,
"a-2": "a-2-1,
"a-3": [
{
"b-1": "hi",
"b-2": 10,
"b-3": [
{
"c-1": 100
},
{
"c-1": 101,
"c-2": "aaa"
},
]
},
{
"b-1": "hello",
"b-2": 20,
"b-3": [
{
"c-1": 200
},
{
"c-1": 201,
"c-2": "bbb"
}
]
// <- want to set to here
}
]
}
Then, I want to set some value to key "b-4".
I tried like below but, xcode throws me "Cannot assign to immutable expression of type 'AnyObject'" error.
((dic1["a-3"] as? [[String: AnyObject]])?[1] as? [String: AnyObject])?["b-4"] = blahblah as AnyObject

The error message says, you're trying to change an immutable object, which is not possible. Because in Swift, a Dictionary is a value type.
First get the object you want to to modify to a variable, and assign back after modification. Smth like below:
var a3 = (dic1["a-3"] as? [[String: AnyObject]])
a3?[1]["b-4"] = blahblah as AnyObject
dic1["a-3"] = a3 as AnyObject

Related

How to add dictionary element stored in constant directly to dictionary literal in Swift?

I'm adding a specific dictionary element to multiple dictionaries, therefore I'd like to store this element in a constant so I can easily reuse it.
I like to do something like this:
let reusableElement: Dictionary<String, String>.Element = ("reusableKey", "reusableValue")
let dictUsingTheReusableElement: [String: Any] = ["name": "john",
"age": 15,
"legend": true,
reusableElement]
Which results the following error Expected ':' in dictionary literal.
Is there a way to directly insert the element in a dictionary literal? Or is it only possible to add this by doing something like this:
let dictUsingTheReusableElement: [String: Any] = ["name": "john",
"age": 15,
"legend": true,
reusableElement.0: reusableElement.1]
I guess the difficult part with a dictionary literal is what it would do if the element's key was already in the literal. You can see if you implement something like this:
let element: Dictionary<String, String>.Element = ("rK", "rV")
extension Dictionary where Key == String, Value == Any {
func with(_ element: Element) -> Self {
merging([element.0: element.1], uniquingKeysWith: { a, _ in a})
}
}
let dictUsingTheReusableElement: [String: Any] = ["name": "john",
"age": 15,
"legend": true]
.with(element)
You need to specify what to do with conflicts with that uniquingKeys(with:
And in fact if you do
let reusableElement: Dictionary<String, String>.Element = ("name", "foo")
let dictUsingTheReusableElement: [String: Any] = ["name": "john",
"age": 15,
"legend": true,
reusableElement.0: reusableElement.1]
You're going to have a runtime issue (Swift/Dictionary.swift:826: Fatal error: Dictionary literal contains duplicate keys), so this is maybe why it's not a thing.

How can I save a dictionary to Firebase in swift?

I have a dictionary of type [Int: Int] that I am trying to save to firebase. My function for writing to firebase looks like this:
func writeToFirebase() {
setScoreToBadge()
setScoreToVehicle()
if GameManager.instance.getUsername() != "" {
self.ref?.child("user").child(
GameManager.instance.getUsername()
).setValue(
[
"email": GameManager.instance.getEmail(),
"userNameLowered": GameManager.instance.getUsername().lowercased(),
"userName": GameManager.instance.getUsername(),
"topScore": GameManager.instance.getTopScores()[0],
"topScores": GameManager.instance.getTopScores(),
"totalCash": GameManager.instance.getTotalCash(),
"friends": GameManager.instance.getFriendsAdded(),
"achievements": GameManager.instance.getAchievementsCompletedBool(),
"badge": GameManager.instance.getBadgeLevel(),
"scoreBadgeDictionary" : GameManager.instance.getTopScoreWithBadgeDictionary(),
"scoreVehicleDictionary" : GameManager.instance.getTopScoreWithVehicleDictionary()
])
} else {
print ("Not signed in, score not saved to firebase")
}
}
The issue I'm having is with scoreBadgeDictionary and , scoreVehiceDictionary. GameManager.instance.getTopScoreWithBadgeDictionary() is a dictionary of type [Int: Int] and GameManager.instance.getTopScoreWithVehicleDictionary() is a dictionary of type [Int: String]. If I try to run this function it will crash.
Is there a way to convert the dictionary to a format that I can save to firebase?
This issue is how the array is defined. A simple example
let myArray = ["some_key": "some_value"]
is defined as an array of [String: String]
and
let myArray = ["some_key": 5]
is defined as an array of [String: Int]
Your array is
[
"email": GameManager.instance.getEmail(),
"userNameLowered": GameManager.instance.getUsername().lowercased(),
.
.
"scoreBadgeDictionary" : ["some key": "some value"]
]
where, I assume GameManager.instance.getTopScoreWithBadgeDictionary() returns a dictionary.
So that array doesn't conform to [String: String] or [String: Int]. However it does conform to a more generic [String: Any] so that's what you need to 'tell' the var.
let myArray: [String: Any] = [
"email": "Hello",
"userNameLowered": "hello",
"scoreBadgeDictionary" : [1: "some score"],
"scoreVehicleDictionary" : [2: "some vehicle"]
]
However, and this is the important bit, the dictionaries returned in getTopScoreWithVehicleDictionary which is a [Int: String] or [Int: Int] is not going to work with Firebase.
Firebase keys must be strings so attempting to write this [1: "Hello, World] will crash. Noting that arrays in Firebase have numeric indexes and
if the data looks like an array, Firebase will render it as an array.
I would suggest rethinking that structure - possibly cast the Int's to a string would be an option but that may not work for your use case.

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

Dictionary in swift

let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}

Updating a nested value in an NSDictionary

I've initialized a dictionary of type [NSObject: AnyObject] so I can save it into NSUserDefaults.
Here's what it looks like:
var allMetadata: [NSObject: AnyObject] = [
String: [String: String]
// Example: "project30": ["deliverablepath": "hello"]
]
I give deliverablepath a value from the very beginning, and later on I want to update it. I've tried this:
allMetadata[arrayOfProjectIDs[index]]!["deliverablepath"]! = "goodbye"
But I get the error
Operand of postfix '!' should have optional type; type is '(NSObject,
AnyObject)'
I know about updateValue(), but it seems to overwrite adjacent keys in the first nested layer, so it's not working for me.
Any ideas?
Use question optional to avoid "let pyramid"
var allMetadata: [String: [String: String]] = ["a": ["b": "c"]]
allMetadata["a"]?["b"] = "z" // ok!
allMetadata["q"]?["b"] = "d" // nil
UPD:
If you want to cast directly, you should try this:
var allMetadata: [NSObject: AnyObject] = ["a": ["b": "c"]]
if var dict = allMetadata["a"] as? [String: String] {
dict["b"] = "z"
// for dict update, because it's value typed
allMetadata["a"] = dict
}
Mention, that I've written "var", not "let" in condition.
To do this in a safe way, it is best to do this in an if let pyramid as follows:
if let projectId = arrayOfProjectIDs[index] {
if var project = allMetadata[projectId] as? [String:String] {
project["deliverablePath"] = "Goodbye"
}
}
That is not too bad actually.
I want to give an alternative answer here.
I understand the original question is about how to deal with nested arrays and dictionaries, but I think it is worth mentioning that this kind of data model may be better implemented with a more formal API.
For example, how about this:
class Project {
var id: String
var deliverablePath: String
... etc ...
}
class ProjectRepository {
func getProjectWithId(id: String) -> Project? {
...
}
}
Then you can use high level code like:
if let project = repository.getProjectWithId("") {
project.deliverablePath = "Goodbye"
}
Underneath you can still implement this with dictionaries and arrays of course.