UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: not sure how to decode this JSON

Forums > Swift

I want to get the exchange rate of a currency and put it into a variable as a double. The JSON looks like the following:


{"rates":{"USD":1.1608},"base":"EUR","date":"2020-07-24"}
2020-07-25 14:58:08.511568-0400 data from web site[1116:502354] [AXRuntimeCommon] Unknown client: data from web site

  It looks like I would need an array within an array, but I'm not sure.```

2      

You could do something like this:

struct CurrencyRates {
  let base: String

  let rates: [String: Double]

}

I think this should work. I have omitted Decodable and other properties.

3      

With the following code, I get these errors:

site/ContentView.swift:54:18: Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'Binding<[CurrencyRates]>' /Users/rmann/Library/Autosave Information/data from web site/data from web site/ContentView.swift:54:26: Value of type '[CurrencyRates]' has no dynamic member 'rates' using key path from root type '[CurrencyRates]' /Users/rmann/Library/Autosave Information/data from web site/data from web site/ContentView.swift:54:31: Cannot convert value of type 'Binding<Subject>' to expected argument type 'Double?' /Users/rmann/Library/Autosave Information/data from web site/data from web site/ContentView.swift:59:1: Initializer 'init(_:)' requires that 'Double' conform to 'StringProtocol' /SwiftUI.Text:4:12: Where 'S' = 'Double'


import SwiftUI
struct Response: Codable {
    var results:[CurrencyRates]
}
struct CurrencyRates:Codable {
  let base: String

  let rates: [String: Double]

}

struct ContentView: View {
    @State private var results = [CurrencyRates]()

    func loadData() {
        guard let url = URL(string: "https://api.exchangeratesapi.io/latest?symbols=USD") else {
            print("Invalid URL")
            return
        }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { data, response, error in
        // below is step 4
    if let data = data {
        if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
            // we have good data – go back to the main thread
            DispatchQueue.main.async {
                // update our UI
                self.results = decodedResponse.results
            }

            // everything is good, so we can exit
            return
        }
    }

    // if we're still here it means there was a problem
    print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")

    }.resume()

    }
//end of function
    var exchangeRate:Double {
        self.loadData()
        let rate=results.rates["USD"] ?? 1.0

        return rate
    }
    var body: some View {
Text(exchangeRate)

}

}

2      

As the error messages indicate, results isn't of type CurrencyRates so it has no member rates.

Instead, results is an array of CurrencyRates, so you need to use an index to access the rates property of one of its items.

However, looking at exchangeratesapi.io, I don't think you've set up your data model correctly. The API only returns a single object with multiple rates, so your results property shouldn't be an array.

Here's your code with tweaks I made to get it to work...

import SwiftUI

struct CurrencyRates:Codable {
    let base: String

    let rates: [String: Double]

}

struct CurrencyRatesView: View {
    @State private var results: CurrencyRates?

    func loadData() {
        guard let url = URL(string: "https://api.exchangeratesapi.io/latest?symbols=USD") else {
            print("Invalid URL")
            return
        }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, response, error in
            // below is step 4
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode(CurrencyRates.self, from: data) {
                    // we have good data – go back to the main thread
                    DispatchQueue.main.async {
                        // update our UI
                        self.results = decodedResponse
                    }

                    // everything is good, so we can exit
                    return
                }
            }

            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")

        }.resume()

    }
    //end of function
    var exchangeRate:Double {
        self.loadData()
        let rate=results?.rates["USD"] ?? 1.0

        return rate
    }

    var body: some View {
        Text(String(exchangeRate))

    }

}

3      

Also, this bit of code works? :

var exchangeRate:Double {
        self.loadData()
        let rate=results?.rates["USD"] ?? 1.0

        return rate
    }

It is problematic because the loadData will run asynchronously (the part with dataTask so the next line will be executed before the data is downloaded.

Also it is not a good practice to hide complex work inside computed properties because you generally don't expect that accessing exchangeRate in your code will trigger network call. What if you later use exchangeRate for List rows?

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.