Will
Will

Reputation: 79

JSON not decoding to struct (Fatal error, Swift)

I am trying to decode the following JSON response:

{"success":true,"initialprice":"0.00003592","price":"0.00006587",
"high":"0.00006599","low":"0.00003499","volume":"0.68979910",
"bid":"0.00006205","ask":"0.00006595"}

Into a structure in Swift that looks like this:

struct TOTicker : Codable {
public var success : Bool?
public var initialprice : Double?
public var price : Double?
public var high : Double?
public var low : Double?
public var volume :Double?
public var bid :Double?
public var ask :Double?
}

The line of code I am using to decode is as follows:

let decoder = JSONDecoder()
let ticker = try! decoder.decode(TOTicker.self, from: jsonData)

And it keeps throwing a fatal error but I have no idea why. I have used this method for decoding before with no trouble.

Upvotes: 0

Views: 548

Answers (3)

Shehata Gamal
Shehata Gamal

Reputation: 100503

All values are strings( marked by "" not the content ) except success is Bool

struct TOTicker : Codable {
    public var success : Bool?
    public var initialprice : String?
    public var price : String?
    public var high : String?
    public var low : String?
    public var volume :String?
    public var bid :String?
    public var ask :String?
}

//

do {
    let decoder = JSONDecoder()
    let ticker = try decoder.decode(TOTicker.self, from: jsonData)
  }
catch {
      print(error)
}

Upvotes: 1

Leo Dabus
Leo Dabus

Reputation: 236360

The problem here as already posted by @AhmadF is that the decoder is expecting to decode Double but found a string instead. A better solution would be to instead of changing the properties type is to implement your own decoder to decode those strings and coerce them to Double.

Note: You should declare your structure properties as constants and only declare optional those that might not be returned by the server (api):

struct TOTicker: Codable {
    let success: Bool
    let initialprice: Double
    let price: Double
    let high: Double
    let low: Double
    let volume: Double
    let bid: Double
    let ask: Double
}

The custom decoder:

extension TOTicker {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        guard
            let initialprice = try Double(container.decode(String.self, forKey: .initialprice)),
            let price = try Double(container.decode(String.self, forKey: .price)),
            let high = try Double(container.decode(String.self, forKey: .high)),
            let low = try Double(container.decode(String.self, forKey: .low)),
            let volume = try Double(container.decode(String.self, forKey: .volume)),
            let bid = try Double(container.decode(String.self, forKey: .bid)),
            let ask = try Double(container.decode(String.self, forKey: .ask))
        else {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Error decoding String into Double"))
        }
        success = try container.decode(Bool.self, forKey: .success)
        self.initialprice = initialprice
        self.price = price
        self.high = high
        self.low = low
        self.volume = volume
        self.bid = bid
        self.ask = ask
    }
}

Now you can properly decode your json:

let data = Data("""
{"success":true,"initialprice":"0.00003592","price":"0.00006587",
    "high":"0.00006599","low":"0.00003499","volume":"0.68979910",
    "bid":"0.00006205","ask":"0.00006595"}
""".utf8)

let decoder = JSONDecoder()
do {
    let ticker = try decoder.decode(TOTicker.self, from: data)
    print(ticker)
} catch {
    print(error)
}

This will print:

TOTicker(success: true, initialprice: 3.5920000000000002e-05, price: 6.5870000000000005e-05, high: 6.5989999999999997e-05, low: 3.4990000000000002e-05, volume: 0.6897991, bid: 6.2050000000000004e-05, ask: 6.5950000000000004e-05)

Upvotes: 1

Ahmad F
Ahmad F

Reputation: 31645

First, to know what's the reason of the error you should do a do-catch block instead of try!:

do {
    let ticker = try decoder.decode(TOTicker.self, from: jsonData)
} catch {
    print(error)
}

Therefore, you would notice that the error is:

typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "initialprice", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

which seems to be clear enough; Your json contains strings as values instead of doubles (the floating point values are surrounded by ""). What you should do is to declare TOTicker properties as:

struct TOTicker : Codable {
    public var success : Bool?
    public var initialprice : String?
    public var price : String?
    public var high : String?
    public var low : String?
    public var volume :String?
    public var bid :String?
    public var ask :String?
}

Now, you'd notice that you are able to successfully parse it:

let decoder = JSONDecoder()

do {
    let ticker = try decoder.decode(TOTicker.self, from: jsonData)
    print(ticker)
} catch {
    print(error)
}

You should see in the log:

TOTicker(success: Optional(true), initialprice: Optional("0.00003592"), price: Optional("0.00006587"), high: Optional("0.00006599"), low: Optional("0.00003499"), volume: Optional("0.68979910"), bid: Optional("0.00006205"), ask: Optional("0.00006595"))

Upvotes: 1

Related Questions