How to add value to dictionary without replacing it? - swift

I'm trying to append values inside dictionary without replacing it for example:
var dict = [String:Any]()
dict["login"] = ["user" : "jon"]
//...
//...
//...
dict["login"] = ["password": "1234"]
When I'm trying to add a value to login at the second time , it's overwrite the first value.
How can I append this data?
I'm using Swift 3.
Edit: let me rephrase my question a little bit.
I want to build a dynamic dictionary which I'll post it to alamoFire as body parameters. so if I have an JSON that looks like this:
{
"name" : "Jon",
"details" : {
"occupation" : "lifeguard"
"years_of_ex" : 3
}
"more_details" : "extra info"
"inner_body" : {
"someInfo" : "extra info"
}
... // there might be lots of other fields since it's dynamic
... // the server expect to have missing fields and not empty ones
}
I want to add dynamically details since I don't know how my Dictionary would looks like.
so adding to values to dictionary without override them is a must for me.

Define an intermediate variable and assign to it:
var dict = [String:Any]()
dict["login"] = ["user" : "jon"]
if var login = dict["login"] as? [String: String] {
login["password"] = "1234"
dict["login"] = login
}

To reflect for the edit in the question: without knowing the exact structure of the dictionary, you cannot modify it as you wish. Anyways, modifying a JSON dictionary directly is really bad practice.
Parse the JSON response into a custom object, modify the object as you wish, then encode the object back to JSON and use it as your request's body.
If you don't want to write the JSON parsing function yourself, have a look at ObjectMapper. It can both map a JSON response to objects and can also map an object to JSON.

you cannot directly "append" new values to a dinamic dictionary without knowing the type and content.
Since
dict["login"]
returns you a Any? you have no way to directly manipulate it.
Do you have any ideas of the possible combinations of the content of each dictionary leaf?
you could write a recursive methods that tries to downcast the content of the leaf and base on it, try to do something
switch dict["login"] {
case is [String: Any?]:
// check that the key does not already exist, to not replace it
case is [Int: Any?]:
// Do something else
}

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

What Data Structure should I use for this particular case?

I have a dictionary, something like this
var dict = [String : [String]]()
The functionality I want to achieve is that, I have a hashtable which I can quick get the list of data from.
In my code, I use a dictionary and an array.
I am not very good with algorithem and data structure, so I am wondering if there is any better data structure that is suitable for something like this?
Use:
var dict = [String : [String]]()
Swift already has built in search algorithms that allow you you to retrieve data inside of your dictionary with simple subscript syntax like so
dict["element"]
You will use it in this way -
Declaration:
var dict: [String: [String]] = [:]
Initialise:
dict["element"] = myArray

What is the difference between initializing a dictionary and declaring in Swift?

Not sure if my title is accurate. Please comment if so will update.
In one method I just create the dictionary and initialize it with ()
In the other I create it and immediately fill it with a key value pair.
What is the difference? Is one prefered over the other?
//initializing dictionary
var airPortCodesInitialize = [Int: String]()
//vs declaring
var airPortCodes: [String: String] = ["SLC": "Salt Lake City", "LAX": "Los Angeles"]
In both cases, you are declaring a dictionary and initializing it. The only difference is that the first line creates an empty dictionary, the second line creates a dictionary filled with key value pairs.
A single declaration looks like this:
var myDictionary: [String: String]
I think you misunderstood what declarations are, so for the rest of this answer, I will compare the line above and your first line.
What is the difference?
A single declaration gives the variable no value, so if you try to use myDictionary immediately after the declaration without initializing it, the compiler gives you an error:
print(myDictionary["Hello"]) // error
Is one prefered over the other?
Most of the time, you should put the initialisation and declaration on the same line like you did in
var airPortCodesInitialize = [Int: String]()
This is more readable.
Sometimes though, you might want different initial values for a constant dictionary depending on a value. Then you must separate the declaration and initialisation:
let myConstantDict: [String: String]
switch something {
case .foo:
myConstantDict = ...
case .bar:
myConstantDict = ...
}

Setter for dictionary property - OR: get last added item from dictionary

I have a custom class with different computed properties. One of them is a Dictionary of [String: String]. The getter is no problem, but I don't know how to use the setter: How can I figure out, what was the last value added to the dictionary? Obviously newValue.last doesn't exists (.first does!).
EDIT:
This seems to work:
var myProp: [String: String] {
get { ... }
set {
let lastVal = newValue[newValue.startIndex.advancedBy(newValue.count-1)]
...
}
BUT: will this always return the last added value?
EDIT 2
The first edit is wrong. A dictionary is unordered and with this way it's not sure, if it really returns the last added key and value. See my answer below.
As you point out, a Dictionary is an unorderd collection of key-value pairs, so there is no last getter (first is just a convenience for what in Objective-C was more appropriately called anyObject) . The Dictionary also does not keep track of the order items were added.
To get the last item, there are two possibilities. You could refactor to use an array, e.g. of tuples (key, value); or you could keep track of the last item added in a separate variable.
But maybe there is a misunderstanding about the "setter". A setter sets the entire object.
set { myProp = newValue }
So if you have a myProp = ["foo": "bar"], the entire dictionary in myProp is overwritten with this data.
What you want is to add a key to the property. In Swift, this is done by subscripting.
myProp["foo"] = "bar"
You do not have to implement anything special in the get closure.
Note that you have to remember two things, though: first, the dictionary has to be properly initialized; second, any existing item will be overwritten if the new value uses the identical key.
I understand now... the dictionary is unordered. To really get the last added value, I have to compare the value itself with the newValue. The working code:
var myProp: [String: String] {
get { // doing things to read the things and add them to a dictionary }
set {
var new = newValue
for (key, value) in myProp {
if new[key] == value {
new.removeValueForKey(key)
}
}
// now 'new' should only have one key and one value, that one, that just was added
}
}

Can't assign String to AnyObject?

Ok, this is a weird one. I'm looping through an array of what apparently is a Dictionary<String, AnyObject?!> (notice the ?!, it's not a typo). I'm trying to assign a String to one of its keys, but it's not working and I get the weird error below.
Has anyone seen anything like this?
Edit:
Some extra info: even the debugger says it's an AnyObject?!:
You cannot apply the Dictionary syntax on an Array.
Try this:
if var dict = fields as? [String: String]{
dict["test"] = "test"
}