Swift4 / JSON / Decode - swift

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.

Related

JSONDecoder fails to parse a list of maps

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?

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")]

Swift - Expected to decode Array<Any> but found a dictionary instead

I have a json like bellow:
object{2}
status: 1
result{3}
cohorts[23]
categories[158]
languages[16]
And I am Decoder it like bellow:
struct ResultAPIJSON: Decodable {
private enum RootCodingKeys: String, CodingKey {
case result
}
private enum FeatureCohortsCodingKeys: String, CodingKey {
case cohorts
}
var cohortsPropertiesArray = [CohortsProperties]()
init(from decoder: Decoder) throws {
let rootContainerCohorts = try decoder.container(keyedBy: RootCodingKeys.self)
var featuresContainerCohorts = try rootContainerCohorts.nestedUnkeyedContainer(forKey: .result)
let AAA = try featuresContainerCohorts.nestedContainer(keyedBy: FeatureCohortsCodingKeys.self)
let BBB = try AAA.nestedUnkeyedContainer(forKey: .cohorts)
while BBB.isAtEnd == false {
let propertiesContainer = try featuresContainerCohorts.nestedContainer(keyedBy: FeatureCohortsCodingKeys.self)
// Decodes a single quake from the data, and appends it to the array.
let properties = try propertiesContainer.decode(CohortsProperties.self, forKey: .cohorts)
cohortsPropertiesArray.append(properties)
}
}
}
But get me bellow error:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
If this is your JSON
object{2}
status: 1
result{3}
cohorts[23]
categories[158]
languages[16]
(or more likely, this:)
{
"status": 1,
"result": {
"cohorts": 23,
"categories": 158,
"languages": 16
}
}
then you need two structs:
struct object: Decodable {
let status: Int
let result: Result
}
struct Result: Decodable {
let cohorts: Int
let categories: Int
let languages: Int
}

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)