简体   繁体   中英

Empty List from JSON in SwiftUI

I'm having some trouble fetching data from the PiHole API;

This is the JSON format (from the url http://pi.hole/admin/api.php?summary ):

{
  "domains_being_blocked": "1,089,374",
  "dns_queries_today": "34,769",
  "ads_blocked_today": "11,258",
  "ads_percentage_today": "32.4",
  "unique_domains": "9,407",
  "queries_forwarded": "17,972",
  "queries_cached": "5,539",
  "clients_ever_seen": "35",
  "unique_clients": "23",
  "dns_queries_all_types": "34,769",
  "reply_NODATA": "1,252",
  "reply_NXDOMAIN": "625",
  "reply_CNAME": "10,907",
  "reply_IP": "21,004",
  "privacy_level": "0",
  "status": "enabled",
  "gravity_last_updated": {
    "file_exists": true,
    "absolute": 1588474361,
    "relative": {
      "days": "0",
      "hours": "14",
      "minutes": "18"
    }
  }
}

This is my code:

ContentView.swift

import SwiftUI

struct NetworkController {
    static func fetchData(completion: @escaping (([PiHole.Stat]) -> Void)) {
       if let url = URL(string: "http://pi.hole/admin/api.php?summary") {
          URLSession.shared.dataTask(with: url) { (data, response, error) in

            if let data = data {
                let stat = try? JSONDecoder().decode(PiHole.self, from: data)
                completion(stat?.stats ?? [])
            }
          }.resume()
       }
    }
}

class ContentViewModel: ObservableObject {
    @Published var messages: [PiHole.Stat] = []

    func fetchData() {
        NetworkController.fetchData { messages in
            DispatchQueue.main.async {
                self.messages = messages
            }
        }
    }
}

struct ContentView: View {

    @ObservedObject var viewModel = ContentViewModel()

    var body: some View {
        List {
            ForEach(viewModel.messages, id: \.self) { stat in
                Text(stat.domains_being_blocked)
            }
        }.onAppear{
            self.viewModel.fetchData()
        }
    }
}

Data.swift

struct PiHole: Decodable {
    var stats: [Stat]

    struct Stat: Decodable, Hashable {
        var domains_being_blocked: String
        var ads_percentage_today: String
        var ads_blocked_today: String
        var dns_queries_today: String
    }
}

Everything seems okay, no errors, yet when I run it, the simulator only shows an empty list

In Playground I can retrieve those data just fine:

import SwiftUI

struct PiHoleTest: Codable {
    let domains_being_blocked: String
    let ads_blocked_today: String
}

let data = try! Data.init(contentsOf: URL.init(string: "http://pi.hole/admin/api.php?summary")!)

do {
    let decoder: JSONDecoder = JSONDecoder.init()
    let user: PiHoleTest = try decoder.decode(PiHoleTest.self, from: data)

    print("In Blocklist \(user.domains_being_blocked)")
    print("Blocked Today: \(user.ads_blocked_today) ")


} catch let e {
    print(e)
}

The Output:

In Blocklist 1,089,374
Blocked Today: 11,258

What am I doing wrong? Or better, is there another way to fetch these stats?

Thanks in Advance!

The issue was related to the structure. Your JSON decoded were not an array. So PiHole struct was unnecessary. I can tested and this code is working now.

import SwiftUI

struct NetworkController {
    static func fetchData(completion: @escaping ((Stat) -> Void)) {
        if let url = URL(string: "http://pi.hole/admin/api.php?summary") {
            let request = URLRequest(url: url)
            URLSession.shared.dataTask(with: request) { (data, response, error) in
                do {
                    if let data = data {
                        let stat = try JSONDecoder().decode(Stat.self, from: data)
                        DispatchQueue.main.async() {
                            completion(stat)
                        }
                        return
                    } else {
                        print("Error Found")
                    }
                } catch let error as NSError {
                    print(error.localizedDescription)
                }
            }.resume()
        }
    }
}

class ContentViewModel: ObservableObject {
    @Published var stat: Stat? = nil
    func fetchData() {
        NetworkController.fetchData { stat in
            self.stat = stat
        }
    }
}

struct TestView: View {
    @ObservedObject var viewModel = ContentViewModel()
    var body: some View {
        List {
            Text(viewModel.stat?.domains_being_blocked ?? "No Data")
            Text(viewModel.stat?.ads_blocked_today ?? "No Data")
            Text(viewModel.stat?.ads_percentage_today ?? "No Data")
            Text(viewModel.stat?.dns_queries_today ?? "No Data")
        }.onAppear{
            self.viewModel.fetchData()
        }
    }
}

struct Stat: Decodable, Hashable {
    var domains_being_blocked: String
    var ads_percentage_today: String
    var ads_blocked_today: String
    var dns_queries_today: String
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM