Swift 4 download Swedens radio channels - json

I got the same error when I try to get previous and song that play right now for every channel. I think something is wrong in my struct Root and playlist because data is not in the correct format.I am using Swift 4 and trying to learn JSON, but its hard. Previous struct is same as Song struct.
How do I fix my error?
My code looks now:
*struct Root : Decodable {
let copyright : String
let channels : [Channel]
let pagination : Pagination
//let playlists : [Playlist]
}
struct Channel : Decodable {
let image : String
let imagetemplate : String
let color : String
let tagline : String?
let siteurl : String
let id : Int
let url : URL?
let statkey : String?
let scheduleurl : String
let channeltype : String
let name : String
}
struct Pagination : Decodable {
let page, size, totalhits, totalpages : Int
let nextpage : URL
}
struct Playlist : Decodable{
let id : Int
let name :String
let prev : [previoussong]
let song : [Song]
}
struct Song : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
var tests = [Channel]()
var pl = [Playlist]()
func downloadStations(){
let test2 = "http://api.sr.se/api/v2/channels?format=json"
let play = "http://api.sr.se/api/v2/playlists/rightnow?format=json"
let url = URL(string: test2) /// play instead of test2
URLSession.shared.dataTask(with: url!){ (data , response, error) in
do{
let root = try JSONDecoder().decode(Root.self, from: data!)
self.tests = root.channels
//self.pl = root.playlists
for eachStations in self.tests {
DispatchQueue.main.async {
self.tableV.reloadData()
}
}
} catch {
print(error.localizedDescription)
}
}.resume()*

It doesn't return the array of channels. It returns an object:
{
"copyright": "somestring",
"pagination": {},
"channels": []
}
So, copyright is not a part of channel and you have an object with field channels.

Please read the JSON carefully. In the root object there is the key copyright and a key channels which contains the channel array.
You need to add an umbrella struct for the root object.
Please name the channel struct in singular form and with starting uppercase letter (Channel). Further some keys in the struct are optional.
struct Root : Decodable {
let copyright : String
let channels : [Channel]
let pagination : Pagination
}
struct Channel : Decodable {
let image : String
let imagetemplate : String
let color : String
let tagline : String?
let siteurl : String
let id : Int
let url : URL?
let statkey : String?
let scheduleurl : String
let channeltype : String
let name : String
}
struct Pagination : Decodable {
let page, size, totalhits, totalpages : Int
let nextpage : URL
}
var tests = [Channel]()
let root = try JSONDecoder().decode(Root.self, from: data!)
self.tests = root.channels

I got the same error when I try to get previous and song that play right now for every channel. How do I fix this?
My code looks now:
struct Root : Decodable {
let copyright : String
let channels : [Channel]
let pagination : Pagination
//let playlists : [Playlist]
}
struct Channel : Decodable {
let image : String
let imagetemplate : String
let color : String
let tagline : String?
let siteurl : String
let id : Int
let url : URL?
let statkey : String?
let scheduleurl : String
let channeltype : String
let name : String
}
struct Pagination : Decodable {
let page, size, totalhits, totalpages : Int
let nextpage : URL
}
struct Playlist : Decodable{
let id : Int
let name :String
let prev : [previoussong]
let song : [Song]
}
struct previoussong : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
struct Song : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
var tests = [Channel]()
var pl = [Playlist]()
var stationsName:String?
#IBOutlet weak var tableV: UITableView!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var statLabel: UILabel!
#IBOutlet weak var songLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tableV.dataSource = self
downloadStations()
}
func downloadStations(){
let test2 = "http://api.sr.se/api/v2/channels?format=json"
let play = "http://api.sr.se/api/v2/playlists/rightnow?format=json"
let url = URL(string: test2) /// play instead of test2
URLSession.shared.dataTask(with: url!){ (data , response, error) in
do{
let root = try JSONDecoder().decode(Root.self, from: data!)
self.tests = root.channels
//self.pl = root.playlists
for eachStations in self.tests {
self.stationsName = eachStations.name
print(" " + self.stationsName!)
DispatchQueue.main.async {
self.tableV.reloadData()
}
}
} catch {
print(error.localizedDescription)
}
}.resume()

Related

Why I can't create Array<[String : Any]>? Work with Alamofire (Swift)

I created a model that will take all the data.
Unfortunately I was ordered to work with Alamofire.
But for some reason, an array of arrayOfItem is not created. What could be the reason?
This is value of case .success(let value): here
func fetchData(from url: String) {
guard let url = URL(string: url) else { return }
request(url).validate().responseJSON { (dataResponse) in
switch dataResponse.result {
case .success(let value):
// try to print --- value have data!
guard let arrayOfItem = value as? Array<[String : Any]> else { return }
// try to print --- arrayOfItem is EMPTY!
}
case .failure(let error):
print(error)
}
}
}
Edited: add my model:
import Foundation
public struct Information: Decodable {
let copyright : String?
let numResults : Int?
let results : [Output]?
let status : String?
init(dict: [String: Any]) {
let copyright = dict["copyright"] as? String
let numResults = dict["numResults"] as? Int
let results = dict["results"] as? [Output]
let status = dict["status"] as? String
self.copyright = copyright
self.numResults = numResults
self.results = results
self.status = status
}
}
import Foundation
public struct Output: Decodable {
let abstractField : String?
let adxKeywords : String?
let assetId : Int?
let byline : String?
let column : String?
let countType : String?
let desFacet : [String]?
let emailCount : Int?
let etaId : Int?
let geoFacet : [String]?
let id : Int?
let media : [Media]?
let nytdsection : String?
let orgFacet : String?
let perFacet : String?
let publishedDate : String?
let section : String?
let source : String?
let subsection : String?
let title : String?
let type : String?
let updated : String?
let uri : String?
let url : String?
init(dict: [String : Any]) {
let abstractField = dict["abstractField"] as? String
let adxKeywords = dict["adxKeywords"] as? String
let assetId = dict["assetId"] as? Int
let byline = dict["byline"] as? String
let column = dict["column"] as? String
let countType = dict["countType"] as? String
let desFacet = dict["desFacet"] as? [String]
let emailCount = dict["emailCount"] as? Int
let etaId = dict["etaId"] as? Int
let geoFacet = dict["geoFacet"] as? [String]
let id = dict["id"] as? Int
let media = dict[""] as? [Media]
let nytdsection = dict["nytdsection"] as? String
let orgFacet = dict["orgFacet"] as? String
let perFacet = dict["perFacet"] as? String
let publishedDate = dict["publishedDate"] as? String
let section = dict["section"] as? String
let source = dict["source"] as? String
let subsection = dict["subsection"] as? String
let title = dict["title"] as? String
let type = dict["type"] as? String
let updated = dict["updated"] as? String
let uri = dict["uri"] as? String
let url = dict["url"] as? String
self.abstractField = abstractField
self.adxKeywords = adxKeywords
self.assetId = assetId
self.byline = byline
self.column = column
self.countType = countType
self.desFacet = desFacet
self.emailCount = emailCount
self.etaId = etaId
self.geoFacet = geoFacet
self.id = id
self.media = media
self.nytdsection = nytdsection
self.orgFacet = orgFacet
self.perFacet = perFacet
self.publishedDate = publishedDate
self.section = section
self.source = source
self.subsection = subsection
self.title = title
self.type = type
self.updated = updated
self.uri = uri
self.url = url
}
}
import Foundation
public struct Media: Decodable {
let approvedForSyndication : Bool?
let caption : String?
let copyright : String?
let mediaMetadata : [MediaMetadata]?
let subtype : String?
let type : String?
init(dict: [String: Any]) {
let approvedForSyndication = dict["approvedForSyndication"] as? Bool
let caption = dict["caption"] as? String
let copyright = dict["copyright"] as? String
let mediaMetadata = dict["mediaMetadata"] as? [MediaMetadata]
let subtype = dict["subtype"] as? String
let type = dict["type"] as? String
self.approvedForSyndication = approvedForSyndication
self.caption = caption
self.copyright = copyright
self.mediaMetadata = mediaMetadata
self.subtype = subtype
self.type = type
}
}
import Foundation
public struct MediaMetadata: Decodable {
let format : String?
let height : Int?
let url : String?
let width : Int?
init(dict: [String: Any]) {
let format = dict["format"] as? String
let height = dict["height"] as? Int
let url = dict["url"] as? String
let width = dict["width"] as? Int
self.format = format
self.height = height
self.url = url
self.width = width
}
}
Accordifnong to the json https://github.com/nytimes/public_api_specs/blob/master/most_popular_api/most_popular_api_v2.md
{ <<<< top root dictionary
"status":"OK",
"copyright":"Copyright (c) 2010 The New York Times Company. All Rights Reserved.",
"num_results":94,
"results":[{}] <<<<< it's an array of dictionaries
}
Your top root is a dictionary not an array
if let res = value as? [String: Any], let results = res["results"] as? [[String: Any]] {
print(results)
}
also it's better to use Codable
Alamofire.request(URL(string:"urlStr")!, method: .get, parameters: [:], encoding: URLEncoding.default, headers: [:]).responseData { response in
if response.result.isSuccess {
guard let data = response.data else { return }
do {
let res = try JSONDecoder().decode(Information.self, from: data)
print(res.results)
}
catch {
print(error)
}
}
}
Actual JSON is contained in value not in . So use:
guard let value = value as? [String : Any] else { return }
let arrayOfItem = value["result"] as? [[String : Any]]
print(arrayOfItem)
I'll be able to debug more if you add the JSON you're getting in value.
Like you said value is of type Data, you can simply get all the data using the Information type you created, i.e.
if let data = dataResponse.data {
let information = try? JSONDecoder().decode(Information.self, from: value)
print(information)
}

