How can I save a dictionary to Firebase in swift? - 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.

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 to set value to deep depth of Dictionary?

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

Type Any has no subscript members while adding an array to [String: Any] in Swift3

In my Swift3 code I have an array:
var eventParams =
[ "fields" :
[ "photo_url":uploadedPhotoURL,
"video_url":uploadedVideoURL
]
]
Later on I want to add another array to this array, I thought I could just do:
eventParams["fields"]["user_location"] = [
"type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]
]
but I'm getting error here:
Type Any? has no subscript members
How can I add that array to my previously declared array fields?
Since your dictionary is declared as [String : Any], the compiler doesn't know that the value for "fields" is actually a dictionary itself. It just knows that it's Any. One very simple way to do what you're trying is like this:
(eventParams["fields"] as? [String : Any])?["user_location"] = [
"type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]
]
This will just do nothing if eventParams["fields"] is nil, or if it's not actually [String : Any].
You can also do this in a couple steps to allow for troubleshooting later on like this:
//Get a reference to the "fields" dictionary, or create a new one if there's nothig there
var fields = eventParams["fields"] as? [String : Any] ?? [String : Any]()
//Add the "user_location" value to the fields dictionary
fields["user_location"] = ["type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]]
//Reassign the new fields dictionary with user location added
eventParams["fields"] = fields

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.

Adding additional items to dictionary

Would like to add John together with Peter in this combination:
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = "John"
println(myData0)
Println gives only John back instead Peter AND John. Is there a way to add John without overwriting Peter?
If you want to keep a string value of dictionary, you can do like that
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = myData0["DrinksMilk"]! + ", John"
println(myData0)
If not, you can change the type of dictionary value to AnyObject like that :
var myData0: [String: AnyObject] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = [myData0["DrinksMilk"]!, "John"]
println(myData0)
It appears that you are attempting to encode a data type into a dictionary. A better approach, not knowing anything more about your specific problem, is to define a class for this:
class Drink {
var name:String
var desc:String
var drinkers:[Person]
// ...
func addDrinker (person:Person) {
drinkers.append(person)
}
}
You've declared your dictionary to hold values of type String but, as you describe, now you want the dictionary values to be String or Array. The common type of these type value types is Any (it can be AnyObject if you've imported Foundation; but that is a Swift anomaly). You will then need to 'fight' the type system to coerce the "DrinksMilk" field as a modifiable array and to add "John".
// First extract `"DrinksMilk"` and force it to a mutating array:
var drinkers:[String] = myData0["DrinksMilk"]! as [String]
// Add the new drinker
drinkers.append("John")
// Reassign back into data
myData0["DrinksMilk"] = drinkers