Swift as overload - swift

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.

Related

Error: "Expected to decode Dictionary<String, Any> but found an array instead." — but I haven't defined a dictionary? [duplicate]

This question already has answers here:
Decoding Error -- Expected to decode Dictionary<String, Any> but found an array instead
(2 answers)
Closed 1 year ago.
I'm working on a creative project, and I'm trying to decode content from an API database using Swift's JSONDecoder() function. I've built my structs, a getData() function, and I've set up a do-try-catch for the JSONDecoder() function. I'm having difficulty understanding what I'm doing to get the error I'm getting.
Here are my structs:
struct Response: Codable {
let foundRecipes: [Recipe]
let foundIngredients: [Ingredient]
}
struct Recipe: Codable {
let id: Int
let title: String
let image: String
let imageType: String
let usedIngredientCount: Int
let missedIngredientCount: Int
let missedIngredients: [Ingredient]
let usedIngredients: [Ingredient]
let unusedIngredients: [Ingredient]
let likes: Int
}
struct Ingredient: Codable {
let id: Int
let amount: Int
let unit: String
let unitLong: String
let unitShort: String
let aisle: String
let name: String
let original: String
let originalString: String
let origianalName: String
let metaInformation: [String]
let meta: [String]
let image: String
}
Here's my getData() function:
func getData(from url: String) {
URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong.")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("")
print(String(describing: error)) // Right here is where the error hits.
}
guard let json = result else {
return
}
print(json.foundRecipes)
}).resume()
}
Here's a link to the API's documentation. The URL I'm calling in getData() links to the same structure of search as shown in their example: https://spoonacular.com/food-api/docs#Search-Recipes-by-Ingredients — and here's a screenshot of the url results for the exact search I'm working on: https://imgur.com/a/K3Rn9SZ
And finally, here's the full error that I'm catching:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
My understanding of this error is that it's saying I told the JSONDecoder() to look for a Dictionary of <String, Any>, but it's at the link and only seeing an array. I'm confused, because I don't know where it thinks I'm providing a dictionary. Where am I screwing up? Not looking for specific code changes, just some guidance on what I'm missing.
Thanks in advance :)
As you can see in your image of the API data and in the API documentation you linked to, the API is returning an array (in the documentation, for example, you can see that it is surrounded by [...]). In fact, it looks like the API returns an array of Recipe.
So, you can change your decoding call to this:
var result: [Recipe]?
do {
result = try JSONDecoder().decode([Recipe].self, from: data)
print(result)
} catch {
print(error)
}
Perhaps your idea for Response came from somewhere else, but the keys foundRecipes or foundIngredients don't show up in this particular API call.
Also, thanks to #workingdog's for a useful comment about changing amount to a Double instead of an Int in your model.

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)
}
}

Casting from Any to anything else fails

API gives me back a variable that has type Any. It looks like this when I print it.
{
"sender" : "Kira",
"created" : "08.05.2018",
"text" : "Cncncm"
}
I tried to use SwiftyJSON to cast it like this let mydata = JSON(data) but it failes. I tried to use Swift 4 decoding technique but that failed as well. I tried to do this let myData = data as? Dictionary<String, String> but it fails again.
I am clueless what to do here. Any tips or solutions?
Finally a chance to demonstrate one of the Codable protocols hidden gems. Please run the following in a Playground:
import Cocoa
let jsonData = """
{
"sender" : "Kira",
"created" : "08.05.2018",
"text" : "Cncncm"
}
""".data(using: .utf8)!
struct SenderText: Codable {
let sender: String
let created: Date
let text: String
}
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "dd.MM.yyyy"
let date = dayFormatter.date(from:"08.05.2018")
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dayFormatter)
do {
let sendText = try decoder.decode(SenderText.self, from: jsonData)
print(sendText)
} catch {
print(error)
}
The sheer elegance of how easy it is to define such an intricate parser mapping a messy JSON-string to your favourite struct will hardly ever stop to amaze me. No matter how weird your date format looks, it is hardly more than 3 lines away from being parsed during the process.
There is something in regard to casting you should note though: In Swift, as in most object oriented languages, you can only cast something to something else if (and only if) it already is something else in the first place (but that knowledge has been lost somewhere). Since your String is "just" a String (in disguise of an Any maybe) you won't be able to cast it to anything else. However the Codable protocol provides you with a terrific means to decode from the Strings Data with astonishing ease. This process should not be mistaken as a cast, even if it looks largely the same. It is the creation and initialisation of another, more fittingly structured object from a simple piece of Data that you are likely to have gotten from your average web service of choice.
Great so far, at least in my book.
You can parse it like this as it's a json string
let trd = yourVar as? String
if let data = trd?.data(using: String.Encoding.utf8) {
do {
var content = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:String]
print(content)
}
catch let error as NSError {
print(error)
}
}

How to use an asynchronous Swift Firebase function?

So basically I have a function that connects to Firebase and gets data in the form of a string and returns a string(at least I think).
this question Is very simple and kind of a dumb question but how would I call this method in a different thread or core. Sorry don't really know the terms yet. I am trying.
also, I don't think it returns a String which I need so how would I accomplish that?
also this is not a duplicate because if you look at all these type of questions I have not found one that calls the actual method.
typealias someting = (String?) -> Void
func getOpposingUsername( _ index: Int, completionHandler: #escaping someting) {
var opposingUser: String = ""
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: AnyHashable] else {
return
}
opposingUser = dict["OpposingUsername"] as! String
if opposingUser.isEmpty {
completionHandler(nil)
} else {
completionHandler(opposingUser)
}
})
}
Changing threads would be something like this DispatchQueue.main.async {your code} here is the documentation on GCD Dispatch.
Here is an example of a function that returns a string,
func stringReturn() -> String {
let aString = "a string"
return aString
}

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

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.