Using JSON Decodable in Swift 4.1

{
"count":30,
"recipes":[
{
"publisher":"Closet Cooking",
"f2f_url":"http://food2fork.com/view/35382",
"title":"Jalapeno Popper Grilled Cheese Sandwich",
"source_url":"http://www.closetcooking.com/2011/04/jalapeno-popper-grilled-cheese-sandwich.html",
"recipe_id":"35382",
"image_url":"http://static.food2fork.com/Jalapeno2BPopper2BGrilled2BCheese2BSandwich2B12B500fd186186.jpg",
"social_rank":100.0,
"publisher_url":"http://closetcooking.com"
}
]
}
How can I parse this JSON using Swift 4.1 Decodable please?
Your previous question is quite close but you have to add the struct for the root object
Declare the struct members non-optional as much as possible. The URLs can be decoded as URL
struct Root : Decodable {
let count : Int
let recipes : [Recipe]
}
struct Recipe : Decodable { // It's highly recommended to declare Recipe in singular form
let recipeId : String
let imageUrl, sourceUrl, f2fUrl : URL
let title : String
let publisher : String
let socialRank : Double
let page : Int?
let ingredients : [String]?
}
Now decode
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
self.recipes = result.recipes
below are the models for your JSON:
struct Recipe: Codable{
let publisher: String
let f2f_url: String
let title: String
let source_url: String
let recipe_id: String
let image_url: String
let social_rank: Float
let publisher_url: String
}
struct Model: Codable {
let count: Int
let recipes: [Recipe]
}
and below is JSON decodable:
let json = """
{
"count":30,
"recipes":[
{
"publisher":"Closet Cooking",
"f2f_url":"http://food2fork.com/view/35382",
"title":"Jalapeno Popper Grilled Cheese Sandwich",
"source_url":"http://www.closetcooking.com/2011/04/jalapeno-popper-grilled-cheese-sandwich.html",
"recipe_id":"35382",
"image_url":"http://static.food2fork.com/Jalapeno2BPopper2BGrilled2BCheese2BSandwich2B12B500fd186186.jpg",
"social_rank":100.0,
"publisher_url":"http://closetcooking.com"
}
]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let model = try decoder.decode(Model.self, from: json) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}

how can I save some part of decodable struct that json has parsed into that into the UserDefaults in swift 4?

so here is my struct that I will parse my json into that
public struct Response : Decodable {
var Data : [Data]?
let Status : Int?
let Message : String?
}
public struct Data : Decodable {
let id : String?
let full_name : String?
let is_private : String?
let profile_pic_url : String?
let username : String?
let biography : String?
let type : String?
let code : String?
let date : String?
let thumbnail_src : String?
let display_src : String?
let caption : String?
let detail : String?
let type_id : String?
let district_id : String?
let p_name : String?
}
and here I made an object from my Data struct
public class publicVariables {
static var savedItems = [listPageModel.Data]()
}
and here is the method when my app launches for first time or not
func setDefults() {
let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
if launchedBefore {
print("Not first launch.")
let decoded = UserDefaults.standard.object(forKey: "savedPosts") as! Data
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [listPageModel.Data]
publicVariables.savedItems = decodedTeams
} else {
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "launchedBefore")
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: publicVariables.savedItems)
UserDefaults.standard.set(encodedData, forKey: "savedPosts")
UserDefaults.standard.synchronize()
}
}
and then I parse the json to the struct
var res : listPageModel.Response? = nil
var mainData = [listPageModel.Data]()
.
.
.
self.res = try JSONDecoder().decode(listPageModel.Response.self , from : data!)
for i in 0...((self.res?.Data?.count)!) - 1 {
self.mainData.append((self.res?.Data?[i])!)
.
.
.
so now we have the json decoded into the struct and I can show that in table view or collection view now I want when user select of of them that index store into the userDefaults for example this
self.res?.Data[0]
so I used this method here
#objc func likeDislike(_ sender : UIButton!) {
print(sender.tag)
if !publicVariables.savedItems.contains(where: {$0.code! == "\(self.res?.Data[sender.tag].code)"}) {
print((self.mainData[sender.tag].caption)!)
publicVariables.savedItems.append((self.mainData[sender.tag]))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: [publicVariables.savedItems])
publicVariables().defaults.set(encodedData, forKey: "savedPosts")
publicVariables().defaults.synchronize()
} else {
print((self.mainData[sender.tag].caption)!)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: [publicVariables.savedItems])
publicVariables().defaults.set(encodedData, forKey: "savedPosts")
publicVariables().defaults.synchronize()
}
}
But when this functions will run I will receive this error and the app will crash
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x7f8eda563be0'
*** First throw call stack:

Swift 4 get song for every channel

I got the same error when I try to get previous and song that play right now for every channel. I think something is wrong in my struct Root and playlist because data is not in the correct format.I am using Swift 4 and trying to learn JSON, but its hard. Previous struct is same as Song struct. How do I fix my error?
My code looks now:
import UIKit
struct Root : Decodable {
let copyright : String
let channels : [Channel]
let pagination : Pagination
}
struct Channel : Decodable {
let image : String
let imagetemplate : String
let color : String
let tagline : String?
let siteurl : String
let id : Int
let url : URL?
let statkey : String?
let scheduleurl : String
let channeltype : String
let name : String
}
struct Pagination : Decodable {
let page, size, totalhits, totalpages : Int
let nextpage : URL
}
struct Playlist : Decodable{
let id : Int
let name :String
let prev : [previoussong]
let song : [Song]
}
struct previoussong : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
struct Song : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
import UIKit
class ViewController: UIViewController,UITableViewDataSource {
var auth = SPTAuth.defaultInstance()!
var session:SPTSession!
var loginUrl: URL?
var tests = [Channel]()
//var pl = [Playlist]()
var stationsName:String?
#IBOutlet weak var tableV: UITableView!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var statLabel: UILabel!
#IBOutlet weak var songLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tableV.dataSource = self
downloadStations()
}
func downloadStations(){
let test2 = "http://api.sr.se/api/v2/channels?format=json"
let play = "http://api.sr.se/api/v2/playlists/rightnow?format=json"
let url = URL(string: test2)
URLSession.shared.dataTask(with: url!){ (data , response, error) in
do{
let root = try JSONDecoder().decode(Root.self, from: data!)
self.tests = root.channels
for eachStations in self.tests {
self.stationsName = eachStations.name
print(" " + self.stationsName!)
DispatchQueue.main.async {
self.tableV.reloadData()
}
}
}
catch{
print(error.localizedDescription)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style : .default, reuseIdentifier : nil)
cell.textLabel?.text = tests[indexPath.row].name
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The data you're getting from api.sr.se/api/v2/playlists/rightnow?format=json does not always have the correct structure. Notice that sometimes you will get json that starts with a copyright field, while other times you get something like this:
{
"playlist": {
"previoussong": {
"title": "Stir Fry",
"description": "Migos - Stir Fry",
"artist": "Migos",
"composer": "Quavious Marshall/Pharrell Williams",
"recordlabel": "Universal",
"starttimeutc": "\/Date(1521137326000)\/",
"stoptimeutc": "\/Date(1521137510000)\/"
},
...
When you attempt to decode into Root, the decoder looks in the json for something that looks like your Root class, something like this:
{
"copyright": "Copyright Sveriges Radio 2018. All rights reserved.",
"channels": [{
"id": 4540,
"name": "Ekot",
"playlists": {
"playlist": {
"channel": {
"id": 4540,
"name": "Ekot sänder direkt"
}
If it can't find something like that in the json, it says the data is missing. You will have to figure out a way to tell when to decode into Root, and when the data isn't appropriate for Root.

SWIFT 4, Xcode 9, JSON DECODER

I'm pretty stuck now. I'm attempting to PARS a JSON RETURN for just the year make make and model. It's buried in an array of dictionaries, and the decoder is having a hard time pulling them out. What am I doing wrong?
public struct Page: Decodable {
let Count: Int
let Message: String
let SearchCriteria: String
let Results: [car]}
public struct car: Decodable {
let ModelYear: String
let Make: String
let Model: String
let VIN: String}
let session = URLSession.shared
let components = NSURLComponents()
components.scheme = "https"
components.host = "vpic.nhtsa.dot.gov"
components.path = "/api/vehicles/decodevinvaluesextended/\(VIN)"
components.queryItems = [NSURLQueryItem]() as [URLQueryItem]
let queryItem1 = NSURLQueryItem(name: "Format", value: "json")
components.queryItems!.append(queryItem1 as URLQueryItem)
print(components.url!)
let task = session.dataTask(with: components.url!, completionHandler: {(data, response, error) in
guard let data = data else { return }
do
{
//let Result = try JSONDecoder().decode(Page.self, from: data)
// let PageResult = try JSONDecoder().decode(Page.self, from: data)
let json = try JSONDecoder().decode(Page.self, from: data)
let Results = json.Results;
print(Results)
First of all it's highly recommended to conform to the Swift naming convention that variable names start with a lowercase and structs start with a capital letter
public struct Page: Decodable {
private enum CodingKeys : String, CodingKey {
case count = "Count", message = "Message", searchCriteria = "SearchCriteria", cars = "Results"
}
let count: Int
let message: String
let searchCriteria: String
let cars: [Car]
}
public struct Car: Decodable {
private enum CodingKeys : String, CodingKey {
case modelYear = "ModelYear", make = "Make", model = "Model", VIN
}
let modelYear: String
let make: String
let model: String
let VIN: String
}
The cars array is in the variable result. This code prints all values
let result = try JSONDecoder().decode(Page.self, from: data)
for car in result.cars {
print("Make: \(car.make), model: \(car.model), year: \(car.modelYear), VIN: \(car.VIN)")
}