'"Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil; GitHub repo showing app; SwiftUI
I need to create an app that will be able to return all the repositories that a GitHub user owns.
I created an app that contains of 3 files: CONTENT VIEW
import SwiftUI
struct ContentView: View {
@StateObject var netManager = NetworkingManager()
var body: some View {
List {
ForEach(netManager.owner) { item in
Text(item.reposUrl)
}
}
}
}
API KEYS
import Foundation
struct Root : Decodable, Identifiable {
let id: Int
let items : [Repository]
}
struct Repository: Decodable, Identifiable {
let id: Int
let name, fullName: String
let owner : Owner
}
struct Owner : Decodable, Identifiable {
let id: Int
let reposUrl : String
}
DECODERS (since I know I should need another one later, unless I can abstract this one enough)
class NetworkingManager: ObservableObject{
@Published var owner = [Owner]()
init() {
loadData()
}
func loadData() {
guard let url = URL(string: "https://api.github.com/users/jacobtoye/repos") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(Owner.self, from: data)
} catch {
print("error: \(error)")
}
}.resume()
}
}
The code runs fine, but I don't get any results (the first screen is blank) and I would like to see a list of the chosen user repos there. Could you please help me decode the dictionary?
I also wonder if the problem doesn't lie with that I didn't use convertFromSnakeCase key Decoding Strategy either, but I don't know how to put it there when the JSONDecoder is wrapped in a constant.
Solution 1:[1]
for a minimalist working example code, try this:
struct Repository: Decodable, Identifiable {
let id: Int
let name, fullName: String
let owner: Owner
enum CodingKeys: String, CodingKey {
case id, name, owner
case fullName = "full_name" // <-- here
}
}
struct Owner : Decodable, Identifiable {
let id: Int
let reposUrl : String
enum CodingKeys: String, CodingKey, CaseIterable {
case id
case reposUrl = "repos_url" // <-- here
}
}
class NetworkingManager: ObservableObject{
@Published var owner = [Owner]()
init() {
loadData()
}
func loadData() {
guard let url = URL(string: "https://api.github.com/users/jacobtoye/repos") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
DispatchQueue.main.async { // <-- here
do {
let repos = try JSONDecoder().decode([Repository].self, from: data) // <-- here
repos.forEach{ self.owner.append($0.owner) }
} catch {
print("error: \(error)")
}
}
}.resume()
}
}
struct ContentView: View {
@StateObject var netManager = NetworkingManager()
var body: some View {
List {
ForEach(netManager.owner) { item in
Text(item.reposUrl)
}
}
}
}
This should give you a list of "https://api.github.com/users/jacobtoye/repos"
because that is what the data consist of.
EDIT-1: to list all repos
class NetworkingManager: ObservableObject{
@Published var repos = [Repository]() // <-- here repos
init() {
loadData()
}
func loadData() {
guard let url = URL(string: "https://api.github.com/users/jacobtoye/repos") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
DispatchQueue.main.async { // <-- here
do {
self.repos = try JSONDecoder().decode([Repository].self, from: data) // <-- here
} catch {
print("error: \(error)")
}
}
}.resume()
}
}
struct ContentView: View {
@StateObject var netManager = NetworkingManager()
var body: some View {
List {
ForEach(netManager.repos) { repo in
VStack {
Text(repo.fullName).foregroundColor(.blue)
Text(repo.owner.reposUrl)
}
}
}
}
}
Solution 2:[2]
One small amendment, after my recent project, in the solution suggested above it is better to use [weak self] if no more parameters are passed in the closure:
guard let url = URL(string: "https://api.github.com/users/jacobtoye/repos") else { return }
URLSession.shared.dataTask(with: url) { [weak self] (data, _, _) in
guard let self = self, let data = data else { return }
instead of
guard let url = URL(string: "https://api.github.com/users/jacobtoye/repos") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
Thanks to that approach, you can prevent memory leak, which might make your app slower at the end of the day.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Swantewit |