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)
Related
I am having issues with decoding JSON, where some of the fields returned from the server as not available on the client. Take a look at the following code. The JSON consists of three roles but the Role enum only consists of student and staff. How can I successfully decode JSON ignoring the missing faculty role.
let json = """
[
{
"role": "student"
},
{
"role": "staff"
},
{
"role": "faculty"
},
]
"""
struct User: Decodable {
let role: Role
}
enum Role: String, Decodable {
case student
case staff
private enum CodingKeys: String, CodingKey {
case student = "student"
case staff = "staff"
}
}
let decoded = try! JSONDecoder().decode([User].self, from: json.data(using: .utf8)!)
print(decoded)
Currently, I get the following error:
error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 2", intValue: 2), CodingKeys(stringValue: "role", intValue: nil)], debugDescription: "Cannot initialize Role from invalid String value faculty", underlyingError: nil))
It will always throw that error because the decoder will be presented with "role": "faculty" which it won't recognize. Thus if you really really want to ignore it you can ditch the decoder and do something like this:
guard let json = try? JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: .mutableContainers) as? [[String: Any]] else {return}
let users: [User] = json.filter({$0["role"] != "faculty")}.map({User(role: Role(rawValue: $0["role"])!)})
Or you can add the missing case and filter the array:
users = users.filter({$0 != Role.faculty})
You'll need to create a top-level object to hold the [User]:
struct UserList {
var users: [User]
}
Then, give it a custom decoder that will check for malformed role keys and ignore those Users, but won't ignore other errors:
extension UserList: Decodable {
// An empty object that can decode any keyed container by ignoring all keys
private struct Ignore: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var users: [User] = []
while !container.isAtEnd {
do {
// Try to decode a User
users.append(try container.decode(User.self))
}
// Check for the specific failure of "role" not decoding.
// Other errors will still be thrown normally
catch DecodingError.dataCorrupted(let context)
where context.codingPath.last?.stringValue == "role" {
// You still need to decode the object into something, but you can ignore it.
_ = try container.decode(Ignore.self)
}
}
self.users = users
}
}
let decoded = try JSONDecoder().decode(UserList.self,
from: json.data(using: .utf8)!).users
// [User(role: Role.student), User(role: Role.staff)]
If you define User.CodingKeys, then you can skip the Ignore struct:
struct User: Decodable {
// The auto-generated CodingKeys is private, so you have to
// define it by hand to access it elsewhere
enum CodingKeys: String, CodingKey {
case role
}
let role: Role
}
With that, you can get rid of the Ignore struct and replace this line:
_ = try container.decode(Ignore.self)
with:
_ = try container.nestedContainer(keyedBy: User.CodingKeys.self)
I have a JSON data that I want to parse on my swift project,
JSON data:
{
"receipt_id": 9498,
"status": "ACCEPTED",
"value": 100,
"promotionName": "Kampagne ",
"promotionId": 2062,
"imageUrl": "https://image.png",
"uploaded": "2022-02-22T11:58:21+0100"
}
On my project I have this code:
struct Receipt: Decodable {
let receiptId: Int?
let status: ReceiptStatus
let rejectionReason: String?
let value: Cent?
let promotionName: String
let promotionId: Int
let imageUrl: URL
let uploaded: Date
enum CodingKeys: String, CodingKey {
case receiptId = "receipt_id"
case status = "status"
case rejectionReason = "rejectionReason"
case value = "value"
case promotionName = "promotionName"
case promotionId = "promotionId"
case imageUrl = "imageUrl"
case uploaded = "uploaded"
}
}
When decoding JSON data this error appears:
'Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "imageUrl", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "receipts", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "No value associated with key CodingKeys(stringValue: "imageUrl", intValue: nil) ("imageUrl"), converted to image_url.", underlyingError: nil))'
On decoding the JSON data I use convertFromSnakeCase but sometimes I don't want to follow this method for decoding so I force it inside codingKeys and that error appears
Seems like the issue has nothing to do with the keyDecodingStrategy (convertFromSnakeCase). I think that the imageUrl is not a required field in your JSON and can be equal to null or even miss sometimes. Making the imageUrl property in your struct optional will fix the problem.
Try this:
struct Receipt: Codable {
let receiptID: Int
let status: String
let value: Int
let promotionName: String
let promotionID: Int
let imageURL: String
let uploaded: Date
enum CodingKeys: String, CodingKey {
case receiptID = "receipt_id"
case status, value, promotionName
case promotionID = "promotionId"
case imageURL = "imageUrl"
case uploaded
}
}
It'wrong is a
let imageUrl: URL, let uploaded: Date, let status: ReceiptStatus
Because the value of imageUrl , uploaded, status is String,
If you using Date, URL and custom enum type of your model. Please using
public init(from decoder: Decoder) throws
to cast new type you want or you set Optional to imageUrl and uploaded, status.
Apple Document
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?
I'm trying to parse a json that looks like this using decodable:
{
"count": 1,
"results": [
{
"title": 1,
"content": "Bla"
} ]
}
My problem is that I don't want to make a class that has a count property just to be able to use the decoder. I want to parse only the results part I don't care about the count.
So my question is, can decodable.decode somehow only parse a part of the result json. I mean a certain key path instead of the whole json ? And I want to do it using Decodable.
In a nutshell I don't want this:
class IncidentWrapper: Codable{
var count: Int
var incident: [Incident]
}
What I would Imagine is to have this:
decodable.decode([Incident].self, from: response.data, forKey: "results")
Thanks
let me see what I can suggest:
struct Result: Codeable {
var id: Int
var message: String
var color: String
var type: String
enum CodingKeys: String, CodingKey {
case results
}
enum NestedResultKeys: String, CodingKey {
case id, message, color, type
}
}
extension Result: Decodable {
init(from decoder: Decoder) throws {
let result = try decoder.container(keyedBy: CodingKeys.self)
let nestedResult = try result.nestedContainer(keyedBy: NestedResultKeys.self, forKey: .result)
id = try nestedResult.decode(Int.self, forKey: .id)
message = try nestedResult.decode(String.self, forKey: .message)
color = try nestedResult.decode(String.self, forKey: .color)
type = try nestedResult.decode(String.self, forKey: .id)
}
}
See this documentation for more insight
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
Hope it helps your project!
You probably is looking for JSONSerialization class. This is an example how it works:
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
if let results = json["results"] as? [Incident] {
print(results.count)
}
}
You can define a generic wrapper for once and use everywhere.
It will work as a generic wrapper for results key only.
protocol ResultsDecodable: Decodable{
associatedtype T: Decodable
var results: [T] {get}
}
struct Result<Element: Decodable>: ResultsDecodable{
typealias T = Element
var results: [Element]
}
Extend JSONDecoder to get results output.
extension JSONDecoder {
func resultDecode<T>(_ type: Result<T>.Type, from data: Data) throws -> [T] where T : Decodable{
let model = try! decode(type, from: data)
return model.results
}
}
And use like this
var str = #"{"count": 1,"results": [{"title": 1,"content": "Bla"}, {"title": 2,"content": "Bla"} ]}"#
class Incident: Decodable{
let title: Int
let content: String
}
let indicents = (try! JSONDecoder().resultDecode(Result<Incident>.self, from: str.data(using: .utf8)!))
See how it makes everything more complex. BETTER USE IncidentWrapper!!!
You only need to use the keys you care about. Just leave off the count. Don't make it part of your struct.
You will only get errors if you can't find a key in the json that you are expecting in the struct. You can avoid this too, though, if you make it an optional in the struct.
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)