I am trying to complete a pretty standard thing: decode a JSON response. I can get the data, however decoding does not succeed - worse, there is no error message. Print statement 1 & 2 output with the correct data but I get no output from print statement 3, it outputs print statement 4 instead (e.g. my error message).
I am hoping someone can see my probably obvious error, thanks for any hints that could resolve me issue!
Phil
(Edit) Added JSON Response:
[{
"open": {
"price": 122.52,
"time": 1668853732275
},
"close": {
"price": 125.44,
"time": 1658436480762
},
"high": 125.35,
"low": 123.57,
"volume": 75244144,
"symbol": "AAPL"
}]
struct ResponsePrice: Codable {
var results: [ResultPrice]
}
struct ResultPrice: Codable {
var open: ResultPriceTime
var close: ResultPriceTime
var high: Double
var low: Double
var volume: Double
var symbol: String
}
struct ResultPriceTime: Codable {
var price: Double
var time: Double
}
///
struct SymbolView: View {
#State private var results = [ResultPrice]()
var body: some View {
List(resultsPrice, id: \.symbol) { item in
VStack(alignment: .leading) {
Text(item.symbol)
.font(.headline)
Text(item.symbol)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://sandbox.iexapis.com/stable/stock/market/ohlc?symbols=aapl&token=Tpk_af03c7f5bac14742a7ce77969a791c66") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
do {
if let data = data {
//let stringData = String(decoding: data, as: UTF8.self)
//print(stringData)
if let decodedResponse = try JSONDecoder().decode(ResponsePrice.self, from: data) {
// we have good data – go back to the main thread
print("Fetched: \(url)")
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse.results
print("Ticker:\(self.results[0].T)")
}
// everything is good, so we can exit
return
}
}
}
catch {
fatalError("Couldn't parse as :\n\(error)")
}
// if we're still here it means there was a problem
print("4 Decode failed: \(error?.localizedDescription ?? "Unknown error")")
// step 4
}.resume()
}
}
struct SymbolView_Previews: PreviewProvider {
static var previews: some View {
SymbolView()
}
}
Related
Iam trying to display the contents of the result.
The Data is returned as JSON Array.
I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.
Help would be highly appreciated
import SwiftUI
struct Stocks: Hashable, Codable{
var results: [Results]
var status: String
struct Results: Hashable, Codable{
var ticker: String
var name: String
var market: String
var locale: String
var primary_exchange: String
var type: String
var active: Bool
var currency_name: String
var cik: String
var composite_figi: String
var share_class_figi: String
var last_update_utc: String
}
}
class ViewModel: ObservableObject{
#Published var stocks: [Stocks] = []
func fetch(){
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
let task = URLSession.shared.dataTask(with: url) {[weak self]data, _, error in
guard let data = data, error == nil else{
return
}
// Convert JSON
do{
let stocks = try JSONDecoder().decode([Stocks].self, from: data)
DispatchQueue.main.async{
self?.stocks = stocks
}
}
catch{
print(error)
}
}
task.resume()
}
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
NavigationView{
List{
ForEach(viewModel.stocks, id: \.self){resu in
ForEach(resu.results, id: \.self){st in
Text(st.currency_name)
}
}
}
}.navigationTitle("Stocks")
.onAppear{
viewModel.fetch()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the Response object:
{
"results": [
{
"ticker": "A",
"name": "Agilent Technologies Inc.",
"market": "stocks",
"locale": "us",
"primary_exchange": "XNYS",
"type": "CS",
"active": true,
"currency_name": "usd",
"cik": "0001090872",
"composite_figi": "BBG000C2V3D6",
"share_class_figi": "BBG001SCTQY4",
"last_updated_utc": "2022-12-20T00:00:00Z"
},
{
"ticker": "AA",
"name": "Alcoa Corporation",
"market": "stocks",
"locale": "us",
"primary_exchange": "XNYS",
"type": "CS",
"active": true,
"currency_name": "usd",
"cik": "0001675149",
"composite_figi": "BBG00B3T3HD3",
"share_class_figi": "BBG00B3T3HF1",
"last_updated_utc": "2022-12-20T00:00:00Z"
},
I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.
The naming of your structs is largely confusing.
According to the JSON you are going to receive one root object containing an array of Stock (supposed to be named in singular form) objects for key results.
And there is a struct member last_update_utc which does not match the key last_updated_utc.
Name your structs this way, I renamed the struct members as camelCase and as constants (let) and you can decode lastUpdatedUtc as Date with the .iso8601 strategy
struct Response: Hashable, Decodable {
let results: [Stock]
let status: String
struct Stock: Hashable, Decodable {
let ticker: String
let name: String
let market: String
let locale: String
let primaryExchange: String
let type: String
let active: Bool
let currencyName: String
let cik: String
let compositeFigi: String
let shareClassFigi: String
let lastUpdatedUtc: Date
}
}
and decode the JSON
class ViewModel: ObservableObject{
#Published var stocks: [Response.Stock] = []
func fetch() {
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
let task = URLSession.shared.dataTask(with: url) {[weak self] data, _, error in
if let error { print(error); return }
// Convert JSON
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let response = try decoder.decode(Response.self, from: data!)
DispatchQueue.main.async {
self?.stocks = response.results
}
}
catch{
print(error)
}
}
task.resume()
}
}
I even recommend to use async/await
#MainActor
class ViewModel: ObservableObject{
#Published var stocks: [Response.Stock] = []
func fetch() {
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let response = try decoder.decode(Response.self, from: data)
self.stocks = response.results
} catch {
print(error)
}
}
}
}
Upon login and validation, the user is sent to the main application page. I have the following code set.
import SwiftUI
typealias MyDefendant = [Defendant]
struct ContentView: View {
var email: String
#State var myDefendant: MyDefendant = []
func getUserData(completion:#escaping (MyDefendant)->()) {
var urlRequest = URLRequest(url: URL(string: "https://milanobailbonds.com/getDefendant.php")!)
urlRequest.httpMethod = "post"
let authData = [
"defEmail" : email
] as [String : Any]
do {
let authBody = try JSONSerialization.data(withJSONObject: authData, options: .prettyPrinted)
urlRequest.httpBody = authBody
urlRequest.addValue("application/json", forHTTPHeaderField: "content-type")
} catch let error {
debugPrint(error.localizedDescription)
}
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else {
return
}
do {
let responseString = String(data: data, encoding: .utf8)!
print(responseString)
var returnValue: MyDefendant?
let decoder = JSONDecoder()
returnValue = try decoder.decode([Defendant].self, from: data)
completion(returnValue!)
}
catch { fatalError("Couldn't Parse")
}
}.resume()
return
}
var body: some View {
NavigationView {
VStack {
Text(email)
Text("I Need Bail")
.font(.largeTitle)
.fontWeight(.semibold)
Button {
print("Test")
} label: {
Label("I Need Bail", systemImage: "iphone.homebutton.radiowaves.left.and.right")
.labelStyle(IconOnlyLabelStyle())
.font(.system(size: 142.0))
}
} .foregroundColor(.green)
.shadow(color: .black, radius: 2, x: 2, y: 2)
.navigationBarTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading:
Button {
print("Test")
} label: {
Label("I Need Bail", systemImage: "line.3.horizontal")
.labelStyle(IconOnlyLabelStyle())
})
}.onAppear() {
getUserData() { myDefendant in
self.myDefendant = myDefendant
}
}
}
}
In my data models I have created a struct for Defendant as such:
struct Defendant: Codable, Hashable, Identifiable {
var id: Int
var defImage: String
var defName: String
var defAddress: String
var defCity: String
var defState: String
var defZip: String
var defPhone: String
var defEmail: String
var defUserName: String
var defPW: String
var defDOB: String
var defPriorFTA: Int
var defFTAExplained: String
var defAssignedAgency: Int
} // Defendant Model
The PHP is working fine and returning valid JSON with all of the required items for the struct.
"\"[\\n {\\n \\\"Id\\\": 5,\\n \\\"defImage\\\": \\\"\\\",\\n \\\"defName\\\": \\\"Some Dude\\\",\\n \\\"defAddress\\\": \\\"123 Main St\\\",\\n \\\"defCity\\\": \\\"Some City\\\",\\n \\\"defState\\\": \\\"FL\\\",\\n \\\"defZip\\\": \\\"12345\\\",\\n \\\"defPhone\\\": \\\"888-888-8888\\\",\\n \\\"defEmail\\\": \\\"someone#someone.com\\\",\\n \\\"defUserName\\\": \\\"\\\",\\n \\\"defPW\\\": \\\"91492cffa4032765f6b025ec6b2c873e49fe5e58\\\",\\n \\\"defDOB\\\": \\\"01\\\\\\/01\\\\\\/1955\\\",\\n \\\"defPriorFTA\\\": 0,\\n \\\"defFTAExplained\\\": \\\"\\\",\\n \\\"defAssignedAgency\\\": 0\\n }\\n]\""
Unfortunately, I keep getting an error "Unable to Parse".
I'm new to Swift, and coding in general.
Any thoughts or ideas are greatly appreciated.
Thank you
Im not sure but you can try to change id into Id in your struct. Please remember name of your struct property must exactly the same with key in Json response.
I want to parse the following JSON into a list with SwiftUI, however I am experiencing some issues with this.
JSON (there are many more entires than this with random Identifiers as the entry. This is what caused it to be much harder to perform):
{
"DCqkboGRytms": {
"name": "graffiti-brush-3.zip",
"downloads": "5",
"views": "9",
"sha256": "767427c70401d43da30bc6d148412c31a13babacda27caf4ceca490813ccd42e"
},
"gIisYkxYSzO3": {
"name": "twitch.png",
"downloads": "19",
"views": "173",
"sha256": "aa2a86e7f8f5056428d810feb7b6de107adefb4bf1d71a4ce28897792582b75f"
},
"bcfa82": {
"name": "PPSSPP.ipa",
"downloads": "7",
"views": "14",
"sha256": "f8b752adf21be6c79dae5b80f5b6176f383b130438e81b49c54af8926bce47fe"
}
}
SwiftUI Codables:
struct Feed: Codable {
let list: [FeedValue]
}
struct FeedValue: Codable, Identifiable {
var id = UUID()
let name, downloads, views, sha256: String
}
JSON Fetch method (and parse):
func getFeeds(completion:#escaping (Feed) -> ()) {
// URL hidden to respect API privacy
guard let url = URL(string: "") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let feed = try! JSONDecoder().decode(Feed.self, from: data!)
DispatchQueue.main.async {
completion(feed)
}
}.resume()
}
Finally, the view containing my ForEach loop:
struct HomeView: View {
#State private var scrollViewContentOffset: CGFloat = .zero
#State var listFeed = Feed(list: [])
var body: some View {
// Z-coord status bar holder
ZStack(alignment: .top) {
TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset) {
// Public files Start
LazyVStack(spacing: 0) { {
// Withholds our content for each asset retrieved.
// Main part
ForEach(listFeed.list, id: \.id) { download in
AssetView(name: .constant(download.name), downloads: .constant(download.downloads), views: .constant(download.views))
}
}.padding([.leading, .top, .trailing], 25)
// Public files End
}.edgesIgnoringSafeArea([.leading, .trailing, .top])
}.background(Color.backgroundColor.ignoresSafeArea())
.onAppear() {
getFeeds { feed in
//print(feed) : this did happen to work at the time of testing
self.listFeed = feed
}
}
}
}
And for the error that is being presented:
Starfiles/HomeView.swift:32: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "list", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "list", intValue: nil) ("list").", underlyingError: nil))
I've spent several hours today changing the method of parsing, but was unable to find a solution on my own.
EDIT 1: I would like to mention that I need a way to access the dictionary ID's as well, as they'll be used for entering in a new view.
EDIT 2: Using what #jnpdx provided, JSON parsing now works as expectedly, and gets the information I need. However there's an issue with ForEach loops, the same one which persisted before (It does work in a List(), as his answer showed).
Here is the updated code (not going to show any code that's irrelevant)
ForEach(listItems) { item in // Cannot convert value of type '[IdentifiableFeedValue]' to expected argument type 'Binding<C>' & Generic parameter 'C' could not be inferred
AssetView(name: .constant(item.feedValue.name), // Cannot convert value of type 'Binding<Subject>' to expected argument type 'String'
downloads: .constant(item.feedValue.downloads), views: .constant(item.feedValue.views))
}.onAppear() {
// URL hidden to respect API privacy, refer to JSON above.
guard let url = URL(string: "") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
do {
list = try JSONDecoder().decode([String:FeedValue].self, from: data!)
} catch {
print("error")
}
}.resume()
}
// AssetView
struct AssetView: View {
#Binding var name: String
#Binding var downloads: String
#Binding var views: String
...
}
Your JSON doesn't have any keys called list, which is why it's failing. Because it's a dynamically-keyed dictionary, you will want to decode it as [String:FeedValue].
I used a wrapper to keep the id from the original JSON, but you could also do some fancier decoding if you wanted to keep it in a one-level struct, but that's beyond the scope of this question.
let jsonData = """
{
"DCqkboGRytms": {
"name": "graffiti-brush-3.zip",
"downloads": "5",
"views": "9",
"sha256": "767427c70401d43da30bc6d148412c31a13babacda27caf4ceca490813ccd42e"
},
"gIisYkxYSzO3": {
"name": "twitch.png",
"downloads": "19",
"views": "173",
"sha256": "aa2a86e7f8f5056428d810feb7b6de107adefb4bf1d71a4ce28897792582b75f"
},
"bcfa82": {
"name": "PPSSPP.ipa",
"downloads": "7",
"views": "14",
"sha256": "f8b752adf21be6c79dae5b80f5b6176f383b130438e81b49c54af8926bce47fe"
}
}
""".data(using: .utf8)!
struct FeedValue: Codable {
let name, downloads, views, sha256: String
}
struct IdentifiableFeedValue : Identifiable {
let id: String
let feedValue: FeedValue
}
struct ContentView: View {
#State var list : [String:FeedValue] = [:]
var listItems: [IdentifiableFeedValue] {
list.map { IdentifiableFeedValue(id: $0.key, feedValue: $0.value) }
}
var body: some View {
List(listItems) { item in
Text(item.feedValue.name)
}.onAppear {
do {
list = try JSONDecoder().decode([String:FeedValue].self, from: jsonData)
} catch {
print(error)
}
}
}
}
I am trying to make a view that shows the stats of a bulb, I want to show if the device is on or off and what its brightness is. I already have an API that can return this information in JSON and also have a web GUI. But I want to make an app on my iPhone so I am very new to Swift so used this video to parse the JSON response from the API and print it to the console. I now don't know how to actually put the information I get into visible pieces of text. I will show you the JSON return I get and the code I have already done:
Parsed JSON
BulbInfo(error_code: 0, result: UITest.Result(device_on: true, brightness: 100))
API return JSON
{'error_code': 0,
'result': {
'device_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'fw_ver': '1.1.9 Build 20210122 Rel. 56165',
'hw_ver': '1.0.0',
'type': 'SMART.TAPOBULB',
'model': 'L510 Series',
'mac': 'xx-xx-xx-xx-xx-xx',
'hw_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'fw_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'oem_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'specs': 'EU',
'lang': 'en_US',
'device_on': True,
'on_time': 3065,
'overheated': False,
'nickname': 'TWFpbiBMaWdodA==',
'avatar': 'hang_lamp_1',
'brightness': 100,
'default_states': {
'brightness': {
'type': 'last_states',
'value': 100
}
},
'time_diff': 0,
'region': 'Europe/London',
'longitude': -xxxxx,
'latitude': xxxxxx,
'has_set_location_info': True,
'ip': '192.168.x.xxx',
'ssid': 'xxxxxxxxxxxx',
'signal_level': 1,
'rssi': -xx
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
func getDeviceInfo(){
let urlString = "http://192.168.x.xxx:xxx/get_bulb_info"
let url = URL(string:urlString)
let session = URLSession.shared
let dataTask = session.dataTask(with: url!){(data,response,error)in
// Check for error
if error == nil && data != nil {
// Parse JSON
let decoder = JSONDecoder()
do{
let bulbInfo = try decoder.decode(BulbInfo.self, from: data!)
print(bulbInfo)
}
catch{
print(error)
}
}
}
dataTask.resume()
}
var body: some View {
Text("Main Light:").padding()
Button(action:getDeviceInfo){
Text("Get Device Info!")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Bulb.swift
//
// Bulb.swift
// UITest
//
// Created by James Westhead on 18/12/2021.
//
import Foundation
struct BulbInfo: Codable{
var error_code: Int
var result: Result
}
struct Result: Codable{
var device_on:Bool
var brightness: Int
}
Add to ContentView
#State var bulbInfo: BulbInfo? = nil
Then remove the let from the do catch block
You can access the information by using something like
bulbInfo.result.device_on.description
or
bulbInfo.result.brightness.description
inside the Text
Currently not getting data from API to display.
Current code:
import SwiftUI
struct Response: Decodable {
var content: [Result]
}
struct Result : Decodable {
var code: String
var fire: String
var name: String
var police: String
var medical: String
}
struct ContentView: View {
#State private var content = [Result]()
var body: some View {
List(content, id: \.code) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.medical)
}
}
.onAppear(perform: loadData)
}
func loadData() {
let url = URL(string: "https://emergency-phone-numbers.herokuapp.com/country/gb")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { print(error); return }
do {
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.content = decodedResponse.content
}
} catch {
print(error)
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Current error:
2021-01-21 19:21:07.094582+0000 iTunes API[39924:1098972] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
keyNotFound(CodingKeys(stringValue: "content", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "content", intValue: nil) ("content").", underlyingError: nil))
Please advise. Thanks in advance.
Please note that the content from https://emergency-phone-numbers.herokuapp.com/country/gb is:
{"code":"GB","fire":"999","police":"999","name":"United Kingdom","medical":"999"}
The problem is that there's no content on the response that you're getting, so the JSONDecoder is failing to parse the response. It seems the response you get contains content that fits on Result, so if you change your code to parse that, it works fine:
import SwiftUI
struct Response: Decodable {
var content: [Result]
}
struct Result : Decodable {
var code: String
var fire: String
var name: String
var police: String
var medical: String
}
struct ContentView: View {
#State private var content = [Result]()
var body: some View {
List(content, id: \.code) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.medical)
}
}
.onAppear(perform: loadData)
}
func loadData() {
let url = URL(string: "https://emergency-phone-numbers.herokuapp.com/country/gb")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { print(error); return }
do {
let decodedResponse = try JSONDecoder().decode(Result.self, from: data!)
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.content = [decodedResponse]
}
} catch {
print(error)
}
}.resume()
}
}
I guess, in the end, you probably wanted a collection of Results to fit on the content, so you might need to change something on that API of yours to return that collection, then your code would work fine.