Handling Swift Dicts: fatal error: can't unsafeBitCast between types of different sizes - swift

I have a function in Swift that needs to be able to handle multiple types. Specifically, it needs to be able to parse both Dicts and Strings.
The problem I have is the Dicts could be several types, depending on their origin. So I could be provided with [String:Any] or [String:String] (coming from Swift) or [String:AnyObject] (coming from objc). The top level parsing function takes Any, which it then tests for specific types and attempts to parse them.
At first I just tried testing for if let dict = object as? [String:Any], but if I passed in another type [String:AnyObject] or [String:String] it failed. So I tried testing each type:
func parseLink(object: Any) {
if let dict = object as? [String:Any] {
return self.parseDict(dict)
} else if let dict = object as? [String:AnyObject] {
return self.parseDict(dict)
} else if let dict = object as? [String:String] {
return self.parseDict(dict)
} else if let string = object as? String {
return parseURL(string)
}
}
func parseDict(dict: [String:Any]) { ..... }
So I've created some Unit Tests to test the behavior:
func testDictTypes() {
let testDict: [String:Any] = [ "orgId" : "123456789" ]
let link = OrgContextLinkParser().parseLink(testDict)
XCTAssertNotNil(link)
let testDict1: [String:AnyObject] = [ "orgId" : "123456789" ]
let link2 = OrgContextLinkParser().parseLink(testDict1)
XCTAssertNotNil(link2)
let testDict3: [String:String] = [ "orgId" : "123456789" ]
let link3 = OrgContextLinkParser().parseLink(testDict3)
XCTAssertNotNil(link3)
}
This all compiles fine, but I get a fatal runtime error if a [String:AnyObject] is passed in. This is troubling since Swift's type system is supposed to prevent these kind of errors and I get no warning or errors thrown when I compile.
I also really don't want to duplicate the exact same logic multiple times just to handle different dict types. I.E., handling [String:Any], [String:AnyObject] and [String:String] have virtually the exact same logic.
The only possible solution I've seen is to actually duplicate the dictionary, which seems rather expensive (Convert [String: AnyObject] to [String: Any]). For performance reasons, it seems better to just copy paste the code and change the function signatures... but really!? That's seems excessive.
The best solution seems to be to parse a Dict as [String:AnyObject] and copy the value only if it's [String:Any]:
if let dict = object as? [String:Any] {
var objDict: [String:AnyObject] = [:]
for (key, value) in dict {
if let obj = value as? AnyObject {
objDict[key] = obj
}
}
return self.parseDict(objDict)
I don't particularly like this, but so far it's be best I've been able to come up with.
Does anyone have any idea how to handle this properly? I'm especially concerned that I can cast Any as [String:AnyObject], pass it to a function that takes [String:Any] and I get no compiler errors, even though it crashes at runtime.

Related

Manually Parsing a a JSON into an Object of a Struct

I'm a beginner in swift and I'm currently making an app that makes a web request. I've been trying to parse this JSON Data but the nested data is just really hard to wrap my head around:
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
]
JSon Serialization Code
let jsonAny = try JSONSerialization.jsonObject(with: data, options: [])
guard let json = jsonAny as? [String: Any] else { return }
This is my attempt to manually parse the JSON Data
private func parsePokemonManual(json: [String: Any]) -> Pokemon {
let abilities = json["abilities"] as? [String: Any] ?? [String: Any]()
return Pokemon(abilities: abilities)
}
}
These are the structs that I made to hold the data.
struct Abilities {
let ability : Ability
struct Ability {
let name : String
}
}
How do I successfully parse the JSON Data into an object of Pokemon structure?
With this code so fat I am getting the error "Cannot convert the value of type '[String : Any]' to expected argument type '[Abilities]'. My problem is that I don't know what type to cast the abilities as and that my struct 'Abilities' is also incorrect.
There are 3 problems with your attempt although one might argue there is only 1, that you should use Codable instead but lets stay with JSONSerialization here. The problems are
You are reading the json wrong and should cast not to a dictionary but an array of dictionaries when accessing "abilities"
Your struct is to complicated, maybe because of the previous problem
Lastly, you can't cast into a custom type, you need to convert or map the data into your type by telling exactly what values to use and how because the compiler doesn't understand how to do it.
First the struct can be simplified to
struct Ability {
let name : String
}
And the rest is fixed in the function parsePokemonManual. First get "abilities" and cast to an array of dictionaries. Then map each item in the array by getting "ability" and casting it to a dictionary that is used to get the "name" value that is then used when creating an instance of Ability
private func parsePokemonManual(json: [String: Any]) -> [Ability] {
guard let abilities = json["abilities"] as? [[String: Any]] else {
return []
}
return abilities.compactMap { dict in
guard let ability = dict["ability"] as? [String: String], let name = ability["name"] else { return nil }
return Ability(name: name)
}
}

swift3 How to remove Optional [duplicate]

