I am getting a response from an API but the problem is that the API is sending values back as a string of dates and doubles. I am therefore getting the error "Expected to decode Double but found a string/data instead." I have structured my struct like this to solve the problem but this seems like a patch. Is there any better way to fix this issue? I feel like apple has thought of this and included something natively to address this.
struct SimpleOrder:Codable{
var createdDate:Date! {
return createdDateString.dateFromISO8601
}
var createdDateString:String
var orderId:String!
var priceVal:Double!
var priceString:String{
didSet {
priceVal = Double(self.priceString)!
}
}
private enum CodingKeys: String, CodingKey {
//case createdDate
case createdDateString = "time"
case orderId = "id"
case priceVal
case priceString = "price"
}
}
I don't know if this is relevant but this is how it is being used. I am getting the data as a string and converting it to data which is stored in the dataFromString variable
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 //This is irrelevant though because the date is returned as a string.
do{
let beer = try decoder.decode(SimpleOrder.self, from: dataFromString)
print ("beer is \(beer)")
}catch let error{
print ("error is \(error)")
}
As a result of using codable, I am getting an error when trying to get an empty instance of SimpleOrder. Before I was using codable, I had no issues using SimpleOrder() without any arguments.
Error: Cannot invoke initializer for type 'SimpleOrder' with no arguments
var singlePoint = SimpleOrder()
struct SimpleOrder: Codable {
var created: Date?
var orderId: String?
var price: String?
private enum CodingKeys: String, CodingKey {
case created = "time", orderId = "id", price
}
init(created: Date? = nil, orderId: String? = nil, price: String? = nil) {
self.created = created
self.orderId = orderId
self.price = price
}
}
extension SimpleOrder {
var priceValue: Double? {
guard let price = price else { return nil }
return Double(price)
}
}
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
Decoding the json data returned by the API:
let jsonData = Data("""
{
"time": "2017-12-01T20:41:48.700Z",
"id": "0001",
"price": "9.99"
}
""".utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
do {
let simpleOrder = try decoder.decode(SimpleOrder.self, from: jsonData)
print(simpleOrder)
} catch {
print(error)
}
Or initialising a new object with no values:
var order = SimpleOrder()
Related
I parsed data from a json file, but I don't know how to get these variables.
Need charcode, name and value.
I need to display them in a table using swiftui. I got a mess in the console and I don't know how to get to this data
this is struct
import Foundation
struct CurrencyModel: Codable {
let valute: [String : Valute]
enum CodingKeys: String, CodingKey {
case valute = "Valute"
}
}
struct Valute: Codable {
let charCode, name: String
let value: Double
enum CodingKeys: String, CodingKey {
case charCode = "CharCode"
case name = "Name"
case value = "Value"
}
}
and this is parser
class FetchDataVM: ObservableObject {
var valueData = [String : Any]()
init() {
fetchCurrency()
}
func fetchCurrency() {
let urlString = "https://www.cbr-xml-daily.ru/daily_json.js"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, _, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CurrencyModel.self, from: data)
print(decodedData)
} catch {
print("Error! Something went wrong.")
}
}
}
}.resume()
}
}
As all needed information is in the Valute struct you need only the values of the valute dictionary. Replace
var valueData = [String : Any]()
with
#Published var valueData = [Valute]()
and after the line print(decodedData) insert
self.valueData = decodedData.valute.values.sorted{$0.name < $1.name}
or
self.valueData = decodedData.valute.values.sorted{$0.charCode < $1.charCode}
In the view you can iterate the array simply with a ForEach expression
import Foundation
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> JSON
{
var json: JSON
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let data = try Data(contentsOf: fileUrl, options: .mappedIfSafe)
json = try? JSONSerialization.jsonObject(with: data)
} catch {
print("Something goes wrong when reading local json file.")
}
}
return json
}
}
I try to read the local json file and output json. But the line json = try? JSONSerialization.jsonObject(with: data) gives an error saying Cannot assign value of type 'Any?' to type 'JSON'.
My json data looks like
{
"leagues":
[
{ "name": "Hockey",
"image": "hockey",
"games":
[
{
"game_state": "Final",
"game_time": 1456662600,
"home_team_city": "Alberta",
"home_team_name": "Pigs",
"home_team_score": 1,
"home_team_logo": "pig",
"visit_team_city": "Montreal",
"visit_team_name": "Fishes",
"visit_team_score": 4,
"visit_team_logo": "fish"
}
]
}
]
}
When I change the output type to be Any? I print the output and it seems missing some elements.
{
leagues = (
{
games = (
{
"game_state" = Final;
"game_time" = 1456662600;
...
How can I fix it?
Check the solution below, I used Codable for the JSON decoding.
import Foundation
struct Sports: Codable {
let leagues: [League]
}
struct League: Codable {
let name, image: String
let games: [Game]
}
struct Game: Codable {
let gameState: String
let gameTime: Int
let homeTeamCity, homeTeamName: String
let homeTeamScore: Int
let homeTeamLogo, visitTeamCity, visitTeamName: String
let visitTeamScore: Int
let visitTeamLogo: String
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> Sports?
{
let path = Bundle.main.path(forResource: fileName, ofType: "json")
let url = URL(fileURLWithPath: path!)
let sportsData = try? Data(contentsOf: url)
guard
let data = sportsData
else { return nil }
do {
let result = try JSONDecoder().decode(Sports.self, from: data)
print(result)
return result
} catch let error {
print("Failed to Decode Object", error)
return nil
}
}
}
ReadLocalJSON.readJSONFromFile(fileName: "test")
Step 1:- first make a modal class in your project
struct Welcome: Codable {
let leagues: [League]?
}
// MARK: - League
struct League: Codable {
let name, image: String?
let games: [Game]?
}
// MARK: - Game
struct Game: Codable {
let gameState: String?
let gameTime: Int?
let homeTeamCity, homeTeamName: String?
let homeTeamScore: Int?
let homeTeamLogo, visitTeamCity, visitTeamName: String?
let visitTeamScore: Int?
let visitTeamLogo: String?
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
Step 2 : - After getting response write this line,
let decoder = JSONDecoder()
let obj = try! decoder.decode(Welcome.self, from: jsonData!)
IF you have still problem let me know
I have this json output that I want to parse using Codable:
{
"success": true,
"total": 1,
"users": [
{
"user": {
"id": "1",
"fname": "admin",
"lname": "admin",
"login": "admin",
"actif": "0",
"last_connection_date": "2018-01-18 16:02:34"
}
}
],
"msg": ""
}
And I just want to exctact the user's informations out of it.
My user's model
import RealmSwift
class User: Object, Codable {
#objc dynamic var id: String = ""
#objc dynamic var fname: String = ""
#objc dynamic var lname: String = ""
#objc dynamic var login: String = ""
// private enum CodingKeys : String, CodingKey {
// case id = "users[0].user.id"
// case fname = "users[0].user.fname"
// case lname = "users[0].lname"
// case login = "users[0].user.login"
// case password = "users[0].user.password"
// }
}
// Somewhere in my code
Alamofire.request(Path.userInformations(id: userId).rawValue).
responseJSON(completionHandler: { response in
do {
let user = try JSONDecoder().decode(User.self, from: response.data!)
} catch (let error) {
print(error.localizedDescription)
}
})
I've tried extracting the user's object, but wasn't successful casting it to Data to feed it to JSONDecoder().decode() method.
Responding to Vishal16 's comment
I've tried you first approach. It does not seem to work because, I think, of keyword "user" before the user's object. I've tried adding a new struct that wrap the user's object, but does not solve it.
struct ResponseBody : Codable {
var success : Bool?
var total : Int?
var users : [UserHolder]?
var msg : String?
var query_start : String?
var query_end : String?
var query_time : String?
var paging : Bool?
}
struct UserHolder : Codable {
var user: User?
enum CodingKeys: String, CodingKey {
case user = "user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
user = try values.decodeIfPresent(User.self, forKey: .user)
}
}
I think your response class structure should be like:
import Foundation
struct ResponseBody : Codable {
var status : Bool?
var total : Int?
var users : [User]? //list of users
var msg : String?
enum CodingKeys: String, CodingKey {
case status = "status"
case total = "total"
case users = "users"
case msg = "msg"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
status = try values.decodeIfPresent(Bool.self, forKey: . status)
total = try values.decodeIfPresent(Int.self, forKey: . total)
users = try values.decodeIfPresent([User].self, forKey: . users)
msg = try values.decodeIfPresent(String.self, forKey: . msg)
}
}
Now you will able to retrive your JSON data to object
let jsonDecoder = JSONDecoder()
let response = try jsonDecoder.decode(ResponseBody.self, from: data)
for user in response.users {
// user object is here
}
#edit
If you do not want to parse full response to JSON object
First convert Data to JSON Object using
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! Dictionary
Get users list string JSON then convert it to Data and after that data to User List object
if let responseBody = jsonResponse["users"] {
let dataBody = (responseBody as! String).data(using: .utf8)!
if let obj = Utils.convertToArray(data: dataBody) {
print(obj) // list of user obj
}
}
Hear is the method using in above implementation
class func convertToArray(data: Data) -> [AnyObject]? {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [AnyObject]
} catch {
Constants.print(items: error.localizedDescription)
}
return nil
}
Hope this help you. Happy codding :)
So hear is the working code for you
It's just working fine in my Playground. Please see below screenshots
1.
2.
3.
Decode json output to a model
Result:
class User: Object, Codable {
#objc dynamic var id: String = ""
#objc dynamic var fname: String = ""
#objc dynamic var lname: String = ""
#objc dynamic var login: String = ""
}
class Users: Object, Codable {
#objc dynamic var users: [User]
}
And for decoding
let user = try JSONDecoder().decode(Users.self, from: response.data!)
I think it should resolve the issue.
The other way is, you have to convert your response.data to Dictionary to dig down to user object.
I'm pretty stuck now. I'm attempting to PARS a JSON RETURN for just the year make make and model. It's buried in an array of dictionaries, and the decoder is having a hard time pulling them out. What am I doing wrong?
public struct Page: Decodable {
let Count: Int
let Message: String
let SearchCriteria: String
let Results: [car]}
public struct car: Decodable {
let ModelYear: String
let Make: String
let Model: String
let VIN: String}
let session = URLSession.shared
let components = NSURLComponents()
components.scheme = "https"
components.host = "vpic.nhtsa.dot.gov"
components.path = "/api/vehicles/decodevinvaluesextended/\(VIN)"
components.queryItems = [NSURLQueryItem]() as [URLQueryItem]
let queryItem1 = NSURLQueryItem(name: "Format", value: "json")
components.queryItems!.append(queryItem1 as URLQueryItem)
print(components.url!)
let task = session.dataTask(with: components.url!, completionHandler: {(data, response, error) in
guard let data = data else { return }
do
{
//let Result = try JSONDecoder().decode(Page.self, from: data)
// let PageResult = try JSONDecoder().decode(Page.self, from: data)
let json = try JSONDecoder().decode(Page.self, from: data)
let Results = json.Results;
print(Results)
First of all it's highly recommended to conform to the Swift naming convention that variable names start with a lowercase and structs start with a capital letter
public struct Page: Decodable {
private enum CodingKeys : String, CodingKey {
case count = "Count", message = "Message", searchCriteria = "SearchCriteria", cars = "Results"
}
let count: Int
let message: String
let searchCriteria: String
let cars: [Car]
}
public struct Car: Decodable {
private enum CodingKeys : String, CodingKey {
case modelYear = "ModelYear", make = "Make", model = "Model", VIN
}
let modelYear: String
let make: String
let model: String
let VIN: String
}
The cars array is in the variable result. This code prints all values
let result = try JSONDecoder().decode(Page.self, from: data)
for car in result.cars {
print("Make: \(car.make), model: \(car.model), year: \(car.modelYear), VIN: \(car.VIN)")
}
I've been trying to work with the below code to get my JSON data. It returns "Error after loading". I am using this JSON data in another application and it works. I'm trying to implement the new simplified method using Swift 4. The code does work to the point of the print statement "downloaded".
class MortgageRatesVC: UIViewController {
final let url = URL (string:"http://mortgous.com/JSON/currentRatesJSON.php")
override func viewDidLoad() {
super.viewDidLoad()
downloadJason()
}
func downloadJason () {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
print("downloaded")
do
{
let decoder = JSONDecoder()
let rates = try decoder.decode([LenederRates].self, from: data)
print(rates)
} catch {
print("Error after loading")
}
}.resume()
}
}
Class
class LenederRates: Codable {
let key : String
let financial_institution : String
let variable_rate : String
let six_months : String
let one_year : String
let two_year : String
let three_year : String
let four_year : String
let five_year : String
// let date : Date
init(key: String, financial_institution: String, variable_rate: String, six_months: String, one_year: String, two_year: String, three_year: String, four_year: String, five_year: String) {
self.key = key
self.financial_institution = financial_institution
self.variable_rate = variable_rate
self.six_months = six_months
self.one_year = one_year
self.two_year = two_year
self.three_year = three_year
self.four_year = four_year
self.five_year = five_year
}
}
The problem is the missing property date in your Codable class. You need to set the decoder dateDecodingStrategy to .formatted and pass a fixed format dateFormatter. Btw I suggest changing your class for a struct, change your property names using the Swift naming convention camelCase and provide the custom CodingKeys:
struct LenederRates: Codable {
let key: String
let financialInstitution : String
let variableRate: String
let sixMonths: String
let oneYear: String
let twoYear: String
let threeYear: String
let fourYear: String
let fiveYear: String
let date: Date
private enum CodingKeys: String, CodingKey {
case key, financialInstitution = "financial_institution", variableRate = "variable_rate", sixMonths = "six_months", oneYear = "one_year", twoYear = "two_year", threeYear = "three_year", fourYear = "four_year", fiveYear = "five_year", date
}
}
let mortgousURL = URL(string:"http://mortgous.com/JSON/currentRatesJSON.php")!
URLSession.shared.dataTask(with: mortgousURL) { data, urlResponse, error in
guard let data = data else { return }
do {
let dateFormat = DateFormatter()
dateFormat.locale = Locale(identifier: "en_US_POSIX")
dateFormat.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormat)
let rates = try decoder.decode([LenederRates].self, from: data)
print(rates)
} catch {
print(error)
}
}.resume()