Swift: How to parse and decode JSON containing an array of dictionaries - json

I'm trying to parse the JSON at a certain API endpoint using the Codable protocol in Swift. How do you properly decode this?
So far, I've created a Model struct to represent the Post objects located at the endpoint. I'm also using JSONDecoder() to decode the array of Post objects. I've tried restructuring my Model to have nested structs but that doesn't work.
Here's a sample of the JSON:
[
{
"ID": 1,
"title": "Title 1",
"content": "Content 1"
...
},
{
"ID": 2,
"title": "Title 2",
"content": "Content 2"
...
}
]
Here's the Model:
struct Post: Codable {
let id: Int
let title, content: String
enum CodingKeys: String, CodingKey {
case id = "ID"
case title, content
}
}
Here's a sample of the fetching code:
let task = URLSession.shared.dataTask(with: url) {
data, _, error in
if let error = error {
print("Error: Failed to fetch data from url. [\(error.localizedDescription)]")
completion(.failure(error))
return
}
guard let data = data else {
print("Data not available.")
return
}
do {
let decoder = JSONDecoder()
let posts = try decoder.decode([Post].self, from: data)
print("POSTS: : ", posts)
// ^ THIS IS NOT WORKING. I've tried Post.self too.
} catch let jsonError {
//error
}
}
task.resume()
Here's the error I'm getting:
ERROR: keyNotFound(CodingKeys(stringValue: "ID", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"ID\", intValue: nil) (\"ID\").", underlyingError: nil))

Your code is just fine. You just need to check your json data. This error shows in the case:
When certain key in Coding keys doesn't match with the key of json data. In your case it is clearly = "ID"
To solve this check response in postMan and make the keys exactly same in Codingkeys

Related

I want to use enum value as type in Swift

I defined this structure.
import Foundation
import SwiftUI
struct Hoge: Hashable, Codable {
var id: Int
var type: Type
}
enum Type: Codable {
case one
case two
}
And I created json.
[
{
"id": 1,
"type": "one",
},
...
]
And this is decode and load json file.
import Foundation
var hogeList: [Hoge] = load("hoge.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else {
fatalError("Couldnt find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldnt load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldnt parse \(filename) as \(T.self):\n\(error)")
}
}
Crach logs shows this logs.
Fatal error: Couldnt parse hoge.json as Array:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "type", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))
You have two issues in your code:
a) there is an extra comma in your JSON which makes it invalid. Remove the comma after "one"
[
{
"id": 1,
"type": "one"
}
]
b) You need to declare your enumeration type as String:
enum Type: String, Codable {
case one, two
}
Note: I would also rename the enumeration from Type to Kind. You would need to change your json as well or provide a custom CodingKeys to your struct

Issue Parsing / Decoding JSON from API Endpoint Into Struct Object

I am writing a Swift 5.x app using Alamofire 5 to get a list of files from an API I wrote. The API returns the file list as a JSON Data object. I want to then get this data into a struct I created. I am not able to get this working. Here is an example JSON string that my server sends over when you hit the API endpoint:
[{
"ID": "0d37ee7a-39bf-4eca-b3ec-b3fe840500b5",
"Name": "File01.iso",
"Size": 6148
}, {
"ID": "656e0396-257d-4604-a85c-bdd0593290cd",
"Name": "File02.iso",
"Size": 224917843
}, {
"ID": "275fdf66-3584-4899-8fac-ee387afc2044",
"Name": "File04.iso",
"Size": 5549504
}, {
"ID": "1c73f857-56b5-475b-afe4-955c9d2d87fe",
"Name": "File05.iso",
"Size": 15476866871
}, {
"ID": "bfebbca2-49de-43d7-b5d0-3461b4793b62",
"Name": "File06.iso",
"Size": 37254264
}]
I created the following Data Model in swift to hold this:
struct Files: Decodable {
let root: [File]
}
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
}
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
Then I used Alamofire 5.x to call the API endpoint and attempt to decode the JSON and place it into the object in question:
func getPackageFilesJSON() {
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: Files.self) { response in
guard let serverFiles = response.value else {
print("Error Decoding JSON")
return
}
let self.serverFilesList = serverFiles
}
}
This fails. If I debugPrint the response I get this for the result:
[Result]: failure(Alamofire.AFError.responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)))))
I have never been great at creating these data models and decoding JSON into them. I am sure I am missing something silly. I am hopeful that someone more knowledgable than me, or a second set of eyes can help me get this working.
Thanks,
Ed
There is no key root in the JSON. The root object is an array
Delete
struct Files: Decodable {
let root: [File]
}
and decode
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: [File].self) { response in ...
and move the CodingKeys enum into the File struct
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
}

