JSON Decoding error pops up, dunno what else to try - ios

guys, I would really appreciate some help with JSON decoding. I am trying get API from this link: http://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019+&country=us
I might have made some really amateur mistakes. First time uploading a problem here. TY for help!
Here is my DataManager
protocol NewsManagerDelegate {
func didUpdateNews(news: NewsModel)
}
import Foundation
struct NewsManager{
var delegate: NewsManagerDelegate?
let url = "https://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019"
func fetchNews(_ countryName: String){
let newsUrlString = "\(url)+&country=\(countryName)"
performRequest(newsUrlString)
}
func performRequest(_ urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print("networking error \(error!)")
return
}
if let safeData = data{
if let news = parseJSON(safeData){
delegate?.didUpdateNews(news: news)
}
}
}
task.resume()
}
}
func parseJSON(_ newsData: Data) -> NewsModel?{
do{
let decodedData = try JSONDecoder().decode(NewsData.self, from: newsData)
let sourceName = decodedData.articles[5].source.name
let titleName = decodedData.articles[5].title
let linkToImage = decodedData.articles[5].urlToImage
let news = NewsModel(sourceName: sourceName, titleName: titleName, linkToImage: linkToImage )
return news
}catch{
print(error)
return nil
}
}
}
and my Data
import Foundation
struct NewsData: Codable {
let totalResults: Int
let articles: [Articles]
}
struct Articles: Codable{
let author: String?
let title: String
let description: String
let urlToImage: String
let source: Source
}
struct Source: Codable{
let name: String
}
I am receiving this error
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "articles", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "description", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
I tried to make some of the constants optional but after that no info would show up at all.

