Currently struggling how to use Decodable. I've done some googling to the errors I'm getting but I still believe that the way i'm structuring the structs isn't correct but it seems to make sense to me.
I've also tried using optionals
In the error that I've posted at the end, I'm confused about the reference to the Double type. As I don't have any type or anything int he response that uses a double.
(I'm also able to serialize the json reponse using the old swift method of casting the data as dictionaries - [String : Any]. But I'd like to use the modern/updated approach.)
JSON Response
{"NEWS":
[
{
"DATE":"2018-10-13T03:56:06+1000",
"SOURCE":"smh.com.au",
"BLURB":"Assistant Treasurer Stuart Robert says he has repaid $37,975 of \"excess usage charges\" in home internet bills footed by taxpayers.",
"ID":102347,
"TITLE":"Stuart Robert pays back $38,000 in excessive home internet charges"
},
{
"DATE":"2018-10-12T18:00:38+1000",
"SOURCE":"itwire.com",
"BLURB":"The CVC costs set by the NBN Co make it very difficult for ISPs to offer gigabit connections to more than a select band of customers who are willing to sign up in numbers and pay slightly more than other speed tiers, according to one ISP who caters to this type of consumer.",
"ID":102343,
"TITLE":"NBN gigabit connections will remain mostly a pipe dream"},
{
"DATE":"2018-10-12T09:48:43+1000",
"SOURCE":"computerworld.com.au",
"BLURB":"The Department of Home Affairs has rejects calls to include independent judicial oversight of the decision to issue Technical Assistance Notices and Technical Capability Notices as part of proposed legislation intended to tackle police agencies’ inability to access encrypted communications services.",
"ID":102342,
"TITLE":"Home Affairs rejects calls for additional safeguards in ‘spyware’ law"
},
{
"DATE":"2018-10-11T12:16:05+1000",
"SOURCE":"itnews.com.au",
"BLURB":"NBN Co is hoping to “speed up” building works on the fibre-to-the-curb (FTTC) portion of its network as it tries to make up lost ground.",
"ID":102334,
"TITLE":"NBN Co says fibre-to-the-curb build is more complex that it hoped"
},
]
}
CODE
struct Root: Decodable {
let news: [News]?
enum CodingKeys: String, CodingKey {
case news = "NEWS"
}
}
struct News: Decodable {
let date: Date
let source, blurb: String
let id: Int
let title: String
enum CodingKeys: String, CodingKey {
case date = "DATE"
case source = "SOURCE"
case blurb = "BLURB"
case id = "ID"
case title = "TITLE"
}
}
Serialization
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let dataStr = data else {
return
}
do {
let root = try JSONDecoder().decode(Root.self, from: dataStr) //error is caught here
guard let r = root else { return }
print(r.news)
} catch let err {
print("JSON Error - \(err)")
}
}.resume()
Error
error serializing json typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "NEWS", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "DATE", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
This is because the default coding strategy for Date is double (seconds since epoch). You can change the default strategy to iso8061 or anything custom. For example, you can set the date formatter in your decoder like so:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
Putting the JSON into Quicktype gives me an extra comma error. Unless there's more nodes to the json file? So make sure about that first. At first glance, I could be wrong, but I think you have to format the DATE to exactly match.
Related
I'm trying to use the BingAPI in Swift which has no guide or directions. I'm so close but I can't figure out what type is webpages (
_type and query context are in the correct format, but I don't know how to write webPages.)
error code:
"typeMismatch(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "webPages", intValue: nil), _JSONKey(stringValue: "value", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, String> but found an array instead.", underlyingError: nil))"
Swift
struct codableData: Codable {
var _type: String
var queryContext: [String : String]
var webPages : [String : [String : String]] // I know it's not right, but here is the problem
}
json results
{
"_type": "SearchResponse",
"queryContext": {
"originalQuery": ""
},
"webPages": {
"totalEstimatedMatches": 20600000,
"value": [
{
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.8",
"name": "tafeqld.edu.au",
"url": "https://tafeqld.edu.au/courses/18106/",
"isFamilyFriendly": true,
"displayUrl": "https://tafeqld.edu.au/courses/18106",
"snippet": "Moved Permanently. The document has moved here.",
"dateLastCrawled": "2023-01-02T12:02:00.0000000Z",
"language": "en",
"isNavigational": false
}
],
"someResultsRemoved": true
},
"rankingResponse": {
"mainline": {
"items": [
{
"answerType": "WebPages",
"resultIndex": 0,
"value": {
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.0"
}
}
]
}
}
}
webPages is not [String: [String: String]] as the value types inside it include numbers as well as other objects which are not simple [String: String] dictionaries either. Like the error is telling you, value is an array and you're trying to decode it as a dictionary.
You could simply change it to [String: Any].
But you'll also benefit from Codable more if you write types matching the structure of the expected JSON.
For example:
struct CodableData: Codable {
let _type: String
let queryContext: QueryContext
let webPages: WebPage
}
struct QueryContext: Codable {
let originalQuery: String
}
struct WebPage: Codable {
let totalEstimatedMatches: Int
let value: [Foo]
let someResultsRemoved: Bool
}
// etc.
Note you only need to define objects and properties for the bits you're interested in.
Notice that the JSON Syntax indicates an Object when it s {} and an Array when it is [].
JSON has a JavaScript origin and stores types from the JavaScript world. JavaScript is not strongly typed and you can have dictionaries and arrays with mixed types.
So in JavaScript to access the WebPages name for example you would do something like BingAPIResponse.webPages.value[0].name and you can do exactly the same in Swift too however you will have to model your Codable struct to match this exact structure with sub-structures because in JavaScript you don't have a guarantee that the array in webPages.value will have all the same types and there is no guarantee that webPages.value[0].name is a string, for example.
You won't be able to use [String : Any] because Any is not decodable and you will get an error if you put values of type Any in you Codable Struct.
This is not a shortcoming of Swift but a result of trying to work with a data structure which doesn't have types in a language with strict types.
So if you want to decode it just using JSONDecoder, you will need to create a Codable struct for each sub object the way #shim described.
Personally I find it too tedious and I built myself a small library to handle JSON, which you can find here: https://github.com/mrtksn/DirectJSON
What this library does is letting you access parts of the JSON before converting them into the type you need. So the type safety is still guaranteed but you can access the data from the JSON using the JavaScript notation without modelling the whole JSON object. It is useful if you are not interested in having the complete model of the JSON object but just want some data from it.
So for example, to access the name of the webpage, you will do:
// add the library to the imports
import DirectJSON
/* Do yourAPI and retrieve the json */
let theJSONResponseString = BingAPICall()
/* Get the part of the data you are interested in */
let name : String? = theJSONResponseString.json.webPages.value[0].name
Note that you can use it with any Codable. So if you are after webPages data, you can have something like:
struct WebPages : Codable {
let id : String
let name : String
let url : String
let isFamilyFriendly : Bool
}
// then simply
let webPages : [WebPages]? = theJSONResponseString.json.webPages.value
I m using this code to call my rest web service.
But if I try to decode the result of web service call I received error.
class func callPostServiceReturnJson(apiUrl urlString: String, parameters params : [String: AnyObject]?, parentViewController parentVC: UIViewController, successBlock success : #escaping ( _ responseData : AnyObject, _ message: String) -> Void, failureBlock failure: #escaping (_ error: Error) -> Void) {
if Utility.checkNetworkConnectivityWithDisplayAlert(isShowAlert: true) {
var strMainUrl:String! = urlString + "?"
for dicd in params! {
strMainUrl.append("\(dicd.key)=\(dicd.value)&")
}
print("Print Rest API : \(strMainUrl ?? "")")
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request(urlString, method: .get, parameters: params)
.responseJSON {
response in
switch (response.result) {
case .success:
do{
let users = try JSONDecoder().decode(OrderStore.self, from: response.result.value! as! Data)
}catch{
print("errore durante la decodifica dei dati: \(error)")
}
if((response.result.value) != nil) {
success(response as AnyObject, "Successfull")
}
break
case .failure(let error):
print(error)
if error._code == NSURLErrorTimedOut {
//HANDLE TIMEOUT HERE
print(error.localizedDescription)
failure(error)
} else {
print("\n\nAuth request failed with error:\n \(error)")
failure(error)
}
break
}
}
} else {
parentVC.hideProgressBar();
Utility.showAlertMessage(withTitle: EMPTY_STRING, message: NETWORK_ERROR_MSG, delegate: nil, parentViewController: parentVC)
}
}
This is the error that I can print:
Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
2021-09-27 16:34:49.810245+0200 ArrivaArrivaStore[15017:380373] Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
CoreSimulator 732.18.6 - Device: iPhone 8 (6F09ED5B-8607-4E47-8E2E-A89243B9BA90) - Runtime: iOS 14.4 (18D46) - DeviceType: iPhone 8
I generated OrderStore.swift class from https://app.quicktype.io/
//EDIT
.responseJSON returns deserialized JSON, in this case a Dictionary. It cannot be cast to Data what the error clearly confirms.
To get the raw data you have to specify .responseData
Replace
.responseJSON {
response in
switch (response.result) {
case .success:
do {
let users = try JSONDecoder().decode(OrderStore.self, from: response.result.value! as! Data)
with
.responseData {
response in
switch response.result {
case .success(let data):
do {
let users = try JSONDecoder().decode(OrderStore.self, from: data)
Consider that AF 5 supports even .responseDecodable to decode directly into the model
.responseDecodable {
(response : DataResponse<OrderStore,AFError>) in
switch response.result {
case .success(let users): print(users)
Side notes:
As mentioned in your previous question there is no AnyObject in the AF API. The parameters are [String:Any] and responseData is the decoded type. I recommend to make the function generic and use the convenient Result type.
Delete the break statements. This is Swift.
This is an addendum to Vadian's answer. I'm trying to illustrate the process that lead you into this error, with the hopes that you can notice it in the future, before it leads you astray
This is a pretty common "pattern" of error.
Picture it as though you're traversing a maze, starting from some initial data format, and trying to get to some destination data format. At each point along the way, there are several options to choose from, some which get you closer to your goal, and some which lead you further away.
You've chosen to enter the maze at the entryway called responseJSON, whose callback will give you a AFDownloadResponse<Any> (which is the inferred type of the variable you called response).
JSON structures always have an array or dictionary at the top level. Since Alamofire can't statically know which kind of JSON you'll be dealing with, it models this with an Any. At runtime, the type of the Value will be either NSDictionary (or one of its concrete subclasses, like __NSDictionaryI) or NSArray (or one of its concrete subclasses).
You then decide to get the result of that response. Its static type is Result<Any, Error>. You switch over this error, ensuring you're dealing with the success case and not the failure case. Inexplicably, you ignore the payload value associated with the success, but later force unwrap it out with result.response.value!.
result.response.value is an Any, but to placate the compiler your force-cast it to a Data. But we already know this will only ever be an NSArray or NSDictionary, so this will never work.
You could keep wandering around in this area of the maze, and stumble to the end goal via a long path. For example, you could force cast to NSDictionary, then re-serialize that dictionary structure back to a JSON string, which you can turn into Data, only for you to then pass it to JSONDecoder().decode, which will then decode that JSON back. Of course, this is all awfully round-about and wasteful. The issue was the that responseJSON maze entrance was not the right one for where you're trying to go!
You could have instead entered into the responseData maze entrance, which gets you right to your Data destination!
Though you might then realize that the Data was a red herring all along. You didn't actually want Data. You wanted to decode an OrderStore, and Data was how you thought you needed to get there. But it turns out that so many people were entering through the Data entrance with the intent to decode some JSON, that the Alamofire people carved out a new entrance just for you: responseDecodable. It takes you right to the OrderStore, and fiddles around with the JSON, Data or whatever, under the hood where you don't have to worry about it.
I have a structure as shown below:
struct ItemList: Decodable {
var items: [UUID: Int]
}
Example JSON data I get is:
{
"items": {
"b4f8d2fa-941f-4f9a-a98c-060bbd468575": 418226428193,
"81efa661-4845-491b-8bf4-06d5dff1d5f8": 417639857722
}
}
Now, when I try to decode the above data, I get an interesting error. Clearly, I'm not decoding an array and clearly everything points at a dictionary.
try JSONDecoder().decode(ItemList.self, from: data)
// typeMismatch(
// Swift.Array<Any>,
// Swift.DecodingError.Context(
// codingPath: [
// CodingKeys(stringValue: "items", intValue: nil)
// ],
// debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
// underlyingError: nil
// )
// )
So I went experimenting and changed the [UUID: Int] to [String: Int], which does make this work, almost making me think the error is not array/dictionary related, but UUID/String related. So I also did the following test, which never fails.
let list = try JSONDecoder().decode(ItemList.self, from: data)
for (key, value) in list.items {
// This will never print `nil`
print(UUID(uuidString: key))
}
So my question is, why do I get this weird typeMismatch error when decoding, and why does it work when I change the UUID to a String, as it can clearly be properly decoded?
this article gives a good explanation about why this happens and what you can do about it. Short summary:
Swift encodes a dictionary as an array, where each value follows a key
There are a couple of approaches that you can use to overcome the problem:
a) Bend to swift's way using the array-representation of a dict
b) Use String or Int as your key-type
c) Use a custom decoder
d) Use the RawRepresentable-Protocol
I'm carrying this on from this question, since the focus has changed.
I am trying to send string data from a vapor server over a websocket. The client side is where the main question is. This code successfully receives the string, which is expected to be JSON (but not absolutely guaranteed -- out of scope).
switch message {
case .data(let data):
print("data: \(data)")
case .string(let str):
// let data = str.message(using: .utf8)
let jsonData = Data(str.utf8)
print("string: \(jsonData)")
do {
struct Person : Codable {
var name: String
}
let decoder = JSONDecoder()
let people = try decoder.decode([Person].self, from: jsonData)
print("result: \(people)")
} catch {
print(error.localizedDescription)
}
}
After some very helpful guidance, sending a string such as "{\"name\": \"Bobberoo\"}" will print out
string: 20 bytes
The data couldn’t be read because it isn’t in the correct format.
If I wrap it in braces "[{\"name\": \"Bobberoo\"}]" produces the more helpful but still mystifing (to me) output:
result: [wb2_socket_client.WebSocketController.(unknown context at $101a35028).(unknown context at $101a350c0).(unknown context at $101a35158).Person(name: "Bobberoo")]
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they? I can see that the first is the instance of the WebSocketController. How do I access this data.
And as a non-inflammatory aside: managing JSON is a trivial operation in any number of contexts. Python/Flask, Node, Ruby/Rails and on and on; I've used all these and implementing this kind of interaction is trivial. In Swift, it's a horrible, underdocumented nightmare. At least, that's my experience. Why? I know the language is type safe, but this is ridiculous.
error.localizedDescription won't give you an error message that is useful message for debugging. On the other hand, if you print error directly:
print(error)
You'd get something along the lines of "expected to decode array but found dictionary instead", which is exactly what is happening in the case of
{
"name": "Bobberoo"
}
You are decoding a [Person].self, i.e. an array of Person, but your JSON root is not a JSON array. The above JSON can be decoded if you did:
let people = try decoder.decode(Person.self, from: jsonData)
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they?
This is the default string representation of a type. Your Person struct does not conform to CustomStringConvertible or CustomDebugStringConvertible or TextOutputStreamable, so "an unspecified result is supplied automatically by the Swift standard library" (the link points to String.init(reflecting:), which presumably gets called somewhere along the way when you print the array of Person) and used as the string representation.
From what I can see, its current implementation is the fully qualified name of the struct - starting with the module, then the top-level class, then each enclosing scope, ending with the struct name, followed by the struct's members in brackets. It turns out that the enclosing scopes has no "names", and so are just called (unknown context at xxxxx). This is all very much implementation details, and things that you shouldn't care about.
What you should do, is provide an implementation of CustomStringConvertible:
struct Person: CustomStringConvertible {
...
var description: String { "name: \(name)" }
}
Now printing people gives:
[name: Bobberoo]
I can see that the first is the instance of the WebSocketController.
No. The WebSocketController is part of the fully qualified name of your Person struct. There is exactly one instance in your decoded array, and it's an instance of Person, as you would expect!
How do I access this data?
To access its name:
if let firstPerson = people.first {
let firstPersonsName = firstPerson.name
}
I'm having issues decoding banking information from an API's JSON response.
Here are the relevant parts of my data structures. I made the TransactionResponse object to simplify pulling the relevant data from the response:
struct Transaction: Decodable {
... // other properties, not relevant to this
let isFutureDated: Float? /\
... // see above ___________/
}
struct TransactionResponse: Decodable {
let response: Response?
struct Response: Decodable {
...
let transactions: [Transaction]?
}
}
And my conversion from the JSON data object:
do {
let transactions = try JSONDecoder().decode(TransactionResponse.self, from: data)
} catch let localError {
print(localError)
}
If I comment out the isFutureDated property, the object loads just fine (there's about a dozen other properties, so it's not just that it successfully loads nothing :P). When I include the isFutureDated property in the data structure, I catch the following error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "response", intValue: nil), CodingKeys(stringValue: "transactions", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "isFutureDated", intValue: nil)], debugDescription: "Expected to decode Float but found a number instead.", underlyingError: nil))
Am I missing something? Is a Float not a number? I've also tried changing the type for isFutureDated to Double, Int, Int8, Int16, Int 32, Int64, UInt, UInt8... you get the idea. Even Data and String. Always the same error, swapping out the Float part with whatever type it's expecting.
Final note, the actual JSON field from the response object reads as follows:
isFutureDated = 0;
It's not a field that I'm going to use in this particular app, but if this problem is going to happen again I'd like to find a solution to it before it counts.
Oh, never mind. isFutureDated is supposed to be a boolean, not any kind of number. I.e.,
let isFutureDated: Bool?
Weird.