I have a dictionary like this. It's a set of parameters to be set in a HTTP body.
let parameters = [ "UserCredentials": [ "Personalnumber": personalNo, "Password": password ], "DeviceCredentials": ["UniqueId": uniqueId] ]
The type inference output of the parameters variable is [String : Dictionary<String, String>].
What I want to do is instead of creating this dictionary explicitly, I want to build it. As in have functions to create each section of it and finally merge them all to have the final output as the original one shown above.
func deviceCredentials() -> [String: [String: String]] {
return ["DeviceCredentials" : ["UniqueId": "1212121212"]]
}
func userCredentials(pnr: String, password: String) -> [String: [[String: String]]] {
let personalNo = ["Personalnumber": pnr]
let password = ["Password": password]
return ["UserCredentials": [personalNo, password]]
}
I have two separate methods to create each section.
I don't know how to merge the output from these two because their output types are different.
You'll need to create the merger function yourself that will loop through both dictionaries and combine them into one. I think the reason Apple wouldn't provide that function is what if there's a conflict (same key is present in both dictionaries)?
For what you want to do, I think it may be easier to create an NSMutableDictionary and then pass it as a parameter to both functions so that each of them can add the objects it needs to add.
Because your inner-most dictionaries vary in type, you will need to use AnyObject when you define your outermost dictionary and downcast when you retrieve the items as necessary. You can also return a tuple instead of a Dictionary to make it easier to build your outermost dictionary -
func deviceCredentials() -> (key:String, value:[String: String]) {
return ("DeviceCredentials",["UniqueId": "1212121212"])
}
func userCredentials(pnr: String, password: String) -> (key:String, value:[[String: String]]) {
let personalNo = ["Personalnumber": pnr]
let password = ["Password": password]
return ("UserCredentials", [personalNo, password])
}
var dict=Dictionary<String,AnyObject>()
let deviceCreds=deviceCredentials()
dict[deviceCreds.key]=deviceCreds.value
let userCreds=userCredentials("user", "password")
dict[userCreds.key]=userCreds.value
However, you can change the structure of your userCredentials value slightly and use -
func deviceCredentials() -> (key:String, value:[String: String]) {
return ("DeviceCredentials",["UniqueId": "1212121212"])
}
func userCredentials(pnr: String, password: String) -> (key:String, value:[String: String]) {
var retDict=Dictionary<String,String>()
retDict["Personalnumber"] = pnr
retDict["Password"]= password
return ("UserCredentials", retDict)
}
var dict=Dictionary<String,Dictionary<String,String>>()
let deviceCreds=deviceCredentials()
dict[deviceCreds.key]=deviceCreds.value
let userCreds=userCredentials("user", "password")
dict[userCreds.key]=userCreds.value
Related
I have created typealias:
typealias Multilang = [String: Any]
Now I would like to extend that Dictionary with property localized. Is it possible?
How do I need to use it?
let dictionary: Multilang = ["en": "b", "pl": "d"]()
let value = dictionary.localized
I try to do it like this:
extension Dictionary where Dictionary: Multilang {
var localized: String {
print("self: \(self)")
return "hello"
}
}
but it doesn't work. Why?
Type 'Dictionary(Key, Value)' constrained to non-protocol, non-class type 'Multilang' (aka 'Dictionary(String, Any)')
You can write an extension like this:
extension Dictionary where Key == String, Value == Any {
var localized: String {
print("self: \(self)")
return "hello"
}
}
or:
extension Dictionary where Dictionary == Multilang
It is (unfortunately) not possible to use typealias in this way, because it does not create a new type. For example, you can have typealias Foo = [String: Any] and typealias Bar = [String: Any], but Foo and Bar still both the same type.
So either you have to extend all [String: Any] dictionaries, or you need to create a wrapper type (you also cannot subclass Dictionary because it is a struct). For example:
struct Multilang<Element> {
var dictionary: [String: Element]
var localized: Element? {
return dictionary[currentLanguage]
}
}
The downside is you need to define all the wrapper methods and properties that you want to use through multilang.foo syntax, but if it suffices to have just multilang.localized, you can do all the rest through multilang.dictionary.foo.
You can also implement ExpressibleByDictionaryLiteral to make creating these objects easy:
extension Multilang: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, Element)...) {
dictionary = .init(elements) { $1 }
}
}
let multilang: Multilang = [ "en": "English", "pl": "Polish" ]
For Swift 3 and above you can try this method to extend a dictionary of type [String: Any].
extension Dictionary where Key: ExpressibleByStringLiteral, Value: Any {
// Your extension code
var localized: String {
print("self: \(self)")
return "hello"
}
ExpressibleByStringLiteral is a protocol that String and StaticString types conform to.
You cannot apply it just to the type alias, because Multilang is just a label for [String:Any], so even if you could extend it, it would extend all dictionaries of this type anyway.
I am new to Swift and have a requirement to store a database of key value pairs. The key value pairs are a name with a corresponding 4 digit number in database that remains in memory after the app is excited. I am thinking to use a dictionary with the name as the key and the 4 digit numbers as the value. These are then stored in the iPad flash memory using the user defaults class.
Below is the code that I’ve currently developed. The code that adds to the database compiles ok but the code that checks the name and number for a match in the database won't compile due to the following message (Value of optional type '[Any]?' not unwrapped; did you mean to use '!' or '?'?) which is because of this line of code (if let databaseCheck = database[name]). Ive obviously tried unwrapping but can't seem to shake the error message.
Anyone got any ideas whats causing the error or any issues with the approach?
public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
var foundInDatabaseFlag: Bool = false
let database = UserDefaults.standard.array(forKey: "Database")
if let databaseCheck = database[name]
{
if (databaseCheck == number)
{
foundInDatabaseFlag = true
}
}
return foundInDatabaseFlag
}
public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry: [String: String] = [:]
newEntry[name] = number
UserDefaults.standard.set(newEntry, forKey: "Database")
}
There is a major mistake. You save a dictionary but retrieve an array.
Apart from that a dictionary retrieved from UserDefaults is [String:Any] by default, you have to conditional downcast the object.
The code checks if there is a dictionary in UserDefaults and if there is the requested key in one expression
public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
guard let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String],
let databaseCheck = database[name] else { return false }
return databaseCheck == number
}
Another mistake is that you are always overwriting the entire dictionary in UserDefaults. If you want to save multiple key-value pairs you have to read the dictionary first.
public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry : [String: String]
if let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String] {
newEntry = database
} else {
newEntry = [:]
}
newEntry[name] = number
UserDefaults.standard.set(newEntry, forKey: "Database")
}
Side note: The parameter labels are highly recommended in Swift for better readability.
I have this pretty basic and straight forward mapping of an object into dictionary. I am using and parsing dictionary on the top level. One of its fields is an array of other dictionaries. To set them I use flatMap which seems an appropriate method but since the object inside is not nullable this method suddenly returns tuples (at least it seems that way) instead of dictionaries.
I created a minimum example that can be pasted into a new project pretty much anywhere any of your execution occurs which should give better detail then any description:
func testFlatMap() -> Data? {
class MyObject {
let a: String
let b: Int
init(a: String, b: Int) { self.a = a; self.b = b }
func dictionary() -> [String: Any] {
var dictionary: [String: Any] = [String: Any]()
dictionary["a"] = a
dictionary["b"] = b
return dictionary
}
}
let objects: [MyObject] = [
MyObject(a: "first", b: 1),
MyObject(a: "second", b: 2),
MyObject(a: "third", b: 3)
]
var dictionary: [String: Any] = [String: Any]()
dictionary["objects"] = objects.flatMap { $0.dictionary() }
dictionary["objects2"] = objects.map { $0.dictionary() }
print("Type of first array: " + String(describing: type(of: dictionary["objects"]!)))
print("Type of first element: " + String(describing: type(of: (dictionary["objects"] as! [Any]).first!)))
print("Type of second array: " + String(describing: type(of: dictionary["objects2"]!)))
print("Type of second element: " + String(describing: type(of: (dictionary["objects2"] as! [Any]).first!)))
return try? JSONSerialization.data(withJSONObject: dictionary, options: [])
}
_ = testFlatMap()
So this code crashes saying
'NSInvalidArgumentException', reason: 'Invalid type in JSON write
(_SwiftValue)'
(Using do-catch makes no difference which is the first WTH but let's leave that for now)
So let's look at what the log said:
Type of first array: Array<(key: String, value: Any)>
Type of first element: (key: String, value: Any)
Type of second array: Array<Dictionary<String, Any>>
Type of second element: Dictionary<String, Any>
The second one is what we expect but the first just has tuples in there. Is this natural, intentional?
Before you go all out on "why use flatMap for non-optional values" let me explain that func dictionary() -> [String: Any] used to be func dictionary() -> [String: Any]? because it skipped items that were missing some data.
So only adding that little ? at the end of that method will change the output to:
Type of first array: Array<Dictionary<String, Any>>
Type of first element: Dictionary<String, Any>
Type of second array: Array<Optional<Dictionary<String, Any>>>
Type of second element: Optional<Dictionary<String, Any>>
which means the first solution is the correct one. And on change there are no warnings, no nothing. The app will suddenly just start crashing.
The question at the end is obviously "How to avoid this?" without too much work. Using dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]] seems to do the trick to preserve one-liner. But using this seems very silly.
I see that your question boils down to map vs. flatMap. map is the more consistent one and it works as expected here so I won't delve into it.
Now on to flatMap: your problem is one of the reasons that SE-0187 was proposed. flatMap has 3 overloads:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
When your dictionary() function returns a non-optional, it uses the first overload. Since a dictionary is a sequence of key-value tuples, an array of tuple is what you get.
When your dictionary() function returns an optional, it uses the third overload, which essentially filters out the nils. The situation can be very confusing so SE-0187 was proposed (and accepted) to rename this overload to compactMap.
Starting from Swift 4.1 (Xcode 9.3, currently in beta), you can use compactMap:
// Assuming dictionary() returns [String: Any]?
dictionary["objects"] = objects.compactMap { $0.dictionary() }
For Swift < 4.1, the solution you provided is the only one that works, because it gives the compiler a hint to go with the third overload:
dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]]
All, I am having some trouble with a Firebase driven app I am working on. The issue came about after upgrading my xCode to version 8. I have done some extensive research on this and gone through a few Stackoverflow topics trying to solve this, as it seems to be a very common error. I've gathered it is probably something super obvious that I am missing, excuse my stupidity.
This is the error I am receiving.
Cannot store object of type _SwiftValue at name. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
This is my section of code it's occuring in I believe.
func saveUser(withUID uid: String, username: String, value: [String: String?], completion: #escaping (Error?) -> Void) {
let values = ["/users/\(uid)": value, "/usernames/\(username)": uid] as [String: Any]
root.updateChildValues(values) { (error, _) in
completion(error)
}
}
func updateUser(_ newValue: [String: String?]) {
currentUser.updateChildValues(newValue)
}
// MARK: - Posts
func createPost(_ _post: [String: String]) {
let userUID = Auth.shared.currentUserUID
let key = posts.childByAutoId().key
var post = _post
post["userUID"] = userUID
// get current user's username
currentUser.child("username").observeSingleEvent(of: .value, with: { snapshot in
if let username = snapshot.value as? String {
post["username"] = username
let values = [
"/posts/\(key)": post,
"/user-posts/\(userUID)/\(key)": post
]
self.root.updateChildValues(values)
}
})
}
I got the same error in my code as well, just figured that out. Make sure values is type of [String : String] instead of [String : String?].
I am working inside a Swift Extension. I am trying to append data to an array of the type [[String: AnyObject]]. The reason that this is in an extension is because I have to do this lot's of times to lot's of arrays. The problem is, when I append an object of type: [String: AnyObject], I get the error: Dictionary'<'String, AnyObject'>' Not Convertible to T (the quotes are there because within the carrots nothing showed up).
mutating func appendData(data: [String: [String: AnyObject]]?) {
if data != nil {
for (id, object) in data! {
var mutatingObject = object
mutatingObject["id"] = id
append(mutatingObject)
}
}
}
I am not certain what exactly are you trying to achieve. but take a note - Arrays are generic collections that store specific type. Extension for Array might not know what type will be used in each case, so it cannot simply allow you to store Dictionary<String, AnyObject>.
Here is an example on how to make your code more generic:
extension Array {
mutating func appendData(data: [String: T]?) {
if data != nil {
for (id, object) in data! {
if var mutatingObject = object as? [String : AnyObject] {
mutatingObject["id"] = id
}
append(object)
}
}
}
}