JSON list not visible in SwiftUI - json

I’m trying to loading the content from a JSON that I created, but unfortunately it doesn’t work.
As you can see from the code below, the only thing I see is “Error” printed in the console.
I also tried with other JSONs from the web, changing the struct accordingly, and it works, but not with the JSON I created.
Here is the code:
import SwiftUI
import URLImage
struct Busso: Codable, Identifiable {
public var id: UUID
public var titolo: String
public var autore: String
public var testo: String
public var data: String
public var extra1: String
public var extra2: String
public var foto: String
public var fotoUrl: String
}
class FetchBusso: ObservableObject {
#Published var Bussos = [Busso]()
init() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([Busso].self, from: bussoData)
DispatchQueue.main.async {
self.Bussos = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct CategoryView: View {
#ObservedObject var fetch = FetchBusso()
var body: some View {
VStack {
List(fetch.Bussos) { todo in
VStack(alignment: .leading) {
Text(todo.titolo)
}
}
}
}
}
struct CategoryView_Previews: PreviewProvider {
static var previews: some View {
CategoryView()
}
}
And here is the link to the JSON I want to use:
https://geniuspointfrezza.altervista.org/index.php?json=1
Waiting for your advice, thank you!

Json I'm throwing the site to help you read. You can get help at the point where you get stuck.shttps: //app.quicktype.io

The issue is
public var id: UUID your JSON is public var id: String
catch {
print(error)
}
is more useful than
print("Error")

Your Busso struct is not a proper decoder for the given JSON. As I gave you the link to a prior one of my answers that explains how to get a struct that will parse your data. You really need to review that, but for the sake of putting this to bed, the struct should be:
// MARK: - GeniuspointfrezzaDecoder
struct GeniuspointfrezzaDecoder: Codable {
let id: String?
let titolo: String?
let autore: String?
let testo: String?
let data: String?
let extra1: String?
let extra2: String?
let creazione: String?
let foto: String?
let fotoURL: String?
enum CodingKeys: String, CodingKey {
case id, titolo, autore, testo, data, extra1, extra2, creazione, foto
case fotoURL = "fotoUrl"
}
}
I made the elements all optional since you don't actually know if the server response will contain all of the elements. Another issue you had was declaring the id in your decoder to be a UUID, when the JSON response is clearly a string. Changing the response data to your app's needs has to be done AFTER decoding the response. The decoder is NOT your Busso struct. It is a decoder that gives you the data to put into your Busso struct.

Related

How do I parse this JSON API data in SwiftUI?

A beginner here.
I'm having some trouble trying to parse this json url in SwiftUI
Here is what I've tried:
import SwiftUI
struct PriceData2: Decodable {
var id: Int
var last: Double
var lowestAsk: Double
var highestBid: Double
var percentChange: Double
var baseVolume: Double
var quoteVolume: Double
var isFrozen: Int
var high24hr: Double
var low24hr: Double
var change: Double
var prevClose: Double
var prevOpen: Double
}
struct ContentView: View {
#State var priceData2: PriceData2?
var body: some View {
if #available(iOS 15.0, *) {
VStack(){
Text("\(priceData2?.id ?? 0)")
}.onAppear (perform: loadData2)
} else {
// Fallback on earlier versions
}
}
public func loadData2() {
guard let url = URL(string: "https://api.bitkub.com/api/market/ticker?sym=THB_BTC") else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {return}
if let decodedData = try? JSONDecoder().decode(PriceData2.self, from: data){
DispatchQueue.main.async {
self.priceData2 = decodedData
}
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I think it has something to do with the THB_BTC property of the dictionary that I haven't dealt with but I'm not totally sure of.
Any help with this is much appreciated.
Thanks
What is missing when decoding is a top level type to hold the price data and there are two ways to solve this, either by using a top level struct or by using a dictionary.
Since the top level key, THB_BTC, seems to be the "symbol" argument from the query and thus might change a top level dictionary is the best alternative here
do {
let result = try JSONDecoder().decode([String: PriceData].self, from: data)
if let priceData = result.values.first { //or = result["THB_BTC"]
print(priceData)
}
} catch {
print(error)
}
Just for completeness, here is the variant with the top level struct
struct Result: Decodable {
enum CodingKeys: String, CodingKey {
case data = "THB_BTC"
}
let data: PriceData
}
let result = try JSONDecoder().decode(Result.self, from: data)
print(result.data)

json file is missing/ struct is wrong

I have been trying to get this code to work for like 6 hours. I get the error: "failed to convert The data couldn’t be read because it is missing." I don't know while the File is missing is there something wrong in my models(structs). Do I need to write a struct for very json dictionary? Currently I have only made those JSON dictionaries to a struct, which I actually need. The full JSON file can be found at https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00 . I want to be able to print the time of the sunrise, sunset and solar noon as well as the elevation of the sun at solar noon. It's currently 1 am and I am desperate. Good Night!
class ViewController: NSViewController {
#IBOutlet weak var sunriseField: NSTextField!
#IBOutlet weak var sunsetField: NSTextField!
#IBOutlet weak var daylengthField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00"
getData(from: url)
// Do any additional setup after loading the view.
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: MyTime?
do {
result = try JSONDecoder().decode(MyTime.self, from: data)
}
catch {
print("failed to convert \(error.localizedDescription)")
}
guard let json = result else {
return
}
let sunrise1 = json.sunrise.time
DispatchQueue.main.async { [weak self] in
self?.sunriseField.stringValue = sunrise1
}
print(json)
})
task.resume()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
struct MyData : Codable {
let location : Location
let meta : Meta
}
struct MyTime : Codable {
let solarnoon : Solarnoon
let sunset : Sunset
let sunrise : Sunrise
}
struct Location : Codable {
let height : String
let time : [MyTime]
let longitude : String
let latitude : String
}
struct Meta : Codable {
let licenseurl : String
}
struct Solarnoon : Codable {
let desc : String
let time : String
let elevation : String
}
struct Sunrise : Codable {
let desc : String
let time : String
}
struct Sunset : Codable {
let time : String
let desc : String
}
You don't really have a SwiftUI class, but that is a different question. I am going to work on fixing getData(). I have tried to comment it extensively, but let me know if you have any questions.
private func getData(from url: String) {
// Personally I like converting the string to a URL to unwrap it and make sure it is valid:
guard let url = URL(string: urlString) else {
print("Bad URL: \(urlString)")
return
}
let config = URLSessionConfiguration.default
// This will hold the request until you have internet
config.waitsForConnectivity = true
URLSession.shared.dataTask(with: url) { data, response, error in
// A check for a bad response
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
print("Bad Server Response")
return
}
if let data = data {
// You can print(data) here that will shown you the number of bytes returned for debugging.
//This work needs to be done on the main thread:
DispatchQueue.main.async {
let decoder = JSONDecoder()
if let json = try? decoder.decode(MetDecoder.self, from: data){
print(json)
//At this point, you have your data in a struct
self.sunriseTime = json.dailyData?.solarData?.first?.sunrise?.time
}
}
}
}
.resume()
}
With regard to your structs, you only need them for the data you are trying to parse. If you don't need it, don't worry about it. I would make this a separate class named MetDecoder or something that makes sense to you and indicates the decoder for your JSON. You will also note that I changed the names of some of the variables. You can do that so long as you use a CodingKeys enum to translate your JSON to your struct as in the case of dailyData = "location", etc. This is ugly JSON, and I am not sure why the Met decided everything should be a string, but this decoder is tested and it works:
import Foundation
// MARK: - MetDecoder
struct MetDecoder: Codable {
let dailyData: DailyData?
enum CodingKeys: String, CodingKey {
case dailyData = "location"
}
}
// MARK: - Location
struct DailyData: Codable {
let solarData: [SolarData]?
enum CodingKeys: String, CodingKey {
case solarData = "time"
}
}
// MARK: - Time
struct SolarData: Codable {
let sunrise, sunset: RiseSet?
let solarnoon: Position?
let date: String?
enum CodingKeys: String, CodingKey {
case sunrise, sunset, solarnoon, date
}
}
// MARK: - HighMoon
struct Position: Codable {
let time: String?
let desc, elevation, azimuth: String?
}
// MARK: - Moonrise
struct RiseSet: Codable {
let time: String?
let desc: String?
}
You should see what the National Weather Service does to us in the US to get the JSON. Lastly, when working on JSON I find the following pages VERY helpful:
JSON Formatter & Validator which will help you parse out the wall of text that gets returned in a browser, and
quicktype which will parse JSON into a programming language like Swift. I will warn you that the parsing can give some very ugly structs in Swift, but it gives you a nice start. I used both sites for this answer.
Apple's new framework, Combine, helps to simplify the code needed for async fetch requests. I have used the MetDecoder in #Yrb's response above (you can accept his answer) and altered the getData() function. Just make sure you import Combine at the top.
import Combine
var sunriseTime: String?
var sunsetTime: String?
var solarNoonTime: String?
var solarNoonElevation: String?
func getData() {
let url = URL(string: "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00")!
URLSession.shared.dataTaskPublisher(for: url)
// fetch on background thread
.subscribe(on: DispatchQueue.global(qos: .background))
// recieve response on main thread
.receive(on: DispatchQueue.main)
// ensure there is data
.tryMap { (data, response) in
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
// decode JSON data to MetDecoder
.decode(type: MetDecoder.self, decoder: JSONDecoder())
// Handle results
.sink { (result) in
// will return success or failure
print("completion: \(result)")
} receiveValue: { (value) in
// if success, will return MetDecoder
// here you can update your view
print("value: \(value)")
if let solarData = value.dailyData?.solarData?.first {
self.sunriseTime = solarData.sunrise?.time
self.sunsetTime = solarData.sunset?.time
self.solarNoonTime = solarData.solarnoon?.time
self.solarNoonElevation = solarData.solarnoon?.elevation
}
}
// After recieving response, the URLSession is no longer needed & we can cancel the publisher
.cancel()
}
from the json data (2nd entry), looks like you need at least:
struct MyTime : Codable {
let solarnoon : Solarnoon?
let sunset : Sunset?
let sunrise : Sunrise?
}
and you need:
var result: MyData?
do {
result = try JSONDecoder().decode(MyData.self, from: data)
}
catch {
print("----> error failed to convert \(error)")
}

Decode JSON using Codable and then populate my SwiftUI

I am new to Swift and SwiftUI. I am currently teaching myself how to code (loving it!) through hackingwithswift.com I am currently on Day 60 and I am stuck and not sure what to do from here.
The challenge is to decode some information using Codable and populate SwiftUI.
I created a struct to match the JSON, but when I go to run the app, I keep getting my error "Fetch Failed: Unknown Error" and therefore my UI won't update.
Would someone glance at my code and provide any pointers on where I am going wrong and possibly why? Thank you so much for any suggestions and help, it is much appreciated! Code is posted below.
Cody
import SwiftUI
struct Response: Codable {
var results: [User]
}
struct User: Codable, Identifiable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: String
let tags: [String]
struct FriendRole: Codable {
let id: String
let name: String
}
let friend: [FriendRole]
}
struct ContentView: View {
#State private var results = [User]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.address)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.results = decodedResponse.results
}
return
}
}
print("Fetch Failed: \(error?.localizedDescription ?? "Unkown Error").")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm also new to swiftUI this is how I managed to make it work by using Observable Objects
Here's the structures Since in the json file there is a [ in the very beginning you do not have to create a struct that has a User array you could create a struct and then create a variable of that struct type as an array
here's how I did it
I created separate files for instance for the structures i had a different file for them
here's what I have for my structure file
import Foundation
struct User : Codable, Identifiable {
let id : String
let isActive : Bool
let name : String
let age : Int
let company : String
let email : String
let address : String
let about : String
let registered : String
let tags = [String]()
let friends = [Friends]()
}
struct Friends : Codable {
let id : String
let name : String
}
I created another file for the observable object class
class JsonChannel : ObservableObject {
#Published var retVal = [User]()
func getInfo () {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {return}
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let data = data {
DispatchQueue.main.async {
do {
self.retVal = try JSONDecoder().decode([User].self, from: data)
}
catch {
print(error)
}
}
}
}.resume()
}
}
and here's what i have for my contentView file
import SwiftUI
struct ContentView : View {
#ObservedObject var info = JsonChannel()
var body: some View {
VStack {
Button(action: {
self.info.getInfo()
}) {
Text("click here to get info")
}
List {
ForEach (self.info.retVal) { item in
VStack {
Text("\(item.name)")
Text("\(item.address)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How do I fix error: "Expected to decode Dictionary<String, Any> but found an array instead" in this code?

import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//API Key: 5ca10b2d20a545099a108a3aeceb329c
//url: https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c
// model
struct Source: Decodable {
var id: String
var name: String
}
struct Articles: Decodable {
var source: Source
var author: String
var title: String
var description: String
var url: String
var urlToImage: String
var publishedAt: String
var content: String
}
struct JSONDescription: Decodable {
var status: String
var totalResults: Int
var articles: Articles
}
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
//let dataAsString = String(data: data, encoding: .utf8)
// print(dataAsString)
do {
let jsonDescription = try JSONDecoder().decode(JSONDescription.self, from: data)
print(jsonDescription.totalResults)
}
catch let jsonError {
print("Json Error:", jsonError)
}
}.resume()
}
}
What I expected to see was the JSON data returned here: https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c
You can put it into this formatter to make is readable: https://jsonformatter.curiousconcept.com
I thought I did everything correctly. Have I built my model wrong? I'm not sure how to fix this error.
So, with the help of the error returned, and looking at the data, it seems that "articles" is an array.
Here's what I'd try:
Rename your Articles struct to Article
Change JSONDescription's articles property from Articles to [Article]
I didn't notice any other errors in the data mapping, but hopefully this gets you closer.

Syntax for accessing struct property with enum type in JSON model

I am trying to access a url string contained within some JSON data.
The string is contained within the "urls" array with type "detail" as can be seen below.
JSON DATA
I used quicktype to construct my model as below:
struct Response: Codable {
let data: DataClass
}
struct DataClass: Codable {
let results: [Result]
}
struct Result: Codable {
let name: String
let description: String
let thumbnail: Thumbnail
let urls: [URLElement]
}
struct Thumbnail: Codable {
let path: String
let thumbnailExtension: Extension
enum CodingKeys: String, CodingKey {
case path
case thumbnailExtension = "extension"
}
}
enum Extension: String, Codable {
case jpg = "jpg"
}
struct URLElement: Codable {
let type: URLType
let url: String
}
enum URLType: String, Codable {
case comiclink = "comiclink"
case detail = "detail"
case wiki = "wiki"
}
I have tried accessing it by declaring it like so...
var urlelement: URLElement!
override func viewDidLoad() {
super.viewDidLoad()
let detailurl = urlelement.url
print(detailurl)
... but it always returns an empty string. Any suggestions will be most welcome. Thanks!
First Download the JSON then user JSONDecoder
let url = URL(string: "your url")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("error \(error.localizedDescription)")
return
}
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(Response.self, from: data)
// use this
response?.data.results.forEach({ (rsl) in
rsl.urls.forEach({ (element) in
print(element.type, element.url)
})
})
// or this one
for rsl in response!.data.results {
for element in rsl.urls {
print(element.type, element.url)
}
}
} catch let error {
print("error while decoding the json \(error.localizedDescription)")
}
}.resume()