JSONDecoder fails to parse a list of maps - swift

I have prepared a simple test Playground at Github to demo my problem:
My Swift code:
struct TopResponse: Codable {
let results: [Top]
}
struct Top: Codable {
let uid: Int
let elo: Int
let given: String
let photo: String?
let motto: String?
let avg_score: String?
let avg_time: String?
}
let url = URL(string: "https://slova.de/ws/top")!
let task = URLSession.shared.dataTask(with: url) {
data, response, error in
let decoder = JSONDecoder()
guard let data2 = data,
let tops = try? decoder.decode(TopResponse.self, from:
data2) else { return }
print(tops.results[4].given)
}
task.resume()
fails to parse the fetched JSON string and does not print anything.
What could be the problem here please?

What's wrong with your code?
try?
That's the main culprit.
Why? You are ignoring the error thrown by the decode(_:from:). You are ignoring the error that could give you the exact reason or at least a hint on why it failed. Instead, write a proper do { try ... } catch { ... }.
So:
guard let data2 = data,
let tops = try? decoder.decode(TopResponse.self, from:
data2) else { return }
print(tops.results[4].given)
=>
guard let data2 = data else { return }
do {
let tops = try decoder.decode(TopResponse.self, from: data2)
print(tops.results[4].given)
} catch {
print("Got error while parsing: \(error)")
print("With response: \(String(data: data2, encoding: .utf8))") //Just in case because I've seen plenty of code where expected JSON wasn't the one received: it was an error, doc changed, etc...
}
Output for the first print:
$>Got error while parsing: keyNotFound(CodingKeys(stringValue: "results", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"results\", intValue: nil) (\"results\").", underlyingError: nil))
Fix:
struct TopResponse: Codable {
let results: [Top]
enum CodingKeys: String, CodingKey {
case results = "data"
}
}
Or rename results with data.
Then, next error:
$>Got error while parsing: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "avg_score", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Extract from JSON:
"avg_score": 20.4
It's not a String (the value it's not between double quotes), that's a Double.
Fix:
let avg_score: String?
=>
let avg_score: Double?

Related

No value associated with key CodingKeys while trying to get data from GitHub API in Xcode app

So listening to #Larme and #JoakimDanielson (thanks a lot, guys!) I started doing some tasks on URLSessions to actually get the data I am looking for from GitHub API.
The endgame here is to create a mobile GitHub search app for repositories.
I have realised a code from this tutorial:
https://blog.devgenius.io/how-to-make-http-requests-with-urlsession-in-swift-4dced0287d40
Using relevant GitHub API URL. My code looks like this:
import UIKit
class Repository: Codable {
let id: Int
let owner, name, full_name: String
enum CodingKeys: String, CodingKey {
case id = "id"
case owner, name, full_name
}
init(id: Int, owner: String, name: String, fullName: String) {
self.id = id
self.owner = owner
self.name = name
self.full_name = full_name
}
}
(...)
let session = URLSession.shared
let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20")!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
// Check the response
print(response)
// Check if an error occured
if error != nil {
// HERE you can manage the error
print(error)
return
}
// Serialize the data into an object
do {
let json = try JSONDecoder().decode(Repository.self, from: data! )
//try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
} catch {
print("Error during JSON serialization: \(error.localizedDescription)")
print(String(describing:error))
}
})
task.resume()
}
}
The full error text is:
keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))
I tried removing some values that had this error but the others still threw the same and I used the coding keys I could find in the GitHub docs:
https://docs.github.com/en/rest/reference/search#search-repositories
Please help!
First of all you don't need CodingKeys and the init method.
Second of all use structs, not classes.
If you want to decode the repositories you have to start with the root object, the repositories are in the array for key items
struct Root : Decodable {
let items : [Repository]
}
struct Repository: Decodable {
let id: Int
let name, fullName: String
let owner : Owner
}
struct Owner : Decodable {
let login : String
}
Another issue is that owner is also a Dictionary which becomes another struct.
To get rid of the CodingKeys add the .convertFromSnakeCase strategy which translates full_name into fullName.
let session = URLSession.shared
let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20")!
let task = session.dataTask(with: url) { data, response, error in
// Check the response
print(response)
// Check if an error occured
if let error = error {
// HERE you can manage the error
print(error)
return
}
// Serialize the data into an object
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data! )
print(json)
} catch {
print("Error during JSON serialization:", error)
}
}
task.resume()

