No value associated with key CodingKeys while trying to get data from GitHub API in Xcode app
First of all you don't need CodingKeys
and the init
method.
Second of all use structs, not classes.
If you want to decode the repositories you have to start with the root object, the repositories are in the array for key items
struct Root : Decodable {
let items : [Repository]
}
struct Repository: Decodable {
let id: Int
let name, fullName: String
let owner : Owner
}
struct Owner : Decodable {
let login : String
}
Another issue is that owner
is also a Dictionary which becomes another struct.
To get rid of the CodingKeys add the .convertFromSnakeCase
strategy which translates full_name
into fullName
.
let session = URLSession.shared
let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20")!
let task = session.dataTask(with: url) { data, response, error in
// Check the response
print(response)
// Check if an error occured
if let error = error {
// HERE you can manage the error
print(error)
return
}
// Serialize the data into an object
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data! )
print(json)
} catch {
print("Error during JSON serialization:", error)
}
}
task.resume()
Having trouble decoding JSON file from Github api swift 5
Your code works well for me, MoreInfo
is decoded as required.
Using macos 12.2 Beta, Xcode 13.2,
targets ios 15 and macCatalyst 12. Tested on real devices.
You of course need to deal with the asynchronous function, as @Larme mentioned.
Try this approach:
func getMoreInfo(completion: @escaping(MoreInfo?) -> ()) {
if let url = URL(string: "https://api.github.com/repos/allegro/typescript-strict-plugin"){
URLSession.shared.dataTask(with: url) { data, responde, error in
if let data = data {
do {
let moreInfo = try JSONDecoder().decode(MoreInfo.self, from: data)
completion(moreInfo)
} catch let error {
print(error)
completion(nil) // todo deal with errors
}
}
}.resume()
}
}
and use it like this:
getMoreInfo() { info in
print("\n-------> info: \(info)")
}
OpenWeather One Call API coord not loading
OpenWeatherMap is well documented, please read the docs.
There are multiple different APIs, I guess your struct represents another API data.
The basic OneCall root object (omitting minutely
, daily
, hourly
and alerts
) is
struct OneCall: Decodable {
let lat, lon: Double
let timezone : String
let timezoneOffset : Int
let current: Current
}
And the descendants Current
and Weather
are
struct Current: Decodable {
let dt, sunrise, sunset : Date
let temp, feelsLike, dewPoint, uvi, windSpeed : Double
let pressure, humidity, clouds, visibility, windDeg : Int
let windGust : Double?
let weather : [Weather]
}
struct Weather: Decodable, Identifiable, CustomStringConvertible {
let id : Int
let main, description, icon : String
}
dt
, sunrise
and sunset
are decoded as Date
and the snake_case keys are converted to camelCase by applying appropriate decoding strategies.
I highly recommend to build the URL with URLComponents
and URLQueryItems
, apiKey
is the API key constant.
let apiKey = "•••••••••"
enum WeatherManagerError : Error { case missingURL, badResponse }
class WeatherManager {
// HTTP request to get the current weather depending on the coordinates we got from LocationManager
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> OneCall {
var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/onecall")!
let queryItems = [URLQueryItem(name: "appid", value: apiKey),
URLQueryItem(name: "lat", value: "\(latitude)"),
URLQueryItem(name: "lon", value: "\(longitude)"),
URLQueryItem(name: "units", value: "metric")]
urlComponents.queryItems = queryItems
guard let url = urlComponents.url else { throw WeatherManagerError.missingURL }
let (data, response) = try await URLSession.shared.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw WeatherManagerError.badResponse }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
return try decoder.decode(OneCall.self, from: data)
}
}
Errors while trying to create a list based on JSON in SwiftUI
try something like this approach, to get you data from github. Works very well for me:
class NetworkingManager: ObservableObject{
@Published var gitHubList: [Item] = []
init() {
loadData()
}
func loadData() {
guard let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(Root.self, from: data)
DispatchQueue.main.async {
self.gitHubList = response.items
}
} catch {
print("error: \(error)")
}
}.resume()
}
}
struct Root: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Item: Identifiable, Codable {
let keysURL, statusesURL, issuesURL: String
let id: Int
let url: String
let pullsURL: String
// ... more properties
enum CodingKeys: String, CodingKey {
case keysURL = "keys_url"
case statusesURL = "statuses_url"
case issuesURL = "issues_url"
case id
case url
case pullsURL = "pulls_url"
}
}
struct ContentView: View {
@StateObject var netManager = NetworkingManager()
var body: some View {
List {
ForEach(netManager.gitHubList) { item in
Text(item.url)
}
}
}
}
JSON decoding struct has a key, but Xcode calls that there is no member with that key in SwiftUI
netManager.repos
is an array of Repository
, it does not have a name, as the error tells you. You must select a single repo
and get the name from that.
For example
Text("\(netManager.repos.first?.name ?? "no name")")
JSONDecoder fails to find key that is present
Try changing sshURL
to sshUrl
. The keyDecodingStartegy
will transform sshURL
into ssh_URL
, which doesn't match your key. sshUrl
will be transformed into ssh_url
, which will match your key.
With JSONDecoder in Swift 4, can missing keys use a default value instead of having to be optional properties?
Approach that I prefer is using so called DTOs - data transfer object.
It is a struct, that conforms to Codable and represents the desired object.
struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}
Then you simply init the object that you want to use in the app with that DTO.
class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}
var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}
This approach is also good since you can rename and change final object however you wish to.
It is clear and requires less code than manual decoding.
Moreover, with this approach you can separate networking layer from other app.
JSONDecoder fails to parse a list of maps
What's wrong with your code?
try?
That's the main culprit.
Why? You are ignoring the error thrown by the decode(_:from:)
. You are ignoring the error that could give you the exact reason or at least a hint on why it failed. Instead, write a proper do { try ... } catch { ... }
.
So:
guard let data2 = data,
let tops = try? decoder.decode(TopResponse.self, from:
data2) else { return }
print(tops.results[4].given)
=>
guard let data2 = data else { return }
do {
let tops = try decoder.decode(TopResponse.self, from: data2)
print(tops.results[4].given)
} catch {
print("Got error while parsing: \(error)")
print("With response: \(String(data: data2, encoding: .utf8))") //Just in case because I've seen plenty of code where expected JSON wasn't the one received: it was an error, doc changed, etc...
}
Output for the first print:
$>Got error while parsing: keyNotFound(CodingKeys(stringValue: "results", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"results\", intValue: nil) (\"results\").", underlyingError: nil))
Fix:
struct TopResponse: Codable {
let results: [Top]
enum CodingKeys: String, CodingKey {
case results = "data"
}
}
Or rename results
with data
.
Then, next error:
$>Got error while parsing: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "avg_score", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Extract from JSON:
"avg_score": 20.4
It's not a String
(the value it's not between double quotes), that's a Double
.
Fix:
let avg_score: String?
=>
let avg_score: Double?
Related Topics
How to Demonstrate a Zombie Object in Swift
How to Read a Property List from Data in Swift 3
Function That Takes a Protocol and a Conforming Class (!) Instance as Parameters
Swift Task Continuation Misuse: Leaked Its Continuation - for Delegate
Swift Protocol to Require Properties as Protocol
Swift Can't Infer Generic Type When Generic Type Is Being Passed Through a Parameter
Safe to Signal Semaphore Before Deinitialization Just in Case
How to Handle Two Different Types in an Array in Swift for a Uitableview
Xcode 6, Swift - Read Standard Input (Console) to String
Swift & Firebase - How to Store More User Data Other Than Email and Password
Why Is the Swift Compiler Marking This as an Error
Extend Generic Array<T> to Adopt Protocol
What Would Be a Proper Storyboard Example of Combining Nav Bars and Tab Bars in One App
How to Implement iOServicematchingcallback in Swift
Appearance Proxies/Ui_Appearance_Selector in Swift
Building a Spritekit/Gamekit Leaderboard Within a Specific Scene