I'm building a Swift app and testing in an Xcode Playground. Calling the NYTimes Search API and trying to store its response in a struct. The code executes cleanly and no errors appear (I am using a do, try, catch), but I cannot print any properties from the resulting object (print(json.status)).
My hunch is that something is fishy with this line but I'm not sure what since no errors are printing from the catch statement
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
Form the URL endpoint to make the API call:
func APICall() {
let APIKey = "MY_API_KEY_GOES_HERE_BUT_IT'S_A_SECRET"
let searchTerm = "A Super Bowl Sideshow: See the Ageless Man!"
// Remove the spaces and convert them to percents
guard let encodedSearchTerm = searchTerm.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
else {
print("Error encoding search term in URL")
return
}
let url = "https://api.nytimes.com/svc/search/v2/articlesearch.json?q=" + encodedSearchTerm + "&api-key=" + APIKey
getData(from: url)
}
Data Task:
func getData(from url: String) {
//I believe something is wrong with the following line but I'm not sure what it is
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("Error loading data")
return
}
var result: NYTSearchResponse?
do {
result = try JSONDecoder().decode(NYTSearchResponse.self, from: data)
} catch {
print(error)
}
guard let json = result else {
print("Error assigning result to json")
return
}
//Try to print these from the resulting object but these commands do not print
print(json.status)
print(json.response.docs[0].abstract)
})
task.resume()
}
My NYTSearchResponse struct which mirrors the NYT API JSON response. It's pretty complicated, but I pasted the json response into https://app.quicktype.io/ to build the struct.
// MARK: - Welcome
struct NYTSearchResponse: Codable {
let status, copyright: String
let response: Response
}
// MARK: - Response
struct Response: Codable {
let docs: [Doc]
let meta: Meta
}
// MARK: - Doc
struct Doc: Codable {
let abstract: String
let webURL: String
let snippet, leadParagraph, printSection, printPage: String
let source: String
let multimedia: [Multimedia]
let headline: Headline
let keywords: [Keyword]
let pubDate: Date
let documentType, newsDesk, sectionName, subsectionName: String
let byline: Byline
let typeOfMaterial, id: String
let wordCount: Int
let uri: String
enum CodingKeys: String, CodingKey {
case abstract
case webURL = "web_url"
case snippet
case leadParagraph = "lead_paragraph"
case printSection = "print_section"
case printPage = "print_page"
case source, multimedia, headline, keywords
case pubDate = "pub_date"
case documentType = "document_type"
case newsDesk = "news_desk"
case sectionName = "section_name"
case subsectionName = "subsection_name"
case byline
case typeOfMaterial = "type_of_material"
case id = "_id"
case wordCount = "word_count"
case uri
}
}
// MARK: - Byline
struct Byline: Codable {
let original: String
let person: [Person]
let organization: String?
}
// MARK: - Person
struct Person: Codable {
let firstname: String
let middlename: String?
let lastname: String
let qualifier, title: String?
let role, organization: String
let rank: Int
}
// MARK: - Headline
struct Headline: Codable {
let main: String
let kicker, contentKicker: String?
let printHeadline: String
let name, seo, sub: String?
enum CodingKeys: String, CodingKey {
case main, kicker
case contentKicker = "content_kicker"
case printHeadline = "print_headline"
case name, seo, sub
}
}
// MARK: - Keyword
struct Keyword: Codable {
let name, value: String
let rank: Int
let major: String
}
// MARK: - Multimedia
struct Multimedia: Codable {
let rank: Int
let subtype: String
let caption, credit: String?
let type, url: String
let height, width: Int
let legacy: Legacy
let subType, cropName: String
enum CodingKeys: String, CodingKey {
case rank, subtype, caption, credit, type, url, height, width, legacy, subType
case cropName = "crop_name"
}
}
// MARK: - Legacy
struct Legacy: Codable {
let xlarge: String?
let xlargewidth, xlargeheight: Int?
}
// MARK: - Meta
struct Meta: Codable {
let hits, offset, time: Int
}
I moved the code out of the playground and it works.
Related
I have several URLs and, accordingly, there is a data structure for each of them.
URLS:
case "Get Day":
return "time/get_day.php"
case "Get Time":
return "time/get_time.php"
case "Get Current Time":
return "user/get_current_time.php"
STRUCTS:
struct Day: Codable {
var status: Int? = nil
var error_message: String? = nil
var result: [Result]? = nil
}
struct Time: Codable {
let status: Int?
let error_message: String?
let result: [Result]?
struct Result: Codable {
let id: String
let startTime: String
let endTime: String
}
}
struct CurrentTime: Codable {
let status: Int?
let error_message: String?
let current_time: Int?
}
struct Result: Codable {
let id: String
let name_en: String
let name_ru: String
let name_kk: String
}
At the moment I have a parseJson () function. In which I can manually change the type of structure for parsing one by one. But I cannot think of how to do this so that I would not change anything in the code manually.
func parseJson(data: Data) {
let decoder = JSONDecoder()
do {
let parsedData = try decoder.decode(Day.self, from: data)
print(parsedData)
} catch {
print("Error parsing Json:\(error)")
}
}
Please, if you have an example or ideas, share with me.
// Generic function to decode any decodable struct
func parseJson<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
do {
let parsedData = try decoder.decode(T.self, from: data)
return parsedData
} catch {
return nil
}
}
// Usage
let someDay: Day? = parseJson(data: dayData)
let sometime: Time? = parseJson(data: timeData)
I'm calling the TMDb Api to get an array of movies by genre. I'm getting 'nil' back on many of the properties I want to access such as "vote_count", "poster_path" and "vote_average".
If I call the api in a browser I get all of the properties as expected.
Here's my model:
import Foundation
// MARK: - MovieList
struct MovieList: Codable {
let page: Int
let totalResults: Int?
let totalPages: Int?
let results: [Result]
enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
}
// MARK: - Result
struct Result: Codable {
let popularity: Double?
let voteCount: Int?
let video: Bool?
let posterPath: String?
let id: Int?
let adult: Bool?
let backdropPath: String?
let originalLanguage: OriginalLanguage?
let originalTitle: String?
let genreIDS: [Int]?
let title: String?
let voteAverage: Double?
let overview, releaseDate: String?
enum CodingKeys: String, CodingKey {
case popularity
case voteCount = "vote_count"
case video
case posterPath = "poster_path"
case id, adult
case backdropPath = "backdrop_path"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case title
case voteAverage = "vote_average"
case overview
case releaseDate = "release_date"
}
}
enum OriginalLanguage: String, Codable {
case en = "en"
case es = "es"
}
Here's the networking call:
func getMovieDetails(movie: Int, completion: #escaping (Result?) -> ()) {
guard let url = URL(string: "https://api.themoviedb.org/3/movie/157336?api_key=6228bff945f7bd2m18c04fc3839829c0") else {
fatalError("Invalid URL")
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: url) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let data = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let movieDetails = try decoder.decode(Result.self, from: data)
DispatchQueue.main.async {
completion(movieDetails)
print(movieDetails)
}
} catch let err {
print("Err", err)
}
}
// execute the HTTP request
task.resume()
}
}
And here is the response:
MovieList(page: 1, totalResults: nil, totalPages: nil, results: [QuickFlicks.Result(popularity: Optional(171.78), voteCount: nil, video: Optional(false), posterPath: nil, id: Optional(454626), adult: Optional(false), backdropPath: nil, originalLanguage: nil, originalTitle: nil, genreIDS: nil, title: Optional("Sonic the Hedgehog"), voteAverage: nil, overview: Optional("Based on the global blockbuster videogame franchise from Sega, Sonic the Hedgehog tells the story of the world’s speediest hedgehog as he embraces his new home on Earth. In this live-action adventure comedy, Sonic and his new best friend team up to defend the planet from the evil genius Dr. Robotnik and his plans for world domination."), releaseDate: nil)])
Any help would be appreciated. Thank you.
You are giving contradicting instructions to the decoder, first you have the CodingKeys enum that say for instance that the posterPath property should be read from the poster_path key but then you set keyDecodingStrategy = .convertFromSnakeCase which means that the decoder first translates the key poster_path to posterPath before trying to match the key to a property.
So either remove decoder.keyDecodingStrategy = .convertFromSnakeCase or remove the CodingKeys enum
I have the Url saved in the info.plist as such:
BASE_URL <-> String <-> $(BASE_URL)
and in my project's Build Settings, I added a user-defined setting as such:
BASE_URL http://data.nba.net
After setting this up, when I try to get the website into the url variable, the variable returns "". As I debug the issue, I don't see the website stored under that variable.
I am new to Swift and still learning so any comments on the way I have setup my structs will be appreciated as well.
import UIKit
struct sports_content: Decodable {
let sports_meta_expanded: sports_meta
let teams_expanded: teams
}
struct sports_meta: Decodable {
let date_time: String?
let season_meta_list: season_meta
}
struct season_meta: Decodable {
let calendar_date: Date
let season_year: Int?
let stats_season_year: Int?
let stats_season_id: Int?
let stats_season_stage: Int?
let roster_season_year: Int?
let schedule_season_year: Int?
let standings_season_year: Int?
let season_id: Int?
let display_year: String
let display_season: String
let season_stage: Int?
}
struct next: Decodable {
let url: String
}
struct teams: Decodable {
let season_year: year
let team_data: [team]
}
struct year: Decodable {
let season_year: Int?
}
struct team: Decodable {
let is_nba_team: Bool
let team_name: String
let team_nickname: String
let team_code: String
let team_abbrev: String
let city: String
let state: String
let team_short_name: String
let team_id: Int?
let conference: String
let division_id: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.infoDictionary?["BASE_URL"] as? String ?? ""
guard let convertedURL = URL(string: url) else {
return
}
URLSession.shared.dataTask(with: convertedURL) { (data, response, error) in
guard let data = data else {
return
}
do{
let dataSet = try JSONDecoder().decode(sports_content.self, from: data)
print(dataSet)
} catch {
print("JSONSerialization error:", error)
}
}.resume()
}
}
A build setting is used at build / compile time and not necessarily at run time.
To get your URL into the infoDictionary, you need to add it to the Info.plist file. Double click on your Info.plist to get the view open in your Xcode, then click "Add Value" under the Editor menu, then you can add BASE_URL as the key and your URL as the value.
Try using $(BASE_URL) as the value in your Info.plist and see if your build setting gets added in at build time. :)
I'm trying to receive data from a JSON link in Swift Playground on Mac, I've struct all the data, but I'm having issue trying to decode all of the data, receiving the error: "Referencing instance method 'decode(_:from:)' on 'Array' requires that 'Bicimia' conform to 'Decodable'"
I've already tries to add the Codable/Decodable option, and tried to change the URLSession respectively, but nothing has changed.
struct Bicimia {
let network: Network
}
struct Network {
let company: [String]
let href, id: String
let location: Location
let name: String
let stations: [Station]
}
struct Location {
let city, country: String
let latitude, longitude: Double
}
struct Station {
let emptySlots: Int
let extra: Extra
let freeBikes: Int
let id: String
let latitude, longitude: Double
let name, timestamp: String
}
struct Extra {
let extraDescription: String
let status: Status
}
enum Status {
case online
}
let url = "https://api.citybik.es/v2/networks/bicimia"
let urlOBJ = URL(string: url)
URLSession.shared.dataTask(with: urlOBJ!) {(data, response, error) in
do {
let res = try JSONDecoder().decode([Bicimia].self, from: data!)
print(res)
}
catch {
print(error)
}
}.resume()
To be Decodable all properties should be Decodable down the chain:
struct Bicimia: Decodable {
let network: Network // should be decodable
}
struct Network: Decodable {
let company: [String]
let href, id: String
let location: Location // should be decodable
let name: String
let stations: [Station] // should be decodable
}
struct Location: Decodable {
let city, country: String
let latitude, longitude: Double
}
struct Station: Decodable {
let emptySlots: Int
let extra: Extra // should be decodable
let freeBikes: Int
let id: String
let latitude, longitude: Double
let name, timestamp: String
}
struct Extra: Decodable {
let extraDescription: String
let status: Status // should be decodable
}
enum Status: String, Decodable {
case online
}
Note that enums can not be Decodable alone, because they should know what is the raw value, or you should manually decode them in decode function.
How can I append into an array using JSON Model Class.
Here is my JSON API Request: https://developer.github.com/v3/search/
import Foundation
typealias GitDecode = [GitDecodeElement]
struct GitDecodeElement: 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: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String
let size, stargazersCount, watchersCount: Int
let language: String
let forksCount, openIssuesCount: Int
let masterBranch, defaultBranch: String
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case masterBranch = "master_branch"
case defaultBranch = "default_branch"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
And here I have my class Model where I'm saying what I want to extract from that response:
import Foundation
struct Git: Codable{
let totalCount: Int
let items: GitItem
init ( totalCount: Int,
itemID: Int, itemDescription: String,
ownerID: Int, ownerAvatarURL: String) {
self.totalCount = totalCount
self.items = GitItem(id: itemID, description: itemDescription, owner: GitOwner(id: ownerID, avatarURL: ownerAvatarURL))
}
}
struct GitItem: Codable{
let id: Int
let description: String
let owner: GitOwner
}
struct GitOwner: Codable {
let id: Int
let avatarURL: String
}
Now I'm stuck when I try to append into my array all my custom properties because itemID, itemDescription, ownerID and ownerAvatarURL are in different classes.
Here is how I'm trying to get all the that properties from JSON using JSONDecoder:
import UIKit
class MainViewController: UIViewController {
var gitRepositoriesArray = [Git]()
override func viewDidLoad() {
super.viewDidLoad()
}
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://developer.github.com/v3/search/")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount,
itemID: <#T##Int#> , itemDescription: <#T##String#>, ownerID: <#T##Int#>, ownerAvatarURL: <#T##String#>))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
}
Complete working playground code:
Makes a search against the API for Swift related repos.
Parses them, adds them to the array and prints out some basic information on each one. (fullName, name, avatarUrl
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Repo]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Repo: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String?
let size, stargazersCount, watchersCount: Int
let language: String?
let forksCount, openIssuesCount: Int
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
var gitRepositoriesArray = [Repo]()
// Download Git Repositories from API
func parseGitRepositories() {
let url = URL(string: "https://api.github.com/search/repositories?q=topic:swift+topic:ios")
var request = URLRequest(url: url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
do {
let gitRepositoriesList = try JSONDecoder().decode(GitDecodeElement.self, from: data!)
gitRepositoriesArray = gitRepositoriesArray + gitRepositoriesList.items
print(gitRepositoriesArray.count)
for repo in gitRepositoriesList.items {
print("\(repo.fullName) - \(repo.name) - \(repo.owner.avatarURL)")
}
} catch {
let str = String(data: data!, encoding: .utf8)
print(str)
print(error)
}
}.resume()
}
parseGitRepositories()
PlaygroundPage.current.needsIndefiniteExecution = true
Output:
30
justjavac/free-programming-books-zh_CN - free-programming-books-zh_CN - https://avatars1.githubusercontent.com/u/359395?v=4
dkhamsing/open-source-ios-apps - open-source-ios-apps - https://avatars0.githubusercontent.com/u/4723115?v=4
matteocrippa/awesome-swift - awesome-swift - https://avatars2.githubusercontent.com/u/475463?v=4
xitu/gold-miner - gold-miner - https://avatars2.githubusercontent.com/u/10482599?v=4
lkzhao/Hero - Hero - https://avatars1.githubusercontent.com/u/3359850?v=4
ReactiveX/RxSwift - RxSwift - https://avatars1.githubusercontent.com/u/6407041?v=4
realm/realm-cocoa - realm-cocoa - https://avatars0.githubusercontent.com/u/7575099?v=4
CocoaPods/CocoaPods - CocoaPods - https://avatars1.githubusercontent.com/u/1189714?v=4
CosmicMind/Material - Material - https://avatars1.githubusercontent.com/u/10069574?v=4
// rest truncated
Notice how I use less models than you do in your code. There is no need to duplicate code, just use the parts you want, when you want them.
There is some problem in you Git structure. I have corrected the initializer like this:
struct Git: Codable{
let totalCount: Int
var items = [GitItem]()
init(totalCount: Int, items: [Item]) {
self.totalCount = totalCount
for item in items {
self.items.append(GitItem(id: item.id, description: item.description, owner: GitOwner(id: item.owner.id, avatarURL: item.owner.avatarURL)))
}
}
So your parsing method will be changed accordingly:
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount, items: eachRepo.items))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
Also note the change of url to https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc. The current url that your are using in your code is just the html page with examples. It doesn't return proper json results.