In Swift Convert [String: Any!] to URLComponents - ios

I'm having a dictionary [String: Any!] that has values like integers, floats and strings. When I'm creating URLComponents using below code, its not taking values related to integers and floats.
func queryItems(dictionary: [String: Any]) -> [URLQueryItem] {
return dictionary.map {
URLQueryItem(name: $0, value: $1 as? String)
}
}

I think you should consider using [String:String] instead of [String:Any] and convert your values to String one step before sending it to queryItems function.But if you want to leave it like this than casting from value Int,Float,Double with as? String always fails.
you can use String().

Obviously Any could be anything. But if you have some control of your inputs one easy option is casting to LosslessStringConvertible. String, Substring and your basic numeric types all conform to it.
Warnings:
Bool returns true or false.
NSObject does not conform to this, so if you're mixing in NSNumber or NSString you need to account for those also.
func queryItems(dictionary: [String: Any]) -> [URLQueryItem] {
dictionary.map {
URLQueryItem(name: $0, value: ($1 as? LosslessStringConvertible)?.description)
}
}

Related

i'm trying to change the value in one dictionary by comparing the key from another dictionary

var clariQuestion: [String: String] = ["0": "What are my last 10 transactions",
"1": "What are my spending transactions",
"2": "What are my largest trasactions",
"3": "What are my pending tracnstions"]
var explanationActionParameters: [String: Any] = ["clariQuestion": 2]
So i need to match the value of explanationActionParameters with the key of clariQuestion, and then when the keys match replace the number 2 in explanationActionParameters with "what are my largest transactions". What's causing me difficulty is that the value in explanationActionParameter is of type 2 and the key in clariQuestion is of type String. Not to sure how to go about this.
You can either convert your int to string, using String(2) or convert your string to int, like Int("2")
My answer to you would be to transform your clariQuestion dictionary into an array, because the keys are integers, and an array can be seen as a dictionary where the keys are integers. So, you could do
var clariQuestion: [String] = ["What are my last 10 transactions",
"What are my spending transactions",
"What are my largest trasactions",
"What are my pending tracnstions"]
var explanationActionParameters: [String: Any] = ["clariQuestion": 2]
let questionIndex = explanationActionParameters["clariQuestion"]
let question = clariQuestion[questionIndex]
If you insist on maintaining this dictionary structure, you can flatmap your dictionaries and transform all keys from string to int, like this
clariQuestion.flatMap { (key, value) in [Int(key) : value] }
or transform all your values to a string, like this
explanationActionParameters.flatMap { (key, value) in [key : String(value)] }
Either way, now you should be able to access elements across those dictionaries. I'm going to create a function demonstrating how
func replace(_ clariQuestion: [String : String], _ explanationActionParameters: [String: Any]) -> [String: String] {
// The dictionary that will be returned containing the mapping from clariQuestion to explanationActionParameters
var ret = [String : String]()
let mappedClariQuestion = clariQuestion.flatMap { (key, value) in [Int(key) : value] }
for (key, value) in clariQuestion {
ret[key] = mappedClariQuestion[value]
}
return ret
}
Then, just explanationActionParameters = replace(clariQuestion, explanationActionParameters)

Using flatMap with dictionaries produces tuples?

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]]

Using JSONSerialization() to dynamically figure out boolean values