I would just change the property name to articleDescription and make it optional. If you would like to parse the publishedAt date all you need is to set decoder dateDecodingStrategy property to .iso8601. Besides that you are not constructing your url correctly. You should always use URLComponents when composing your url and change your urls properties types from String to URL:
struct Root: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url, urlToImage: URL
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title, articleDescription = "description", url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
Playground testing:
func fetchNews(_ countryCode: String) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "newsapi.org"
urlComponents.path = "/v2/top-headlines"
urlComponents.queryItems = [.init(name: "apiKey", value: "a16b15f863454928804e218705d0f019"),
.init(name:"country", value: countryCode)]
if let url = urlComponents.url {
performRequest(url)
}
}
func performRequest(_ url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("networking error", error ?? "nil")
return
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let root = try decoder.decode(Root.self, from: data)
let articles = root.articles
for article in articles {
print("article:", article, terminator: "\n")
}
} catch {
print(error)
}
}.resume()
}
fetchNews("us")
This will print:
article: Article(source: __lldb_expr_111.Source(id: Optional("cnn"), name: "CNN"), author: Optional("Oliver Darcy, CNN Business"), title: "Tucker Carlson backlash tells us something important about some Trump supporters - CNN", articleDescription: nil, url: https://www.cnn.com/2020/11/21/media/tucker-carlson-fox-news-traitor/index.html, urlToImage: https://cdn.cnn.com/cnnnext/dam/assets/201105014450-tucker-carlson-fox-news-presidential-election-fraud-super-tease.jpg, publishedAt: 2020-11-21 16:11:00 +0000, content: nil)
article: Article(source: __lldb_expr_111.Source(id: Optional("usa-today"), name: "USA Today"), author: Optional("Joel Shannon, Grace Hauck"), title: "Coronavirus updates: Donald Trump Jr. tests positive; model estimates 471k US deaths by March; Cuomo to receive International Emmy - USA TODAY", articleDescription: Optional("Donald Trump Jr. tests positive for the coronavirus. Model predicts more deaths. Thanksgiving during a pandemic happened before. Latest COVID news."), url: https://www.usatoday.com/story/news/health/2020/11/21/covid-news-donald-trump-jr-positive-thanksgiving-travel-not-advised/6367181002/, urlToImage: https://www.gannett-cdn.com/presto/2020/11/21/NSTT/caa3ba8a-8e3c-4b49-82e2-2810caf33d05-Testing3.jpg?crop=1574,886,x0,y80&width=1600&height=800&fit=bounds, publishedAt: 2020-11-21 15:56:15 +0000, content: Optional("A coronavirus vaccine might not be widely available until several months into 2021.\r\nUSA TODAY\r\nThe U.S reported a record high of more than 195,000 new daily cases of COVID-19 Friday, the same week t… [+11662 chars]"))
article: Article(source: __lldb_expr_111.Source(id: nil, name: "CBS Sports"), author: Optional(""), title: "Clemson vs. Florida State game postponed hours before kickoff as teams disagreed about whether to play - CBS Sports", articleDescription: Optional("A Clemson player's late positive test for COVID-19 is the reason for the abrupt postponement"), url: https://www.cbssports.com/college-football/news/clemson-vs-florida-state-game-postponed-hours-before-kickoff-as-teams-disagreed-about-whether-to-play/, urlToImage: https://sportshub.cbsistatic.com/i/r/2019/01/31/3cfd9702-8ef4-4f0d-9cc6-2601f4b3ad9c/thumbnail/1200x675/d00f9bb392291c844de43984526b773e/clemson.jpg, publishedAt: 2020-11-21 15:28:00 +0000, content: Optional("No. 4 Clemson and Florida State were set to kick off at noon ET in Tallahassee, Florida, a game that would have marked Tigers quarterback Trevor Lawrence's return to action following a positive COVID… [+3139 chars]")) ......

articles - description
"Expected String value but found null instead."
Change
let description: String
to
let description: String?

It says there is no value at the first value, can you post the json response what you're getting as that would be useful. Another possibility could be that the json response is in a different order than what you are decoding.
You could just change description to an optional as that would get rid of the problem but then you wouldn't have a description so it doesn't fully solve the problem
let description: String?

Related

Issues with Swift valueNotFound during API Call

I am looking for some help working with a Swift project. I am building an app that pulls aviation weather data from an API and this situation is common:
User wants data from airport weather station KDAB - current report says:
Wind 10 kt
Scattered Clouds 2500 ft
Visibility 10 SM
Light Rain
User wants data from airport weather station KJAX - current report says:
Wind 16 kt
Wind Gust 24 kt
Broken Clouds 1400 ft
Scattered Clouds 1900 ft
Few clouds 2400 ft
In this simple example, you may notice that there is no wind gust data supplied for KJAX during this reporting period and no "special weather" (ie rain, haze, fog) specified for KDAB. My app needs to be able to handle "nil" or not provided data without just telling me that there is a valueNotFound or that the index is out of range.
Here are the API Docs: https://avwx.docs.apiary.io/#reference/0/metar/get-metar-report
Here is my code:
import Foundation
struct WeatherManager {
let weatherURL = "https://avwx.rest/api/metar/"
func fetchWeather (stationICAO: String) {
let urlString = "\(weatherURL)\(stationICAO)?token=OVi45FiTDo1LmyodShfOfoizNe5m9wyuO6Mkc95AN-c"
performRequest(urlString: urlString)
}
func performRequest (urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(weatherData: safeData)
}
}
task.resume()
print(urlString)
}
}
func parseJSON(weatherData: Data) {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let lowCloudsType = decodedData.clouds[0].type
let midCloudsType = decodedData.clouds[1].type
let highCloudsType = decodedData.clouds[2].type
let lowCloudsAlt = decodedData.clouds[0].altitude
let midCloudsAlt = decodedData.clouds[1].altitude
let highCloudsAlt = decodedData.clouds[2].altitude
let reportingStationVar = decodedData.station
let windGustValue = decodedData.wind_gust.value
let windSpeedValue = decodedData.wind_speed.value
let windDirectionValue = decodedData.wind_direction.value
let visibilityValue = decodedData.visibility.value
let flightRulesValue = decodedData.flight_rules
let weather = WeatherModel(lowestCloudsType: lowCloudsType, lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType, middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType, highestCloudsAlt: highCloudsAlt, reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue)
print(weather.flightConditions)
} catch {
print(error)
}
}
}
import Foundation
struct WeatherModel {
let lowestCloudsType: String
let lowestCloudsAlt: Int
let middleCloudsType: String
let middleCloudsAlt: Int
let highestCloudsType: String
let highestCloudsAlt: Int
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: Int
let visibility: Int
let flightRules: String
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
Last One:
import Foundation
struct WeatherData: Decodable {
let clouds: [Clouds]
let flight_rules: String
let remarks: String
let wind_speed: WindSpeed
let wind_gust: WindGust
let wind_direction: WindDirection
let visibility: Visibility
let station: String
}
struct Clouds: Decodable {
let type: String
let altitude: Int
}
struct WindSpeed: Decodable {
let value: Int
}
struct WindGust: Decodable {
let value: Int
}
struct WindDirection: Decodable {
let value: Int
}
struct Visibility: Decodable {
let value: Int
}
Depending on what I play with, I get the following errors when entering a station that does not have all of that given information that I need to be able to present to the user if reported by the weather service.
2020-09-22 02:47:58.930421-0400 AvWx Pro[66612:4483807] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KDAB
https://avwx.rest/api/metar/KDAB?token=(mySecretToken)
2020-09-22 02:48:02.943231-0400 AvWx Pro[66612:4483809] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
valueNotFound(Swift.KeyedDecodingContainer<AvWx_Pro.WindGust.(unknown context at $1053fb3b8).CodingKeys>,
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "wind_gust", intValue: nil)],
debugDescription: "Cannot get keyed decoding container
-- found null value instead.", underlyingError: nil))
A different error when I use an airport that doesn't have all three of the possible cloud layers reported:
2020-09-22 03:06:02.398628-0400 AvWx Pro[66736:4497432] libMobileGestalt MobileGestaltCache.c:38: No persisted cache on this platform.
KJAX
https://avwx.rest/api/metar/KJAX?token=(mySecretKey)
2020-09-22 03:06:07.955064-0400 AvWx Pro[66736:4497429] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
2020-09-22 03:06:08.908826-0400 AvWx Pro[66736:4497429] Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1200.2.22.2/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
(lldb)
I've spent a few hours now trying various solutions I've found online, including using optionals and force unwrapping, using guard let, using if let, and a few others. I'm pretty lost at the moment.
I am new to this platform (as a poster) and would really appreciate any insight anyone can offer! Thanks for the help in advance.
To avoid crashes while decoding json you should make correct configuration of your structs and check the fields before access values:
Optional fields
[CodingKeys(stringValue: "wind_gust", intValue: nil)],
debugDescription: "Cannot get keyed decoding container -- found null
value instead.", underlyingError: nil))
wind_gust can be empty so you should make it optional:
If a field can be empty or null in responses you should make it optional in your struct e.g.:
struct WeatherData: Decodable {
...
let wind_gust: WindGust?
...
}
Then in your code just use optional binding to extract a value if wind_gust exists:
if let value = decodedData.wind_gust?.value {
print(value)
}
Arrays
Fatal error: Index out of range
You must always check an array's bounds before access to items e.g.:
let clouds = decodedData.clouds
let lowCloudsType = clouds.count > 0 ? clouds[0].type : nil
let midCloudsType = clouds.count > 1 ? clouds[1].type : nil
let highCloudsType = clouds.count > 2 ? clouds[2].type : nil
if let low = lowCloudsType, let mid = midCloudsType, let high = highCloudsType {
print(low)
print(mid)
print(high)
}
The answer is to use Optionals
Consider the below playground example, the age and location properties are optional, the location property is missing from the JSON.
import Foundation
struct TestModel: Decodable {
let name: String
let age: Int?
let location: String?
enum CodingKeys: String, CodingKey {
case name
case age
case location
}
}
let jsonData = """
{
"name": "John Smith",
"age": 28
}
""".data(using: .utf8)!
let result = try JSONDecoder().decode(TestModel.self, from: jsonData)
print(result)
Output
TestModel(name: "John Smith", age: Optional(28), location: nil)