Decode JSON with different structures with Swift

I have json data with the same core structure from a nosql database (PK, SK, attributes). The attributes part will be different depending on the value of SK.
Example:
[
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "#METADATA#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"attributes": {
"latitude": "53.34589121858683",
"longitude": "-6.272215191675388",
"max_choices": 50,
"number": "1544693",
"poll_open": false,
}
},
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "CHOICE#00a6ec5c-acc1-40f1-a087-31160d2cfc65",
"attributes": {
"distance": 790.95097525,
"latitude": 53.3416,
"price": "€€",
"categories": [
{
"title": "Ramen",
"alias": "ramen"
}
],
"vote_count": 0,
"longitude": -6.26274
}
}
]
Is it possible to use decode without errors? I've been stuck on this for hours.
I've defined the following:
struct Result: Codable {
var PK: String
var SK: String
var attributes: String
}
But, when I decode, I get the error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "attributes", intValue: nil)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
I just want to decode 'attributes' as a generic string and parse it later depending on the value of SK when I know how to properly handle it. Why is this so difficult?
Do you need attributes right now? Or are you just looking for pk and sk? If you do not need it just do not include
var attributes: String
in your struct. It will not have a decode error and it will decode the other two, you just will not get the attributes parameter. It is not able to decode the attributes
as a string because it is not. It is really more like a dictionary. Swift does not know how to handle that unless you specifically tell it. That being said you could always do this
struct Result: Codable {
var PK: String
var SK: String
var attributes: Attributes
}
struct Attributes: Codable {
var lattitude: String
var longitude: String
//and whatever else you know you have
}
the key is only adding the values you know will be included in the attributes or else it will give an error
You need to handle this using JSONSerialization instead of Codable. Given you still want to use the same struct you need to change it to
struct Result {
var PK: String
var SK: String
var attributes: [String: Any]
}
and decode the json like this
var result: Result?
do {
if let dictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let pk = dictionary["PK"] as? String,
let sk = dictionary["SK"] as? String,
let attributes = dictionary["attributes"] as? [String: Any] {
result = Result(PK: pk, SK: sk, attributes: attributes)
}
} catch {
print(error)
}
Now you still need to convert the attributes ([String: Any]) property of Result to something more usable but that is beyond this question

how to decodable this JSON with swift 4

I have searched for many tutorial but the problem still exists :(
First see the JSON file :
{
"header":[
"Academic Year",
"Scheme Type",
"Student Type",
"Institution",
"Deadline",
"Remark"
],
"rows":[
[
"2018-19",
"TSFS",
"Continuing",
"HKU",
"17.Apr.18",
""
],
[
"2018-19",
"TSFS",
"Continuing",
"HKBU",
"23.Apr.18",
""
]
]
}
code:
import UIKit
struct Header:Decodable{
let header:[String]
let row:[Row]
}
struct Row:Codable{
let row:[String]
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://api.data.gov.hk/v1/filter?q=%7B%22resource%22%3A%22http%3A%2F%2Fwww.wfsfaa.gov.hk%2Fsfo%2Fpsi%2Fdeadline%2FSFO_Post_Secondary_Schemes_Application_Submission_Deadlines_en.xlsx%22%2C%22section%22%3A1%2C%22format%22%3A%22json%22%7D")
else {return}
//get data
URLSession.shared.dataTask(with: url){
data,response,error in
guard let data = data else { return }
do{
let list = try JSONDecoder().decode(Header.self, from: data)
print(list)
}catch{
print(error)
}
}.resume()
}
}
error:
keyNotFound(CodingKeys(stringValue: "row", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"row\", intValue: nil) (\"row\").", underlyingError: nil))
If I delete “let row:[Row]” I can get the header part of the data:
Header(header: ["Academic Year", "Scheme Type", "Student Type", "Institution", "Deadline", "Remark"])
I think it should be a matter of reading the “rows” line, but I can't find a similar solution,
if someone knows how to solve it, please reply me, thank you very much!!
Inside Header struct row needed to be renamed to rows and the Row struct is no longer needed.
struct Header:Decodable{
let header:[String]
let rows: [[String]]
}