valueNotFound: Cannot get keyed decoding container found null instead

My project includes dynamic data, that changes when controllers change etc, so at one point my data may be:
[
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
]
Then a piece of code is ran and my data is now
[
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
null
]
Which is causing the valueNotFound error when the data is requested again.
What is the best way to skip/handle any indexes that are null?
Here is my API code:
class Service {
static let shared = Service()
let BASE_URL = "https://url.com"
func fetchClient(completion: #escaping ([Calls]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error)
return
}
guard let data = data else {return}
do {
let myDecoder = JSONDecoder()
let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
} catch let error {
print("Failed to create JSON with error: ", error)
}
}.resume()
}
Calls model:
struct Calls: Decodable {
let callDescription, callDuration, callMade_dateTime: String
let callID: Int
let dateTime: Date
let callMade: Bool
let type: String
}
Quick solution:
let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
=>
let calls = try myDecoder.decode([Calls?].self, from: data)
completion(calls.compactMap{ $0 })
Let's simplify the example (I started writing the answer before you wrote a real JSON working) :
struct Custom: Codable {
let data: String
}
let jsonString = """
[{"data": "Hello"}, {"data": "world"}]
"""
let jsonString2 = """
[{"data": "Hello"}, null, {"data": "world"}]
"""
So, some values might be null inside your JSON. That's where we can use Optional.
func test(json: String) {
do {
print("Testing with [Custom].self: \(json)")
let customs = try JSONDecoder().decode([Custom].self, from: json.data(using: .utf8)!)
print("Result: \(customs)")
} catch {
print("Error: \(error)")
}
}
func test2(json: String) {
do {
print("Testing with [Custom?].self: \(json)")
let customs = try JSONDecoder().decode([Custom?].self, from: json.data(using: .utf8)!)
print("Result with optionals: \(customs)")
let unwrapped = customs.compactMap { $0 }
print("Result unwrapped: \(unwrapped)")
} catch {
print("Error: \(error)")
}
}
test(json: jsonString)
test(json: jsonString2)
test2(json: jsonString)
test2(json: jsonString2)
Output:
$>Testing with [Custom].self: [{"data": "Hello"}, {"data": "world"}]
$>Result: [Custom(data: "Hello"), .Custom(data: "world")]
$>Testing with [Custom].self: [{"data": "Hello"}, null, {"data": "world"}]
$>Error: valueNotFound(Swift.KeyedDecodingContainer<.Custom.CodingKeys>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil))
$>Testing with [Custom?].self: [{"data": "Hello"}, {"data": "world"}]
$>Result with optionals: [Optional(.Custom(data: "Hello")), Optional(.Custom(data: "world"))]
$>Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]
$>Testing with [Custom?].self: [{"data": "Hello"}, null, {"data": "world"}]
$>Result with optionals: [Optional(.Custom(data: "Hello")), nil, Optional(.Custom(data: "world"))]
$>Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]

How to coerce Swift's JSON Loader to parse decimal from a string [duplicate]