JSON Decode of Arrays and Dictionaries in Swift Model

For some reason, I can't seem to decode the following JSON from an API, probably because my model is not right for the JSON:
{"definitions":[{"type":"noun","definition":"the larva of a
butterfly.","example":null,"image_url":null,"emoji":null},
{"type":"adjective","definition":"brand of heavy equipment.","example":null,"image_url":null,"emoji":null}],
"word":"caterpillar","pronunciation":"ˈkadə(r)ˌpilər"}
Here is my model:
struct DefinitionReturned : Codable {
let Definition : [Definition]
let word : String
let pronunciation : String
}
struct Definition : Codable {
let type: String
let definition: String
let example: String?
let image_url: String?
let emoji : String?
}
The code to decode is:
let json = try? JSONSerialization.jsonObject(with: data, options: [])
do {
let somedefinitions = try JSONDecoder().decode(DefinitionReturned.self, from: data)
print("here are the definitions",somedefinitions)
}
The error is:
ERROR IN DECODING DATA
The data couldn’t be read because it is missing.
keyNotFound(CodingKeys(stringValue: "Definition", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Definition\", intValue: nil) (\"Definition\").", underlyingError: nil))
Of note, there can be one or more definitions.
What am I doing wrong?
// MARK: - DefinitionReturned
struct DefinitionReturned: Codable {
let definitions: [Definition]
let word, pronunciation: String
}
// MARK: - Definition
struct Definition: Codable {
let type, definition: String
let example, imageURL, emoji: String?
enum CodingKeys: String, CodingKey {
case type, definition, example
case imageURL = "image_url"
case emoji
}
}
Then decode
let definitionReturned = try? JSONDecoder().decode(DefinitionReturned.self, from: jsonData)

How to display JSON data from API? [duplicate]

This question already has an answer here:
Expected to decode Array<Any> but found a dictionary instead
(1 answer)
Closed 4 years ago.
I am developing in Swift 4 and currently using Walmart's API in order to display their products and certain information about the products (example: name of product, price of product). I have read and watched many tutorials regarding parsing JSON data however I continue to get the same error. If anyone could tell me why im getting an error it would be highly appreciated seeing I have been stuck on this issue for days.
Here is the JSON data I am getting from the API call:
{
query: "ipod",
sort: "relevance",
format: "json",
responseGroup: "base",
totalResults: 3570,
start: 1,
numItems: 10,
items: [
{
itemId: 15076191,
parentItemId: 15076191,
name: "Apple iPod Touch 4th Generation 32GB with Bonus Accessory Kit",
salePrice: 189
}
I just want to display the name and salePrice data but I am unable to do so at the moment, instead I get this error: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
Here is my data model:
struct Product: Codable {
let name: String
let salePrice: String
}
Here is the code in my ViewController class:
class ViewController: UIViewController {
import Foundation
import UIKit
var products: [Product]?
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "http://api.walmartlabs.com/v1/search?query=sauce&format=json&apiKey=xyz"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let productData = try JSONDecoder().decode([Product].self, from: data)
print(productData)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
It will be like this,
struct Item: Codable {
let query: String
let sort: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems: Int
let items: [Product]
}
struct Product: Codable {
let name: String
let salePrice: CGFloat
}
Try using this,
let productData = try JSONDecoder().decode(Item.self, from: data)
Your json data is a dictionary not an array you either parse it and get the array , or try this
struct Item: Codable {
let query: String
let sort: String
let format: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems: Int
let items: [Product]
}
struct Product: Codable {
let itemId: Double
let parentItemId: Double
let name: String
let salePrice: Int
}
let productData = try JSONDecoder().decode(Item.self, from: data)

ios get decodable object parameter from json urlsession dataTask

From this reponse from URLSession dataTask, how do I transpose the data recieved:
response: {"message":"login successful","data":{"id":15,"username":"Dummy2","password":"1234","email":"Dummy2#gmail.com","createdAt":"2017-12-23T19:52:49.547Z","updatedAt":"2017-12-23T19:52:49.547Z"}}
To this struct using the statement
struct User : Codable{
var id: String
var username: String
var password: String
var email: String
var createdAt: String
var updatedAt: String
}
let user = try decoder.decode(User.self, from: jsonData)
It is of the type data but I need just the one data table, not both and I cannot subscript the received jsonData.
I cannot get the right format for the from: parameter Im knew to using REST API and JSON and could really use the help. Thank you
You need a struct for the object with message and type to wrap this response, e.g. ResponseObject:
struct User: Codable {
let id: Int // NB: You must use `Int` not `String`
let username: String
let password: String
let email: String
let createdAt: Date // NB: I'd use `Date`, not `String`
let updatedAt: Date
}
func parserUser(from data: Data) -> User? {
struct ResponseObject: Decodable {
let message: String
let data: User?
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
formatter.locale = Locale(identifier: "en_US_POSIX")
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let responseObject = try? decoder.decode(ResponseObject.self, from: data)
return responseObject?.user
}
let user = parseUser(from: data)
Note, this ResponseObject is not something that needs to be at a global scope, as it's only needed for the parsing of this particular request. Put it at whatever the narrowest scope that is best for your app (possibly just in the function.

How to store the JSON data into array(model class) in swift3?

I got nils when I try to store the JSON data into array.
I want to get the JSON data correctly
This is the result of print(articles) on APIManager class
[AppName.Article(author: nil, description: nil, publishedAt: nil, title: nil, url: nil, urlToImage: nil), AppName.Article(author: nil, description: nil, publishedAt: nil, title: nil, url: nil, urlToImage: nil)]
I have three classes and API data below.
Article
import Foundation
import SwiftyJSON
struct Article {
var author: String!
var description: String!
var publishedAt: String!
var title: String!
var url: String!
var urlToImage: String!
init(json: JSON) {
self.publishedAt = json["publishedAt"].string
self.author = json["author"].string
self.title = json["title"].string
self.description = json["desctiption"].string
self.url = json["url"].string
self.urlToImage = json["urlToImage"].string
}
// init(author: String, discription:String, publishedAt: String, title: String, url: String, urlToImage: String) {
// self.author = author
// self.discription = discription
// self.publishedAt = publishedAt
// self.title = title
// self.url = url
// self.urlToImage = urlToImage
// }
}
APIManager
import Foundation
import Alamofire
import SwiftyJSON
class APIManager {
class func getArticle(handler: #escaping (Array<Article>?) -> ()) {
Alamofire.request(
"https://newsapi.org/v1/articles?source=techcrunch&apiKey=XXX"
)
.responseJSON { response in
guard response.result.isSuccess else {
print("Error while fetching jsondata: \(response.result.error)")
return
}
guard let responseJSON = response.result.value else {
print("Invalid jsondata received from the server")
return
}
var articles: Array<Article> = []
let json = JSON(responseJSON)
//print(json)
json.forEach {(_, json) in
print(json)
articles.append(Article(json: json))
print(articles)
}
handler(articles)
}
}
}
ViewController
override func viewDidLoad() {
super.viewDidLoad()
APIManager.getArticle{ (articles: Array<Article>?) in
if let data = articles?[0] {
print(data)
}
}
API data
{
articles = (
{
author = "Matthew Lynley";
description = "Google reported mixed earnings for its fourth quarter today \U2014 but we're starting to see some flashes of improvement in its \"other bets\" category, which is..";
publishedAt = "2017-01-26T20:09:05Z";
title = "Alphabet\U2019s bets beyond search are starting to pay\U00a0off";
url = "http://social.techcrunch.com/2017/01/26/alphabets-bets-beyond-search-are-starting-to-look-better/";
urlToImage = "https://tctechcrunch2011.files.wordpress.com/2016/07/a3c4057e7d804c79b4bfb3278f4afced.jpg?w=764&h=400&crop=1";
},
{
author = "Natasha Lomas";
description = "An Executive Order signed by U.S. President Donald Trump in his first few days in office could jeopardize a six-month-old data transfer framework that enables..";
publishedAt = "2017-01-26T15:41:33Z";
title = "Trump order strips privacy rights from non-U.S. citizens, could nix EU-US data\U00a0flows";
url = "http://social.techcrunch.com/2017/01/26/trump-order-strips-privacy-rights-from-non-u-s-citizens-could-nix-eu-us-data-flows/";
urlToImage = "https://tctechcrunch2011.files.wordpress.com/2017/01/gettyimages-632212696.jpg?w=764&h=400&crop=1";
},
sortBy = top;
source = techcrunch;
status = ok;
}
If you need more information, please let me know.
Thanks.
You need to access articles array from your JSON and loop through it to get the array of Article.
let json = JSON(responseJSON)
//Get the Array of articles(JSON)
let articleArray = json["articles"].arrayValue
//Now loop through this array.
articleArray.forEach {(json) in
print(json)
articles.append(Article(json: json))
print(articles)
}

Resources