I am getting a JSON string from the server (or file).
I want to parse that JSON string and dynamically figure out each of the value types.
However, when it comes to boolean values, JSONSerialization just converts the value to 0 or 1, and the code can't differentiate whether "0" is a Double, Int, or Bool.
I want to recognize whether the value is a Bool without explicitly knowing that a specific key corresponds to a Bool value. What am I doing wrong, or what could I do differently?
// What currently is happening:
let jsonString = "{\"boolean_key\" : true}"
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String:Any]
json["boolean_key"] is Double // true
json["boolean_key"] is Int // true
json["boolean_key"] is Bool // true
// What I would like to happen is below (the issue doesn't happen if I don't use JSONSerialization):
let customJson: [String:Any] = [
"boolean_key" : true
]
customJson["boolean_key"] is Double // false
customJson["boolean_key"] is Int // false
customJson["boolean_key"] is Bool // true
Related:
How do I get NSJSONSerialization to output a boolean as true or false?
Is there a correct way to determine that an NSNumber is derived from a Bool using Swift?
This confusion is a result of the "feature" of all the wonderful magic built into the Swift<->Objective-C bridge. Specifically, the is and as keywords don't behave the way you'd expect, because the JSONSerialization object, being actually written in Objective-C, is storing these numbers not as Swift Ints, Doubles, or Bools, but instead as NSNumber objects, and the bridge just magically makes is and as convert NSNumbers to any Swift numeric types that they can be converted to. So that is why is gives you true for every NSNumber type.
Fortunately, we can get around this by casting the number value to NSNumber instead, thus avoiding the bridge. From there, we run into more bridging shenanigans, because NSNumber is toll-free bridged to CFBoolean for Booleans, and CFNumber for most other things. So if we jump through all the hoops to get down to the CF level, we can do things like:
if let num = json["boolean_key"] as? NSNumber {
switch CFGetTypeID(num as CFTypeRef) {
case CFBooleanGetTypeID():
print("Boolean")
case CFNumberGetTypeID():
switch CFNumberGetType(num as CFNumber) {
case .sInt8Type:
print("Int8")
case .sInt16Type:
print("Int16")
case .sInt32Type:
print("Int32")
case .sInt64Type:
print("Int64")
case .doubleType:
print("Double")
default:
print("some other num type")
}
default:
print("Something else")
}
}
When you use JSONSerialization, any Bool values (true or false) get converted to NSNumber instances which is why the use of is Double, is Int, and is Bool all return true since NSNumber can be converted to all of those types.
You also get an NSNumber instance for actual numbers in the JSON.
But the good news is that in reality, you actually get special internal subclasses of NSNumber. The boolean values actually give you __NSCFBoolean while actual numbers give you __NSCFNumber. Of course you don't actually want to check for those internal types.
Here is a fuller example showing the above plus a workable solution to check for an actual boolean versus a "normal" number.
let jsonString = "{\"boolean_key\" : true, \"int_key\" : 1}"
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String:Any]
print(type(of: json["boolean_key"]!)) // __NSCFBoolean
json["boolean_key"] is Double // true
json["boolean_key"] is Int // true
json["boolean_key"] is Bool // true
print(type(of: json["int_key"]!)) // __NSCFNumber
json["int_key"] is Double // true
json["int_key"] is Int // true
json["int_key"] is Bool // true
print(type(of: json["boolean_key"]!) == type(of: NSNumber(value: true))) // true
print(type(of: json["boolean_key"]!) == type(of: NSNumber(value: 1))) // false
print(type(of: json["int_key"]!) == type(of: NSNumber(value: 0))) // true
print(type(of: json["int_key"]!) == type(of: NSNumber(value: true))) // false
Because JSONSerialization converts each of the values to an NSNumber, this can be achieved by trying to figure out what each NSNumber instance is underneath: https://stackoverflow.com/a/30223989/826435
let jsonString = "{ \"boolean_key\" : true, \"integer_key\" : 1 }"
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String:Any]
extension NSNumber {
var isBool: Bool {
return type(of: self) == type(of: NSNumber(booleanLiteral: true))
}
}
(json["boolean_key"] as! NSNumber).isBool // true
(json["integer_key"] as! NSNumber).isBool // false
(Note: I already got similar [better] answers as I was typing this up, but I figured to leave my answer for anyone else looking at different approaches)

Merge two Dictionaries

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

How to convert between Key and String in Swift?

I'm making an extension on Dictionary, just a convenience method to traverse a deep json structure to find a given dictionary that might be present. In the general extension of a Dictionary, i'm not able to subscript because i give a String instead of a Key
extension Dictionary {
func openingHoursDictionary() -> Dictionary<String,AnyObject>? {
if let openingHours = self["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
}
Error: String is not convertible to DictionaryIndex<Key, Value>
on self["openingHours"]
How can i make a Key from the String "openingHours" or check the dictionary for ths string?
You can check at runtime if the string is a valid key for the dictionary:
extension Dictionary {
func openingHoursDictionary() -> [String : AnyObject]? {
if let key = "openingHours" as? Key {
if let openingHours = self[key] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
}
return nil
}
}
But this will "silently" return nil if called for other dictionaries
like [Int, AnyObject].
If you want the compiler to check if it is safe to subscript the
dictionary with a string then you have to use a (generic) function:
func openingHoursDictionary<T>(dict : [String : T]) -> [String : AnyObject]? {
if let openingHours = dict["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
It is (currently) not possible to write Dictionary (or Array)
extension methods that apply only to a restricted type of the
generic parameters.
Just cast the String as Key using "openingHours" as Key
if let pickupPoints = self["openingHours" as Key] as? Array<AnyObject> {
}
Downside is that if doing this i will get a crash if i have a Dictionary<Int,AnyObject> and use the method there.
0x10e8c037d: leaq 0x362aa(%rip), %rax ; "Swift dynamic cast failure"

Resources