Ckatalay
Ckatalay

Reputation: 13

Decoding nested objects in JSON

I'm trying to fetch some data using YouTube JSON API, JSON looks like this:

{
  "kind": "youtube#channelListResponse",
  "etag": "AVEF8yG4pQMKfzZjMyy1rfVtFXA",
  "pageInfo": {
    "resultsPerPage": 1
  },
  "items": [
    {
      "kind": "youtube#channel",
      "etag": "LpEHq7VFVl1gDbt97Gf4ObyFn0U",
      "id": "UCBJycsmduvYEL83R_U4JriQ",
      "statistics": {
        "viewCount": "2084223845",
        "commentCount": "0",
        "subscriberCount": "12100000",
        "hiddenSubscriberCount": false,
        "videoCount": "1272"
      }
    }
  ]
}

And here is my code:

import Foundation
struct StatResponse: Decodable {
    let items: Items
}
struct Items: Decodable {
    var statistics: Stats
}
struct Stats: Decodable {
    let subscriberCount: Double
}
enum APIError: Error {
    case wrongURL
    case decodingError
    case noData
}
extension URL {
    static func url() -> URL? {
        guard let url = URL(string: "https://www.googleapis.com/youtube/v3/channels?part=statistics&id=UCBJycsmduvYEL83R_U4JriQ&key=API_KEY") else {
            return nil
        }
        return url
    }
}
class StatService {
    func getStats(completion: @escaping (Result<Items?,APIError>) -> Void) {
        guard let url =  URL.url() else {
             return completion(.failure(.wrongURL))
        }
          URLSession.shared.dataTask(with: url) {
            data, response, error in
            guard let data = data, error == nil else {
                return completion(.failure(.noData))
            }
            let statResponse = try? JSONDecoder().decode(StatResponse.self, from: data)
            if let statResponse = statResponse {
                completion(.success(statResponse.items))
                } else {
                    return completion(.failure(.decodingError))
            }
        }.resume()
    }
}
class StatsViewModel: ObservableObject {
    @Published private var items: Items?
    var subCount: Double {
        guard let subscriberCount = items?.statistics.subscriberCount else {
            return 0
        }
        return subscriberCount
    }
    func fetchStats() {
        StatService().getStats {
            result in switch result {
            case .success(let Items):
                DispatchQueue.main.async {
                    self.items = Items
                }
            case .failure(_ ):
                print("fail")
            }
        }
    }
}

I have no idea what is going wrong, it gives 0 as the result. I tried various things like self.items?.statistics = Items.statistics instead of self.items = Items because I'm trying to get the data I'm trying to fetch is under statistics but it also didn't work and got an error saying "Value of optional type 'Stats?' must be unwrapped to a value of type 'Stats'" and I don't know what to do, I can fetch non-nested objects pretty easily in Swift but I can't nested ones. I'm kind of a beginner to both Swift and programming in general so if that's a stupid question sorry for taking your time.

Upvotes: 0

Views: 68

Answers (1)

Claudio
Claudio

Reputation: 5203

You have an error on your model classes, the items property is an array, and you are trying to decode it as an object, change StatResponse to:

struct StatResponse: Decodable {
    let items: [Items]
}

Also change the subscriberCount to String on the Stats struct:

struct Stats: Decodable {
    let subscriberCount: String
}

Then you will have to change the way you are accessing the data, because now you're accessing an array. You can use the first property of an array which will return the first element of and array like this:

if let item = statResponse.items.first {
    completion(.success(item))
} else {
    return completion(.failure(.decodingError))
}

Upvotes: 2

Related Questions