This question already has answers here:
Printing optional variable
(15 answers)
Cannot get rid of Optional() string
(5 answers)
Closed 5 years ago.
this is my code:
func uplouadPost() {
// shortcut to data to be php
let parseJSON = UserDefaults.standard.value(forKey: "parseJSON") as?
NSDictionary
let userID = parseJSON!["userID"] as! String
....
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!,
options: .mutableContainers) as? NSDictionary
print("========================\(userID)")
print I get ========================Optional(23)
But I don't want Optioanl
how to just get 23
What's more, I tried to unwrap the "userID" by this way. However it doesn't work
let unwrappedUserID = [userID]
print (unwrappedUserID)
Thank you guys
The best method of checking for and unwrapping Optionals is using either a guard statement or if-let statements.
So suppose you have a dictionary defined as:
let parseJson: [String: Any]? = ["userId": 23]
Even though it has a value, it is still an Optional type so to access the values in the dictionary we want, we need to check for the possibility of it having a nil value, assuming we didn't create it and know that it has a real value.
Using if-let statements, we can do:
if let json = parseJson {
// WILL ONLY EXECUTE IF parseJson IS NOT nil
// json is now of type [String: Any] instead of [String: Any]?
let userId = json["userId"]
print("=========\(userId)") // =========23
}
This creates a new scope where the json constant now contains the non-nil and unwrapped optional value of parseJson. In that case if parseJson did equal nil, then the code inside of the if-let block would not execute.
The other option is a guard statement, which is very similar.
guard let json = parseJson else {
// ONLY EXECUTES IF parseJson IS nil
// MUST EXIT CURRENT SCOPE AS A RESULT
return // or throw NSError()
}
let userId = json["userId"]
print("==========\(userId)") // ==========23
In this case, the code after the guard will only execute if parseJson is non-nil because the inside of the guard block must exit the scope.
Try this, let unwrappedUserID = userID!

How to use variables outside of .observe

I currently have the code below. This successfully pulls the information from firebase and then appends it to the array of dictionaries. My question is how do I use this dictionary outside of .observe? I understand the issue of async vs sync with firebase, but I haven't found a good explanation on how I can use this data in another function.
override func viewDidLoad() {
FIRDatabase.database().reference().child("vendors").observe(.value, with: { (snapshot) in
let spot = snapshot.value as! [String : AnyObject]
for each in spot{
let name2 = each.value["name"] as! String
let booth2 = each.value["boothNum"] as! String
let desc2 = each.value["description"] as! String
self.name = name2
self.booth = booth2
self.desc = desc2
let dict1: [String: String] = ["name" : name2, "booth" : booth2, "desc" : desc2]
self.vendorDict.append(dict1)
dump(self.vendorDict)
}
})
this successfully dumps the info I need in the .observe but if I dump the dictionary outside it says it is empty.
Your best option is to call needed function at the end of the closure, but outside of the 'for' loop. This way you will have the dictionary populated, and still be on the same thread.
Other options are - adding a completion block and creating a protocol (can't elaborate more on these two since not comfortable with them myself).

Swift as overload

I am creating simple Json Parser that works like that: I have JsonData class that contains Anyobject as data. When I use jsonData["key"] it returns JsonData to i can chain jsonData["key"]["key2"] etc.
My question is how can I implement that class so i could cast it to lets say String:
jsonData["key"] as String without using some workarouds like
jsonData["key"].data as String
Code:
class JsonData:CustomStringConvertible{
let data:AnyObject
var description: String{
get{
return "\(data)"
}
}
init(_ data: Data) {
self.data = try! JSONSerialization.jsonObject(with: data, options: []) as! [[String:AnyObject]]
}
init(_ data: AnyObject) {
self.data = data
}
subscript(key:String) -> JsonData{
let newData = data as! [String:AnyObject]
let test = newData[key]!
return JsonData(test)
}
subscript(index:Int) ->JsonData{
let newData = data[index]!
return JsonData(newData)
}
}
In order to do this, you'd add another overload, but it won't work like you're thinking.
subscript(key: String) -> String {
let newData = data as! [String:AnyObject]
return newData[key] as! String
}
So then jsonData["key"] as String works, but jsonData["key"]["key2"] is ambiguous and you'd have to write it (jsonData["key"] as JsonData)["key2"] which probably isn't what you want.
The short answer to this is don't do this. If you need this much access to JSON, you're probably storing your data incorrectly. Parse it to structs as quickly as you can, and then work with structs. Convert the structs back to JSON when you want that. Extensive work with AnyObject is going to break your brain and the compiler over and over again. AnyObject is a necessary evil, not an every day tool. Soon you will encounter that terrible day that you have an AnyObject? and the compiler just breaks down in tears. Well, at least it isn't Any.
Putting that aside, the better solution is to use labeled-subscripts.
subscript(string key: String) -> String {
let newData = data as! [String:AnyObject]
return newData[key] as! String
}
Now you can access that as json[string: "key"] rather than json["key"] as String.

Swift: How to convert string to dictionary

I have a dictionary which i convert to a string to store it in a database.
var Dictionary =
[
"Example 1" : "1",
"Example 2" : "2",
"Example 3" : "3"
]
And i use the
Dictionary.description
to get the string.
I can store this in a database perfectly but when i read it back, obviously its a string.
"[Example 2: 2, Example 3: 3, Example 1: 1]"
I want to convert it back to i can assess it like
Dictionary["Example 2"]
How do i go about doing that?
Thanks
What the description text is isn't guaranteed to be stable across SDK versions so I wouldn't rely on it.
Your best bet is to use JSON as the intermediate format with NSJSONSerialization. Convert from dictionary to JSON string and back.
I created a static function in a string helper class which you can then call.
static func convertStringToDictionary(json: String) -> [String: AnyObject]? {
if let data = json.dataUsingEncoding(NSUTF8StringEncoding) {
var error: NSError?
let json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? [String: AnyObject]
if let error = error {
println(error)
}
return json
}
return nil
}
Then you can call it like this
if let dict = StringHelper.convertStringToDictionary(string) {
//do something with dict
}
this is exactly what I am doing right now. Considering #gregheo saying "description.text is not guaranteed to be stable across SKD version" description.text could change in format-writing so its not very wise to rely on.
I believe this is the standard of doing it
let data = your dictionary
let thisJSON = try NSJSONSerialization.dataWithJSONObject(data, options: .PrettyPrinted)
let datastring:String = String(data: thisJSON, encoding: NSUTF8StringEncoding)!
you can save the datastring to coredata.