Search code examples
jsonswiftalamofiredecodable

Swift - How to extract all values with same key names


having looked at several posts I didn't find what I was looking for. I hope this post will help me.

I use the api of CoinDesk, what I'm trying to do now is to retrieve in the answer all the codes (EUR, USD, GBP) but I can't get all the assets.

    {
  "time": {
    "updated": "Feb 20, 2021 19:48:00 UTC",
    "updatedISO": "2021-02-20T19:48:00+00:00",
    "updateduk": "Feb 20, 2021 at 19:48 GMT"
  },
  "disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
  "chartName": "Bitcoin",
  "bpi": {
    "USD": {
      "code": "USD",
      "symbol": "$",
      "rate": "57,014.5954",
      "description": "United States Dollar",
      "rate_float": 57014.5954
    },
    "GBP": {
      "code": "GBP",
      "symbol": "£",
      "rate": "40,681.1111",
      "description": "British Pound Sterling",
      "rate_float": 40681.1111
    },
    "EUR": {
      "code": "EUR",
      "symbol": "€",
      "rate": "47,048.5582",
      "description": "Euro",
      "rate_float": 47048.5582
    }
  }
}

here's how I'm going to get the data

public class NetworkManager {

static public func fetchBPI() {
    let url = "https://api.coindesk.com/v1/bpi/currentprice.json"
    Alamofire.request(url).responseJSON { response in
        switch response.result {
        case .success:
            print("✅ Success ✅")
            if let json = response.data {
                do {
                    let data = try JSON(data: json)
                    print(data)
                    
                    let context = PersistentContainer.context
                    let entity = NSEntityDescription.entity(forEntityName: "BPI", in: context)
                    let newObject = NSManagedObject(entity: entity!, insertInto: context)
                    
                    //Date Formatter
                    let dateFormatter = DateFormatter()
                    dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
                    let date = dateFormatter.date(from: data["time"]["updated"].rawValue as! String)
                    dateFormatter.timeZone = NSTimeZone.local
                    let timeStamp = dateFormatter.string(from: date ?? Date())
                    
                    newObject.setValue(timeStamp, forKey: "time")
                    newObject.setValue(data["chartName"].rawValue, forKey: "chartName")
                    newObject.setValue(data["bpi"]["EUR"]["symbol"].rawValue, forKey: "symbol")
                    newObject.setValue(data["bpi"]["EUR"]["rate"].rawValue, forKey: "rate")
                    newObject.setValue(data["bpi"]["EUR"]["code"].rawValue, forKey: "code")
                    
                    do {
                        try context.save()
                        print("✅ Data saved ✅")
                    } catch let error {
                        print(error)
                        print("❌ Saving Failed ❌")
                    }
                }
                catch {
                    print("❌ Error ❌")
                }
            }
        case .failure(let error):
            print(error)
        }
    }
}

}

I would like to get in the bpi key all the codes and put them in a list to use them.


Solution

  • The best way to handle the json here in my opinion is to treat the content under "bpi" as a dictionary instead.

    struct CoinData: Codable {
        let time: Time
        let chartName: String
        let bpi: [String: BPI]
    }
    
    struct BPI: Codable {
        let code: String
        let rate: Double
    
        enum CodingKeys: String, CodingKey {
            case code
            case rate = "rate_float"
        }
    }
    
    struct Time: Codable {
        let updated: Date
    
        enum CodingKeys: String, CodingKey {
            case updated = "updatedISO"
        }
    }
    

    I have removed some unnecessary (?) properties and also note that I made updatedISO in Time into a Date if that might be useful since it's so easy to convert it.

    To properly decode this use try with a do/catch so you handle errors properly.

    Here is an example of that where I also loop over the different currencies/rates

    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        let result = try decoder.decode(CoinData.self, from: data)
    
        print(result.time.updated)
        let coins = result.bpi.values
        for coin in coins {
            print(coin)
        }
    } catch {
        print(error)
    }
    

    Output:

    2021-02-20 19:48:00 +0000
    BPI(code: "GBP", rate: 40681.1111)
    BPI(code: "USD", rate: 57014.5954)
    BPI(code: "EUR", rate: 47048.5582)