I was fetching data from an API returning an array but needed to replace it by an API that has "sub levels":
RAW:
ETH:
USD:
TYPE: "5"
MARKET: "CCCAGG"
FROMSYMBOL: "ETH"
TOSYMBOL: "USD"
PRICE: 680.89
CHANGEPCT24HOUR : -9.313816893529749
Here is my struct:
struct Ethereum: Codable {
let percentChange24h: String
let priceUSD: String
private enum CodingKeys: String, CodingKey {
case priceUSD = "PRICE", percentChange24h = "CHANGEPCT24HOUR"
}
}
And the implementation:
func fetchEthereumInfo(completion: #escaping (Ethereum?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let ethereumUSD = try JSONDecoder().decode([Ethereum].self, from: data).first {
print(ethereumUSD)
completion(ethereumUSD, nil)
}
} catch {
print(error)
}
}
task.resume()
}
The console prints typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
I can't really figure out what to update in my code or what this form of API is
First of all the JSON does not contain any array. It's very very easy to read JSON. There are only 2 (two!) collection types, array [] and dictionary {}. As you can see there are no square brackets at all in the JSON string.
Any (sub)dictionary {} has to be decoded to its own type, so it's supposed to be
struct Root : Decodable {
private enum CodingKeys : String, CodingKey { case raw = "RAW" }
let raw : RAW
}
struct RAW : Decodable {
private enum CodingKeys : String, CodingKey { case eth = "ETH" }
let eth : ETH
}
struct ETH : Decodable {
private enum CodingKeys : String, CodingKey { case usd = "USD" }
let usd : USD
}
struct USD : Decodable {
private enum CodingKeys : String, CodingKey {
case type = "TYPE"
case market = "MARKET"
case price = "PRICE"
case percentChange24h = "CHANGEPCT24HOUR"
}
let type : String
let market : String
let price : Double
let percentChange24h : Double
}
To decode the JSON and and print percentChange24h you have to write
let result = try JSONDecoder().decode(Root.self, from: data)
print("percentChange24h", result.raw.eth.usd.percentChange24h)
Related
I am new to swift programming..was able to obtain a successful response from URLSession but I am unable to parse (decode) the data object to my desired APIResponse Structure
this is my url request code:
func load(urlRequest: URLRequest, withCompletion completion: #escaping (_ response: APIResponse) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("Error fetching data from server\nERROR: \(String(describing: error))")
return
}
guard let jsonData = data else {
print("Response Data is empty")
return
}
printResponseBody(response: data)
let decoder = JSONDecoder()
let response = try? decoder.decode(APIResponse.self, from: jsonData)
guard let decodedResponse = response else {
print("Unable to parse data from response")
return
}
print("Decoded Response: ", decodedResponse)
DispatchQueue.main.async { completion(decodedResponse) }
}
task.resume()
}
and here is my response structure which i need the response data to map to to use in my code:
struct APIResponse: Codable {
let responseCode: Int
let data: ResultSet
let meta: Meta
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case data, meta
}
}
// MARK: - DataClass
struct ResultSet: Codable {
let appVersionUpdate: String
let offers: [Offer]
let rate: Int
enum CodingKeys: String, CodingKey {
case appVersionUpdate = "app_version_update"
case offers, rate
}
}
// MARK: - Offer
struct Offer: Codable, Identifiable {
let id: Int
let title: String
let image: String?
let r, resultCount: Double
enum CodingKeys: String, CodingKey {
case id, r = "r"
case title, image
case resultCount = "result_count"
}
}
// MARK: - Meta
struct Meta: Codable {
let apiVersion: Int
enum CodingKeys: String, CodingKey {
case apiVersion = "api_version"
}
this is the json from server which I am trying to decode
{
"response_code": 0,
"data": {
"app_version_update": "",
"offers": [
{
"title": "Special Scheme1",
"image": "http://59.145.109.138:11101/Offers/BGL_banner_1080_x_540_1.jpg",
"r": 1.0,
"result_count": 5.0
},
{
"title": "test 1",
"image": "http://59.145.109.138:11101/Offers/Yoho-National-Park2018-10-27_10-10-52-11.jpg",
"r": 2.0,
"result_count": 5.0
},
{
"title": "Offer Test 1234444",
"image": "http://59.145.109.138:11101/Offers/Stanley-Park2018-10-27_10-11-27-44.jpg",
"r": 3.0,
"result_count": 5.0
}
],
"rate": 2000
},
"meta": {
"api_version": 2.0
}
}
whenever i run this code i am getting the "unable to parse data from response" error. Would really appreciate if someone tells me what I am doing wrong here
The problem is with decoding the id in Offer. Replace your Offer with this:
struct Offer: Codable, Identifiable {
let id: Int
let title: String
let image: String?
let r, resultCount: Int
enum CodingKeys: CodingKey {
case id, r, title, image, resultCount
var stringValue: String {
switch self {
case .id, .r: return "r"
case .title: return "title"
case .image: return "image"
case .resultCount: return "result_count"
}
}
}
}
Notes
First of all, you shouldn't have gotten rid of the error. Instead you could print the error and try to find out whats going wrong.
If you declare enum CodingKeys: String, CodingKey, the raw value for each case Must be different from the other or the Xcode will complain. In your case it didn't complain because id is the requirement of the Identifiable protocol, but it also didn't even use the raw value that you set for id. If you want to use the same key for 2 different variables, you should do the same thing i did above.
Better code
This will work the same as the your code, but is quite cleaner:
struct Offer: Codable, Identifiable {
var id: Int { r }
let title: String
let image: String?
let r, resultCount: Int
enum CodingKeys: String, CodingKey {
case r, title, image, resultCount
}
}
It basically says everytime you need id, get it from r. You can also remove the stringValue in CodingKeys as i did, and use the CodingKeys: String conformation.
Currently trying to parse a json dictionary to the screen in swift however I'm running into this sequence protocol error, and also a type mismatch error where it gets a string/data and expects a Int. Error is "Type 'ProductResponse' does not conform to protocol 'Sequence'" and
"typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "code", intValue: nil)], debugDescription:
"Expected to decode Int but found a string/data instead.",
underlyingError: nil))"
struct ProductResponse: Codable {
let code: String
let product: Product
let statusVerbose: String
let status: Int
enum CodingKeys: String, CodingKey {
case code, product
case statusVerbose = "status_verbose"
case status
}
}
struct Product: Codable {
let code: String
let productName: String
enum CodingKeys: String, CodingKey {
case code
case productName = "product_name"
}
}
class ViewController: UIViewController {
//var products = [Product]()
let API_URL = "https://carsdata/api/v0/product/5000112630794.json"
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(API_URL).responseJSON {
response in
let json = response.data
do
{
let decoder = JSONDecoder()
let productData = try decoder.decode(ProductResponse.self, from: json!)
for product in productData{ print(product.productName!) } }
catch
let err{ print(err) }
}
}
}
The problem is you are not parsing the JSON as it comes. You have two objects here. ProductResponse and Product and you're trying to parse it as one. productName is part of Product but you're trying to get it from ProductResponse. For clarity, I would recommend you to create 2 entities and try with this:
struct ProductResponse: Codable {
let code: String
let product: Product
let statusVerbose: String
let status: Int
enum CodingKeys: String, CodingKey {
case code, product
case statusVerbose = "status_verbose"
case status
}
}
struct Product: Codable {
let code: String
let productName: String
enum CodingKeys: String, CodingKey {
case code
case productName = "product_name"
}
}
And try to decode ProductResponse.self instead.
How can I decode data to a structure using Alamofire and SwiftyJSON? My attempts give me errors like that
"No value associated with key CodingKeys(stringValue: \"user\",
intValue: nil)
Here is my code, my try doesn't give me the result when I use non-optional values, they respond to me with NIL values
Alamofire.request(url, method: .post, parameters: params, encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
if response.data != nil {
switch response.result {
case.failure( let error):
print(error)
case.success(let val):
var json = JSON(val)
print(json)
guard let data = response.data else {return}
do {
let root = try JSONDecoder().decode(MainInfo.self, from: data)
print(root.submodel)
}
catch {
print("Bigerror")
print(error)
}
This is my structure
struct user: Codable {
var push_id:String?
var name:String?
var id:String?
var role_id:String?
var taxi_park_id:Int?
var car_number:String?
enum CodingKeys:String,CodingKey {
case push_id = "push_id"
case name = "name"
case id = "id"
case role_id = "role_id"
case taxi_park_id = "taxi_park_id"
case car_number = "car_number"
}
}
struct MainInfo : Decodable {
let model: String?
let submodel: String?
let user:user
enum CodingKeys:String,CodingKey {
case model = "model"
case submodel = "submodel"
case user = "user"
}
}
This is my pretty printed json
{
"facilities" : [
],
"model" : "AMC",
"taxi_park" : "Taxi +",
"submodel" : "Gremlin",
"user" : {
"role_id" : 2,
"push_id" : "dW7Cy-ItcDo:APA91bH62zJJKKz0t9VxP29H0iE2xhnQH0hDvKpGaHc5pknuTuZq2lMaj-EapQlN3O4dJF0ysSuCNOeb-2SdJaJaLIZcwHD3CCpeNpz6UVeGktoCm2ykL2rNXF5-ofQckvz1xTvVO0V6",
"taxi_park_id" : 0,
"id" : 3,
"name" : "China",
"car_number" : "X123OOO"
}
}
First of all your question has nothing to do with SwiftyJSON as you are using Codable.
Second of all name the structs with starting capital letter (User), that avoids confusion like let user : user
The error is misleading. All .._id values except push_id are Int rather than String. It's very easy to distinguish strings from all other types: Strings are always wrapped in double quotes.
And if you pass the convertFromSnakeCase key decoding strategy you don't need CodingKeys at all
struct MainInfo : Decodable {
let model : String
let submodel : String
let user : User
}
struct User: Decodable {
let pushId : String
let name : String
let id : Int
let roleId : Int
let taxiParkId : Int
let carNumber : String
}
...
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let root = try decoder.decode(MainInfo.self, from: data)
print(root.submodel)
} catch { print(error) }
Try this code, also a simple tip, we use coding keys in swift because sometimes we have to receive an inconvenient parameter keys but we also want to use it simple and clearly in the struct therefore CodingKeys are helpful in your case you using CodingKeys to decode the same parameter name i added the following taxiPark propriety to give you a hint on why they are useful, for example: i want to parse a JSON that have a key called
Person_ID_From_School
with coding keys i can do that with a better naming simple as personId and so on
struct MainInfo : Decodable {
let model: String?
let submodel: String?
let user:user
let taxiPark: String?
let facilities: [String?]?
enum CodingKeys:String,CodingKey {
case model = "model"
case submodel = "submodel"
case user = "user"
case taxiPark = "taxi_park"
case facilities = "facilities"
}
}
Here's an simplified version of the class:
class Movie: Codable {
var name: String
var genre: MovieGenre
init(name: String, genre: MovieGenre) {
self.name = name
self.genre = genre
}
}
enum MovieGenre: String, Codable {
case action
case drama
case horror
}
And the JSON:
{
"name" : "Test",
"genre" : 1
}
I know the relation between the JSON genre value and the MovieGenre enum is:
1 = action
2 = drama
3 = horror
Using JSONDecoder, how can I convert the JSON genre Int value to my enum MovieGenre?
I would like not to have to write an init from decoder, because it would be very verbose having to convert each attribute manually.
Here's an example:
let movie = Movie(name: "Test", genre: .action)
let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()
do {
// encoding
let jsonData = try jsonEncoder.encode(movie)
let jsonString = String(data: jsonData, encoding: .utf8)
print("JSON String : " + jsonString!) // prints: JSON String : {"name":"Test","genre":"action"}
// decoding
let json = "{\"name\":\"Test\",\"genre\":1}".data(using: .utf8)!
_ = try jsonDecoder.decode(Movie.self, from: json)
} catch {
print(error) // prints: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "genre", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
}
Your type for your enum does not match with the type in the JSON.
I changed the Type for your enum to Int and set the initial value for action (as the default would be 0) and got the expected result without custom decoding.
Trying this in a playground:
import UIKit
import PlaygroundSupport
let jsonData = """
{
"name" : "Test",
"genre" : 1
}
""".data(using: .utf8)!
class Movie: Codable {
var name: String
var genre: MovieGenre
init(name: String, genre: MovieGenre) {
self.name = name
self.genre = genre
}
}
enum MovieGenre: Int, Codable {
case action = 1
case drama
case horror
}
let decoder = JSONDecoder()
let result = try? decoder.decode(Movie.self, from: jsonData)
print(result?.name)
print(result?.genre)
print(result?.genre.rawValue)
Output is:
Optional("Test")
Optional(__lldb_expr_39.MovieGenre.action)
Optional(1)
This should also encode in the same way.
I have the following JSON code and want to parse it in Swift. I use Alamofire to get the JSON and have created a struct for the parsing:
{
"-8802586561990153106-1804221538-5":{
"zug":{
"klasse":"RB",
"nummer":"28721"
},
"ankunft":{
"zeitGeplant":"1804221603",
"zeitAktuell":"1804221603",
"routeGeplant":[
"Wiesbaden Hbf",
"Mainz Hbf"
]
},
"abfahrt":{
"zeitGeplant":"1804221604",
"zeitAktuell":"1804221604",
"routeGeplant":[
"Gro\u00df Gerau",
"Klein Gerau",
"Weiterstadt"
]
}
},
"8464567322535526441-1804221546-15":{
"zug":{
"klasse":"RB",
"nummer":"28724"
},
"ankunft":{
"zeitGeplant":"1804221657",
"zeitAktuell":"1804221708",
"routeGeplant":[
"Aschaffenburg Hbf",
"Mainaschaff"
]
},
"abfahrt":{
"zeitGeplant":"1804221658",
"zeitAktuell":"1804221709",
"routeGeplant":[
"Mainz-Bischofsheim"
]
}
}
}
I have created a struct for this that looks like this:
struct CallResponse: Codable {
struct DirectionTrain: Codable {
struct Train: Codable {
let trainClass: String
let trainNumber: String
}
struct Arrival: Codable {
let line: String
let eta: Date
let ata: Date
let platform: String
let route: [String]
}
struct Departure: Codable {
let line: String
let etd: Date
let atd: Date
let platform: String
let route: [String]
}
}
}
The rest of my code is:
Alamofire.request(url!).responseJSON { response in
switch response.result {
case .success:
let decoder = JSONDecoder()
let parsedResult = try! decoder.decode(CallResponse.self, from: response.data!)
case .failure(let error):
print(error)
}
}
When I run this code the error message is:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "train", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"train\", intValue: nil) (\"train\").", underlyingError: nil))
Can anyone help me find my problem? Thank you for your answers!
The problem is merely that your structs look nothing at all like your JSON!
Your JSON is a dictionary whose keys have names like "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15". But I don't see you declaring any struct that deals with those keys.
Then each of those turns out to be a dictionary with keys like "zug", "ankunft", and "abfahrt". But I don't see you declaring any struct that deals with those keys either.
And then the "zug" has keys "klasse" and "nummer"; you don't have those either.
And so on.
Either your structs must look exactly like your JSON, or else you must define CodingKeys and possibly also implement init(from:) to deal with any differences between your structs and your JSON. I suspect that the keys "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15" are unpredictable, so you will probably have to write init(from:) in order to deal with them.
For example, I was able to decode your JSON like this (I do not really recommend using try!, but we decoded without error and it's just a test):
struct Entry : Codable {
let zug : Zug
let ankunft : AnkunftAbfahrt
let abfahrt : AnkunftAbfahrt
}
struct Zug : Codable {
let klasse : String
let nummer : String
}
struct AnkunftAbfahrt : Codable {
let zeitGeplant : String
let zeitAktuell : String
let routeGeplant : [String]
}
struct Top : Decodable {
var entries = [String:Entry]()
init(from decoder: Decoder) throws {
struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
let con = try! decoder.container(keyedBy: CK.self)
for key in con.allKeys {
self.entries[key.stringValue] =
try! con.decode(Entry.self, forKey: key)
}
}
}
// d is a Data containing your JSON
let result = try! JSONDecoder().decode(Top.self, from: d)