Unable to get Dictionary from JSON - json

This is the code I am using but I am unable to fetch the JSON.
Error message:
Expected to decode Dictionary<String, Any> but found an array instead.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(ItemList.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
JSON:
{
"page": 1,
"total_results": 2102,
"total_pages": 106,
"results": [{
"vote_count": 9052,
"id": 11,
"video": false,
"vote_average": 8.2,
"title": "Star Wars",
"popularity": 31.502792,
"poster_path": "/btTdmkgIvOi0FFip1sPuZI2oQG6.jpg",
"original_language": "en",
"original_title": "Star Wars",
"genre_ids": [
12,
28,
878
],
"backdrop_path": "/4iJfYYoQzZcONB9hNzg0J0wWyPH.jpg",
"adult": false,
"overview": "Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.",
"release_date": "1977-05-25"
}]
}
struct Results: Codable {
let id: Int
let title: String
let poster_path: String
struct ItemList: Codable {
let results: Results
}
}

You can create a Swift Struct for this purpose. Here is how you do it.
import Foundation
struct MovieStruct: Codable {
let page, totalResults, totalPages: Int?
let results: [Result]?
enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
}
struct Result: Codable {
let voteCount, id: Int?
let video: Bool?
let voteAverage: Double?
let title: String?
let popularity: Double?
let posterPath, originalLanguage, originalTitle: String?
let genreIDS: [Int]?
let backdropPath: String?
let adult: Bool?
let overview, releaseDate: String?
enum CodingKeys: String, CodingKey {
case voteCount = "vote_count"
case id, video
case voteAverage = "vote_average"
case title, popularity
case posterPath = "poster_path"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case releaseDate = "release_date"
}
}
After you have created the struct, you can do something like this to parse your data.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try? JSONDecoder().decode(MovieStruct.self, from: jsonData)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Note I have created the struct with the JSON which you have given. I hope it helps.

func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Result struct contains the contents of "results"
struct Result: Codable {
var results: [Movie]
}
Add variables to correspond to the item's fields
struct Movie: Codable {
var id: Int
var vote_count: Int
var title: String
//etc..
}

Related

GET Method Keeps Throwing Index Out Of Range Error (SWIFTUI)

I'm having a hard time accessing data from my JSONDecoder. I see that I am definitely getting a result, but I keep getting an indexOutOfRange error when trying to access the first index.
Model:
struct Movie: Codable {
let page: Int
let results: [Result]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
// MARK: - Result
struct Result: Codable, Identifiable {
let adult: Bool
let backdropPath: String
let id: Int
let title: String?
let originalLanguage: String
let originalTitle: String?
let overview, posterPath: String
let mediaType: MediaType
let genreIDS: [Int]
let popularity: Double
let releaseDate: String?
let video: Bool?
let voteAverage: Double
let voteCount: Int
let name, originalName, firstAirDate: String?
let originCountry: [String]?
enum CodingKeys: String, CodingKey {
case adult
case backdropPath = "backdrop_path"
case id, title
case originalLanguage = "original_language"
case originalTitle = "original_title"
case overview
case posterPath = "poster_path"
case mediaType = "media_type"
case genreIDS = "genre_ids"
case popularity
case releaseDate = "release_date"
case video
case voteAverage = "vote_average"
case voteCount = "vote_count"
case name
case originalName = "original_name"
case firstAirDate = "first_air_date"
case originCountry = "origin_country"
}
}
enum MediaType: String, Codable {
case movie = "movie"
case tv = "tv"
}
Main View
struct MainView: View {
#State var movieList: [Movie] = []
#StateObject var network = myNetwork() // <-- for testing
var sampleText = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout...."
var body: some View {
VStack {
GeometryReader{ proxy in
TabView{
ForEach(0..<5){ num in
VStack {
Button(action: {}) {
Image("Image")
.frame(width: 300, height: 200)
.scaledToFit()
.clipped()
}
Text("\(movieList[0].results)" as String)
.lineLimit(nil)
.foregroundColor(.white)
.multilineTextAlignment(.leading)
.padding()
.frame(width: 300, height: 200)
.background(Rectangle().fill(Color("Grey")).shadow(radius: 1))
}
}
}.tabViewStyle(PageTabViewStyle())
.padding()
.frame(width: proxy.size.width, height: proxy.size.height)
}
.onAppear {
network.getMovies(onCompletion: { (fetchedCity: Movie) in
DispatchQueue.main.async {
self.movieList = [fetchedCity]
}
})
}
}
.padding()
}
}
API:
class movieAPI:ObservableObject{
func getMovies(onCompletion: #escaping ([Movie]) -> ()){
let apiKey = "bb062ca1a1471913abf6b680168fc5ca"
let url = URL(string: "https://api.themoviedb.org/3/trending/all/day?api_key=\(apiKey)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let session = URLSession.shared
let dataTask = session.dataTask(with: request, completionHandler: { (data1, response1, error1) -> Void in
guard let data = data1 else {
return
}
do {
let results = try JSONDecoder().decode([Movie].self, from: data)
DispatchQueue.main.async {
let tmp = results[0].results
print("hotel model \(tmp)")
}
onCompletion(results)
} catch {
print("Couldn't decode hotel json \(error.localizedDescription)")
}
})
dataTask.resume()
}
}
Results:
Text("\(movieList[0].results)" as String)
Result: Thread 1: Fatal error: Index out of range
Text("\(movieList.description)")
Result: Movie(page: 1, results: [SliderExample.Result(adult: false, backdropPath: "/rfnmMYuZ6EKOBvQLp2wqP21v7sI.jpg", id: 774752, title: Optional("The Guardians of the Galaxy Holiday Special"), originalLanguage: "en", originalTitle: Optional("The Guardians of the Galaxy Holiday Special"), overview: "On a mission to make Christmas unforgettable for Quill, the Guardians head to Earth in search of the perfect present.", posterPath: "/8dqXyslZ2hv49Oiob9UjlGSHSTR.jpg", mediaType: SliderExample.MediaType.movie, genreIDS: [35, 878, 12], popularity: 539.588, releaseDate: Optional("2022-11-25"), video: Optional(false), voteAverage: 7.521, voteCount: 382, name: nil, originalName: nil, firstAirDate: nil, originCountry: nil)
My goal is to access the overview information.

How to parse weather data from NASA API

So, the issue is - I am trying to display the Mars weather from the Mars Insight API. Here is a link Insight Weather, the data is returning in JSON format and has three levels. The keys have names that change depending on the current date (sols). How to make the structure of mutable properties?... when the property names change every day. Do we have any instruments to parse such JSON?
{
"815": {
"First_UTC": "2021-03-12T14:54:38Z",
"Last_UTC": "2021-03-13T15:34:09Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 728.378,
"ct": 153082,
"mn": 708.4211,
"mx": 744.9279
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"818": {
"First_UTC": "2021-03-15T20:01:49Z",
"Last_UTC": "2021-03-16T17:32:54Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 727.696,
"ct": 109855,
"mn": 710.223,
"mx": 743.946
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"819": {
....
,
"sol_keys": [
"815",
"818",
"819",
"820",
"821"
],
"validity_checks": {
"815": {
"PRE": {
"sol_hours_with_data": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
],
"valid": true
}
}
So, Structure:
import Foundation
struct WeatherData: Decodable {
let season: String?
let pre: pre?
let solKeys: [String]
enum CodingKeys: String, CodingKey {
case season = "season"
case pre = "PRE"
case solKeys = "sol_keys"
}
struct pre: Decodable {
let av: Double?
let mn: Double?
let mx: Double?
enum CodingKeys: String, CodingKey {
case av = "av"
case mn = "mn"
case mx = "mx"
}
}
}
Parsing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
guard let url = URL(string: urlString) else { return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let safeData = data else { return }
do {
let solWeather = try JSONDecoder().decode([String:WeatherData].self, from: safeData)
if let keys = solWeather["sol_keys"] {
for key in keys { //error: For-in loop requires 'WeatherData' to conform to 'Sequence'
let report = solWeather [key]
}
}
print(solWeather)
}
catch let error {
print(error)
}
}.resume()
}
}
Got an error: For-in loop requires 'WeatherData' to conform to 'Sequence'
Don't understand why :(( Can someone help me?
Thanks!
You cannot decode [String:WeatherData].self because the dictionary contains other values which are not WeatherData, for example the [String] value of sol_keys.
The only way to decode this JSON with JSONDecoder is to implement init(with decoder, decode the sol_keys and create your own temporary CodingKeys to be able to decode the arbitrary dictionary keys.
First declare the custom CodingKey
public struct SolKeys: CodingKey {
public let stringValue: String
public init?(stringValue: String) { self.stringValue = stringValue }
public var intValue: Int? { return nil }
public init?(intValue: Int) { return nil }
}
The Decodable structs are
struct SolData : Decodable {
let firstUTC, lastUTC : Date
let pre : Pre
private enum CodingKeys : String, CodingKey {
case firstUTC = "First_UTC", lastUTC = "Last_UTC", pre = "PRE"
}
}
struct Pre: Decodable {
let av, mn, mx : Double
}
struct WeatherData: Decodable {
let solKeys: [String]
var soldata = [String:SolData]()
enum CodingKeys: String, CodingKey {
case solKeys = "sol_keys"
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.solKeys = try container.decode([String].self, forKey: .solKeys)
let customContainer = try decoder.container(keyedBy: SolKeys.self)
for key in solKeys {
let solKey = SolKeys(stringValue: key)!
let data = try customContainer.decode(SolData.self, forKey: solKey)
soldata[key] = data
}
}
}
And the code to receive and decode the data
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let solWeather = try decoder.decode(WeatherData.self, from: data!)
let keys = solWeather.solKeys
for key in keys {
let report = solWeather.soldata[key]!
print(report)
}
}
catch {
print(error)
}
}.resume()

Swift weather data from Openweathermap API?

From openweathermap api, I am getting below response.
{
"cod":"200",
"message":0,
"cnt":40,
"list":[
{
"dt":1587643200,
"main":{
"temp":289.78,
"feels_like":283.61,
"temp_min":289.03,
"temp_max":289.78,
"pressure":1014,
"sea_level":1014,
"grnd_level":1010,
"humidity":41,
"temp_kf":0.75
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
"clouds":{
"all":94
},
"wind":{
"speed":6.75,
"deg":2
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 12:00:00"
},
{
"dt":1587654000,
"main":{
"temp":289.66,
"feels_like":284.44,
"temp_min":289.34,
"temp_max":289.66,
"pressure":1013,
"sea_level":1013,
"grnd_level":1009,
"humidity":47,
"temp_kf":0.32
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04d"
}
],
"clouds":{
"all":67
},
"wind":{
"speed":5.9,
"deg":357
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 15:00:00"
}
Then I write the code below to get wind data for any specific daytime(dt). I get the jsonresponse to the Any "list". But I can't get the wind data. I get the error
"Value of type 'Any' has no subscripts".
Also, I can't understand how can I get the wind data for dt=1587643200 and dt=1587654000 separately.
if let list = jsonresponse["list"] as? Any {
let wind = list["wind"] as? [String : Any],
print(wind)
}
This is a super simple example, this question is similar to your problem. I would like that you learn about Codable protocol to simplify and improve your code because of this way is super creepy.
let url = URL(string: "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1")!
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
guard let data = data else {
return
}
guard let dictionaryObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return
}
guard let list = dictionaryObj["list"] as? [[String: Any]] else {
return
}
if let first = list.first, let wind = first["wind"] {
print(wind)
}
}).resume()
Incase anyone further needs help, I wrote a small code segment—using Swift Codables—to get this done.
Note that I'm using Higher Order Functions in Swift to filter & print the required values. You can follow this tutorial if you need to know more about how they work..
let urlNew = "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1"
struct WeatherResponse: Codable {
let message: String
let cod: String
let city_id: Int
let calctime: Double
let cnt: Int
let list: [WeatherCompositeObject]
struct WeatherCompositeObject: Codable {
let main: WeatherMainObject
let wind: WeatherWindObject
let clouds: WeatherCloudObject
let weather: [WeatherObject]
let dt: Int
struct WeatherMainObject: Codable {
let temp: Double
let humidity: Int
//You can add the other parameters as needed here
}
struct WeatherWindObject: Codable {
let speed: Double
let deg: Double
}
struct WeatherCloudObject: Codable {
let all: Int
}
struct WeatherObject: Codable {
let id: Int
let main: String
let description: String
}
}
}
class ApiContentDownloader {
func getCurrentWeather(url: String) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, urlResponse, error in
let parser = ContentParser()
let result = parser.parseData(data: data!)
if let result = result {
print("Weather: \(result.message)")
print("DT values: \(result.list.map({ $0.dt})) ")
print("Wind values: \(result.list.map({ $0.wind.speed})) ")
print("Weather descriptions: \(result.list.map({ $0.weather.map( {$0.description} )})) ")
}
else {
print("Oops... Error occured")
}
}.resume()
}
}
class ContentParser {
func parseData(data: Data) -> WeatherResponse? {
var result: WeatherResponse? = nil
do {
let json = try JSONDecoder().decode(WeatherResponse.self, from: data)
result = json
}
catch {
print(error.localizedDescription)
}
return result
}
}
let downloader = ApiContentDownloader()
downloader.getCurrentWeather(url: urlNew)

Unable to fetch data from json through codable swift

This is json code:
{
"status":"success",
"data":
[
{"id":"3",
"city_name":"Delhi",
"city_image":"delhi.png"},
{"id":"4",
"city_name":"Mumbai",
"city_image":"tickmark.png"}
]
}
My Swift Code :
struct city: Decodable{
let status : String
let id: String
let data : String
let city_name: String
let city_image: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrl = "http://parking.onlinekiduniya.org/api/cityList.php"
let url = URL(string: jsonUrl)
URLSession.shared.dataTask(with: url!) {(data, response, error) in
do {
let cities = try JSONDecoder().decode([city].self, from: data!)
for city in cities {
print(city.id)
}
}
catch {
print("we got error")
}
}.resume()
}
}
Replace
let cities = try JSONDecoder().decode([city].self, from: data!)
with
let root = try JSONDecoder().decode(Root.self, from: data!)
let cities = root.data
cities.forEach {
print($0.id)
}
struct Root: Codable {
let status: String
let data: [City]
}
struct City: Codable {
let id, cityName, cityImage: String // you can use snake case also
enum CodingKeys: String, CodingKey {
case id
case cityName = "city_name"
case cityImage = "city_image"
}
}
Your root is a dictionary not an array

Parsing JSON with Swift 4 Decodable

I have been getting the following error every time I try to parse JSON in my program. I can't seem to figure it out.
"Expected to decode String but found an array instead.", underlyingError: nil
Here is the code I have been struggling with:
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]?
}
struct Page: Decodable {
let id: Int
let text: [String]
}
struct Chapter: Decodable {
var chapterNumber: Int
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
if let err = err {
print("Failed to fetch data from", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let books = try decoder.decode([Book].self, from: data)
books.forEach({print($0.title)})
} catch let jsonErr {
print("Failed to parse json:", jsonErr)
}
}.resume()
}
Are you sure that's the real error message?
Actually the error is supposed to be
"Expected to decode String but found a dictionary instead."
The value for key text is not an array of strings, it's an array of dictionaries
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
The struct Chapter is not needed.
Alternatively write a custom initializer and decode the dictionaries containing the chapter number as key and the text as value into an array of Chapter
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
let pages: [Page]
}
struct Page: Decodable {
let id: Int
var chapters = [Chapter]()
private enum CodingKeys : String, CodingKey { case id, chapters = "text" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .chapters)
while !arrayContainer.isAtEnd {
let chapterDict = try arrayContainer.decode([String:String].self)
for (key, value) in chapterDict {
chapters.append(Chapter(number: Int(key)!, text: value))
}
}
}
}
struct Chapter: Decodable {
let number : Int
let text : String
}
This is working:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
print(info.pages[0].text)
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
// print
//Genesis
//50
//1
//[["1": "In the beg...]]
//-------------------
//Exodus
//40
//2
//[["1": "In the beginning God created...]]
//
If you need to print the value of each chapter in the book, I can use something like this:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
//print(info.pages[0].text)
for cc in info.pages[0].text {
for (key, value) in cc {
print("\(key) : \(value)")
}
}
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
//Genesis
//50
//1
//1 : In the beginning God ...
//2 : But the earth became waste...
//.
//.
//.
//31 : And God saw everything...
//-------------------
//Exodus
//40
//2
//1 : In the beginning God...
//2 : But the earth became...
//.
//.
//.
//31 : And God saw everything