This question already has an answer here:
Swift 4 decoding doubles from JSON
(1 answer)
Closed 3 years ago.
I'm trying to intialize an array of Items from a json file. To this end, I followed Apple's tutorial with re: to doing it (The algorithm is in data.swift but I'll post an abridged version down as well) My issue is that the API I'm pulling data from serves up decimals in quotation marks leading me to get the error
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "average_cost", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
What Apple's json decoder expects:
[{
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": 0.00, // Doubles without quotation marks
"otc_price": 0.00,
"dealer_price": 0.00,
"ctc_price": 0.00
}]
Sample data from my API saved in items.json:
[{
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": "0.00",
"otc_price": "0.00",
"dealer_price": "0.00",
"ctc_price": "0.00"
}]
I could probably rewrite my API to serve decimals and ints without quotation marks however it's already being used by other applications so I would rather not risk breaking something.
So is there a way to tell the decoded to ignore the quotation marks?
Item struct:
struct Item : Decodable {
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
Loading function:
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
Calling it:
let items: [Item] = load("items.json")
print(items)
Here's how I would implement this:
struct Item : Decodable {
let company: String
let itemClass: String
let name: String
let stock: Int
let averageCost: Decimal
let otcPrice: Decimal
let dealerPrice: Decimal
let ctcPrice: Decimal
enum CodingKeys: String, CodingKey {
case company
case itemClass = "item_class"
case name
case stock
case averageCost = "average_cost"
case otcPrice = "otc_price"
case dealerPrice = "dealer_price"
case ctcPrice = "ctc_price"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.company = try container.decode(String.self, forKey: .company)
self.itemClass = try container.decode(String.self, forKey: .itemClass)
self.name = try container.decode(String.self, forKey: .name)
self.stock = try container.decode(Int.self, forKey: .stock)
guard
let averageCost = Decimal(string: try container.decode(String.self, forKey: .averageCost))
else {
throw DecodingError.dataCorruptedError(forKey: .averageCost, in: container, debugDescription: "not a Decimal.")
}
guard
let otcPrice = Decimal(string: try container.decode(String.self, forKey: .otcPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .otcPrice, in: container, debugDescription: "not a Decimal.")
}
guard
let dealerPrice = Decimal(string: try container.decode(String.self, forKey: .dealerPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .dealerPrice, in: container, debugDescription: "not a Decimal.")
}
guard
let ctcPrice = Decimal(string: try container.decode(String.self, forKey: .ctcPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .ctcPrice, in: container, debugDescription: "not a Decimal.")
}
self.averageCost = averageCost
self.otcPrice = otcPrice
self.dealerPrice = dealerPrice
self.ctcPrice = ctcPrice
}
}
Alternatively, you could keep the String properties in your model, and convert to decimals on access
struct Item : Decodable {
let company: String
let itemClass: String
let name: String
let stock: Int
let _averageCost: String
let _otcPrice: String
let _dealerPrice: String
let _ctcPrice: String
enum CodingKeys: String, CodingKey {
case company
case itemClass = "item_class"
case name
case stock
case _averageCost = "average_cost"
case _otcPrice = "otc_price"
case _dealerPrice = "dealer_price"
case _ctcPrice = "ctc_price"
}
var averageCost: Decimal {
return Decimal(string: _averageCost) ?? .zero
}
// ... and so on for the other properties
}

Parse complex json code

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)

Swift4 / JSON / Decode

I'm new to Swift 4 and I looked for hours of hours to find a solution for my issue.
import Foundation
public struct Coin: Codable {
let name: String //= "Default"
let symbol: String
}
open class CoinCapIOAPI {
func fetchMap() {
let urlString = "http://socket.coincap.io/map"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// Maybe later...
guard let data = data else { return }
do {
let coins = try JSONDecoder().decode([Coin].self, from: data)
print(coins)
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
}
The JSON looks like:
[
{ aliases: [ ],
name: "300 Token",
symbol: "300",
},
{
aliases: [ ],
name: "SixEleven",
symbol: "611",
},
]
I need just name and symbol. But without the default of name in the struct I get following error:
Error: keyNotFound(CoinBartender.Coin.(CodingKeys in _7C60C6A5E9E301137DE95AF645AB94EB).name, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 91", intValue: Optional(91))], debugDescription: "No value associated with key name (\"name\").", underlyingError: nil))
If I add the default value for "name" I get this result:
[CoinBartender.Coin(name: "Default", symbol: "300"), CoinBartender.Coin(name: "Default", symbol: "611"),
Why does symbol work but name doesn't?
Please read the error message carefully. It's exactly describing the issue:
Error: keyNotFound(CoinBartender.Coin.(CodingKeys in _7C60C6A5E9E301137DE95AF645AB94EB).name, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 91", intValue: Optional(91))], debugDescription: "No value associated with key name (\"name\").", underlyingError: nil))
It says that the 92nd entry (index is zero-based) does not have a key name
{"aliases":[],"symbol":"QTM"}
One solution is to declare name as optional
let name: String?
Your Coin struct should be like this:
public struct Coin: Codable {
let name: String? //= "Default"
let symbol: String
}
Because some indexes does not contain name.