JSON Decoding Error - typeMissmatch Swift 4

I'm trying to parse some data from JSON, I already got that working with another API but now I have another struct and I'm getting typeMissmatch Erros...
The JSON looks like:
{
"status": 200,
"data": {
"date": "2018-04-07T00:00:00.508Z",
"featured": [
{
"id": "2345",
"name": "name",
"price": "1,000",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
],
"daily": [
{
"id": "12324",
"name": "name",
"price": "1,500",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
]
}}
And a Codable struct like that:
struct Base : Codable {
let status : Int
let data : DataItems
}
struct DataItems : Codable {
let date : String
let featured : [Featured]
let daily : [Daily]
}
struct Featured : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Daily : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Images : Codable {
let icon : String
let png : String
let gallery : String
let featured : String
}
But when I try to decode that Json I get a "Swift.DecodingError.typeMismatch" Error:
▿ Swift.DecodingError.typeMismatch
▿ typeMismatch: (2 elements)
- .0: Swift.String #0
▿ .1: Swift.DecodingError.Context
▿ codingPath: 5 elements
- CodingKeys(stringValue: "data", intValue: nil)
- CodingKeys(stringValue: "daily", intValue: nil)
▿ _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue: "Index 0"
▿ intValue: Optional(0)
- some: 0
- CodingKeys(stringValue: "images", intValue: nil)
- CodingKeys(stringValue: "featured", intValue: nil)
- debugDescription: "Expected to decode String but found a number instead."
- underlyingError: nil
My JSON Decoder:
enum Result<Value> {
case success(Value)
case failure(Error)
}
func getItems(for userId: Int, completion: ((Result<Base>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.jsonbin.io"
urlComponents.path = "/myurl"
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"secret-key": "xyz"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonDataTest = responseData {
// Now we have jsonData, Data representation of the JSON returned to us
// from our URLRequest...
// Create an instance of JSONDecoder to decode the JSON data to our
// Codable struct
let decoder = JSONDecoder()
do {
// We would use Post.self for JSON representing a single Post
// object, and [Post].self for JSON representing an array of
// Post objects
let posts = try decoder.decode(Base.self, from: jsonDataTest)
completion?(.success(posts))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
var base:Base?
func loadJson() {
getItems(for: 1) { (result) in
switch result {
case .success(let base):
self.base = base
dump(base)
case .failure(let error):
fatalError("error: \(error.localizedDescription)")
}
}
}
I'm new to swift and not sure what this Error is telling me or where the problem "decode String but found a number" is. I think there is something wrong with me struct.. I hope someone can help me there.
Please show the code where you want to parse the data to json.
let urlString = "your_url.json"
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 }
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let baseData = try JSONDecoder().decode(Base.self, from: data)
print(baseData) //whole project
print(baseData.status) //200.0
print(baseData.data.date)
for day in baseData.data.daily {
print(day.id)
print(day.images.icon)
print(day.images.featured)
print(day.images.gallery)
print(day.images.png)
print(day.name)
print(day.price)
print(day.priceIcon)
print(day.priceIconLink)
print(day.rarity)
print(day.readableType)
print(day.type)
}
for feature in baseData.data.featured {
print(feature.id)
print(feature.images.icon)
print(feature.images.featured)
print(feature.images.gallery)
print(feature.images.png)
print(feature.name)
print(feature.price)
print(feature.priceIcon)
print(feature.priceIconLink)
print(feature.rarity)
print(feature.readableType)
print(feature.type)
}
} catch let jsonError {
print(jsonError)
}
}.resume()
I tried this and it works for me.
By the way I was a little bit confused that Featured and Daily have all the same variables but are different models.
EDIT
Your posted data in the question are valid. The json from https://api.jsonbin.io/b/5acbd2dc214f9a2b84c6f167/1 is wrong or not consistent.
There is "featured": false in Daily and in Featured it is a string. In the struct is a string expected. So you will get a mismatch. Once you try to parse a string (works) and then you try to parse a boolean to a string (error).