Parsing nested json file with decoder - arrays

I am having trouble parsing this json file
{"window":60,"version":200,"timestamp":1582886130,"body":{"totals":{"count":1,"offset":0},"audio_clips":[{"id":7515224,"title":"The West Ham Way Podcast - We`re back!","description":"This is a message from Dave to confirm that the boys are back....\nThe show will be recorded EVERY Wednesday and published on the same night. Please subscribe to this Podcast on your preferred platform and get the West Ham Way back to where they were before it was taken away from them.\nAs always, thanks for your support. COYI!\n#DaveWalkerWHU\n#ExWHUemployee","updated_at":"2020-02-27T11:44:18.000Z","user":{"id":5491115,"username":null,"urls":{"profile":"https://audioboom.com/users/5491115","image":"https://images.theabcdn.com/i/composite/%231c5fc7/150x150/avatars%2Fsmile-1.svg","profile_image":{"original":"https://images.theabcdn.com/i/composite/%231c5fc7/150x150/avatars%2Fsmile-1.svg"}}},"link_style":"channel","channel":{"id":5019730,"title":"The West Ham Way Podcast","urls":{"detail":"https://audioboom.com/channels/5019730","logo_image":{"original":"https://images.theabcdn.com/i/36150002"}}},"duration":32.4895,"mp3_filesize":575908,"uploaded_at":"2020-02-26T13:01:01.000Z","recorded_at":"2020-02-26T13:01:01.000+00:00","uploaded_at_ts":1582722061,"recorded_at_ts":1582722061,"can_comment":false,"can_embed":true,"category_id":283,"counts":{"comments":0,"likes":0,"plays":12},"urls":{"detail":"https://audioboom.com/posts/7515224-the-west-ham-way-podcast-we-re-back","high_mp3":"https://audioboom.com/posts/7515224-the-west-ham-way-podcast-we-re-back.mp3","image":"https://images.theabcdn.com/i/36150892","post_image":{"original":"https://images.theabcdn.com/i/36150892"},"wave_img":"https://images.theabcdn.com/i/w/8560839"},"image_attachment":36150892}]}}
At the moment im just trying to get the title and description from the json but i keep getting error message
keyNotFound(CodingKeys(stringValue: "audioclips", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "body", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"audioclips\", intValue: nil) (\"audioclips\").", underlyingError: nil))
here is my class for trying to parse the json
import UIKit
class PodcastViewController: UIViewController {
struct Root : Decodable {
var body : Body
}
struct Body : Decodable {
enum Codingkeys : String, CodingKey {
case audioclips = "audio_clips"
}
var audioclips : [Clips]
}
struct Clips : Decodable{
enum CodingKeys : String, CodingKey {
case title = "title"
case description = "description"
}
var title : String
var description : String
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = URL(string: "https://api.audioboom.com/channels/5019730/audio_clips?api_version=1")!
URLSession.shared.dataTask(with: url) {data, response, error in
if let data = data {
print("DATA EXISTS")
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
// print("error while parsing:\(error.localizedDescription)")
print(error)
}
}
}.resume()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Any ideas why it says the audio clips key can not be found even though i am renaming it audio_clips

In order to be detected and used, you coding keys must have the correct name, which is CodingKeys and not Codingkeys (uppercase 'K'):
struct Body : Decodable {
enum CodingKeys : String, CodingKey {
case audioclips = "audio_clips"
}
var audioclips : [Clips]
}

Just replace CodingKeys(uppercase 'K') instead of Codingkeys(lowercase 'K')
enum CodingKeys : String, CodingKey {
case audioclips = "audio_clips"
}

Based on your response, this can be your Codable struct. Try parsing using below struct.
// MARK: - Response
struct Response: Codable {
let window, version, timestamp: Int
let body: Body
}
// MARK: - Body
struct Body: Codable {
let totals: Totals
let audioClips: [AudioClip]
enum CodingKeys: String, CodingKey {
case totals
case audioClips = "audio_clips"
}
}
// MARK: - AudioClip
struct AudioClip: Codable {
let id: Int
let title, audioClipDescription, updatedAt: String
let user: User
let linkStyle: String
let channel: Channel
let duration: Double
let mp3Filesize: Int
let uploadedAt, recordedAt: String
let uploadedAtTs, recordedAtTs: Int
let canComment, canEmbed: Bool
let categoryID: Int
let counts: Counts
let urls: AudioClipUrls
let imageAttachment: Int
enum CodingKeys: String, CodingKey {
case id, title
case audioClipDescription = "description"
case updatedAt = "updated_at"
case user
case linkStyle = "link_style"
case channel, duration
case mp3Filesize = "mp3_filesize"
case uploadedAt = "uploaded_at"
case recordedAt = "recorded_at"
case uploadedAtTs = "uploaded_at_ts"
case recordedAtTs = "recorded_at_ts"
case canComment = "can_comment"
case canEmbed = "can_embed"
case categoryID = "category_id"
case counts, urls
case imageAttachment = "image_attachment"
}
}
// MARK: - Channel
struct Channel: Codable {
let id: Int
let title: String
let urls: ChannelUrls
}
// MARK: - ChannelUrls
struct ChannelUrls: Codable {
let detail: String
let logoImage: Image
enum CodingKeys: String, CodingKey {
case detail
case logoImage = "logo_image"
}
}
// MARK: - Image
struct Image: Codable {
let original: String
}
// MARK: - Counts
struct Counts: Codable {
let comments, likes, plays: Int
}
// MARK: - AudioClipUrls
struct AudioClipUrls: Codable {
let detail: String
let highMp3: String
let image: String
let postImage: Image
let waveImg: String
enum CodingKeys: String, CodingKey {
case detail
case highMp3 = "high_mp3"
case image
case postImage = "post_image"
case waveImg = "wave_img"
}
}
// MARK: - User
struct User: Codable {
let id: Int
let username: String?
let urls: UserUrls
}
// MARK: - UserUrls
struct UserUrls: Codable {
let profile: String
let image: String
let profileImage: Image
enum CodingKeys: String, CodingKey {
case profile, image
case profileImage = "profile_image"
}
}
// MARK: - Totals
struct Totals: Codable {
let count, offset: Int
}

Related

Decodeable SWIFT parsing Rest API data error

I am trying to query the marvel API. I believe my decodable is wrong. I have the following code:
struct ReturnedData : Decodable {
var id : Int?
var name : String?
var description : String?
}
var savedData : [ReturnedData] = []
let urlString = "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)"
let url = URL(string: urlString)
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { (data, response, error) in
guard let data = data else {return}
do {
let recievedData = try JSONDecoder().decode([ReturnedData].self, from: data)
self.savedData = recievedData
} catch {
print(error)
}
}
dataTask.resume()
}
I am getting the following error message:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
according to the documentation, I should get all of the below:
Character
id (int, optional): The unique ID of the character resource.,
name (string, optional): The name of the character.
description (string, optional): A short bio or description of the character.
modified (Date, optional): The date the resource was most recently modified.
resourceURI (string, optional): The canonical URL identifier for this resource.
urls (Array[Url], optional): A set of public web site URLs for the resource.
thumbnail (Image, optional): The representative image for this character.
comics (ComicList, optional): A resource list containing comics which feature this character.
stories (StoryList, optional): A resource list of stories in which this character appears
events (EventList, optional): A resource list of events in which this character appears.
series (SeriesList, optional): A resource list of series in which this character appears.
Also, any tips on how to get the thumbnail image is appreciated.
The error you get is because the model you have does not match the json data. So, try this approach ...to query the marvel API....
In future, copy and paste your json data into https://app.quicktype.io/
this will generate the models for you. Adjust the resulting code to your needs. Alternatively read the docs at: https://developer.marvel.com/docs#!/public/getCreatorCollection_get_0 and create the models by hand from the info given.
class MarvelModel: ObservableObject {
#Published var results = [MarvelResult]()
let myAPIKey = "xxxx"
let myhash = "xxxx"
func getMarvels() async {
guard let url = URL(string: "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)&hash=\(myhash)") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(MarvelResponse.self, from: data)
Task{#MainActor in
results = response.data.results
}
} catch {
print("---> error: \(error)")
}
}
}
struct ContentView: View {
#StateObject var vm = MarvelModel()
var body: some View {
VStack {
if vm.results.isEmpty { ProgressView() }
List(vm.results) { item in
Text(item.name)
}
}
.task {
await vm.getMarvels()
}
}
}
struct MarvelResponse: Decodable {
let code: Int
let status, copyright, attributionText, attributionHTML: String
let etag: String
let data: MarvelData
}
struct MarvelData: Decodable {
let offset, limit, total, count: Int
let results: [MarvelResult]
}
struct MarvelResult: Identifiable, Decodable {
let id: Int
let name, resultDescription: String
let modified: String
let thumbnail: Thumbnail
let resourceURI: String
let comics, series: Comics
let stories: Stories
let events: Comics
let urls: [URLElement]
enum CodingKeys: String, CodingKey {
case id, name
case resultDescription = "description"
case modified, thumbnail, resourceURI, comics, series, stories, events, urls
}
}
struct Comics: Decodable {
let available: Int
let collectionURI: String
let items: [ComicsItem]
let returned: Int
}
struct ComicsItem: Identifiable, Decodable {
let id = UUID()
let resourceURI: String
let name: String
}
struct Stories: Decodable {
let available: Int
let collectionURI: String
let items: [StoriesItem]
let returned: Int
}
struct StoriesItem: Identifiable, Decodable {
let id = UUID()
let resourceURI: String
let name: String
let type: String
}
struct Thumbnail: Decodable {
let path: String
let thumbnailExtension: String
enum CodingKeys: String, CodingKey {
case path
case thumbnailExtension = "extension"
}
}
struct URLElement: Identifiable, Decodable {
let id = UUID()
let type: String
let url: String
}
EDIT-1: if you want something very basic, then try this:
struct ContentView: View {
#State var results = [MarvelResult]()
var body: some View {
List(results) { item in
Text(item.name)
}
.onAppear {
getMarvels()
}
}
func getMarvels() {
let myAPIKey = "xxxx"
let myhash = "xxxx"
guard let url = URL(string: "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)&hash=\(myhash)") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let response = try JSONDecoder().decode(MarvelResponse.self, from: data)
DispatchQueue.main.async {
self.results = response.data.results
}
}
catch {
print(error)
}
}
}.resume()
}
}

Decoding an array of objects with nested heterogeneous child objects

An example of the response from the server is below.
The list consists of elements that have heterogeneous substructures in the info fields. Each of them contains 3 fields with the same types, but they have different keys.
I don't know how to decode this, I haven't encountered such a problem so far. I can't find an example on the Internet that fits this case.
I wanted to decode the enum type at the beginning and select the appropriate info structure based on it, but it doesn't work.
I would very much appreciate your help.
{
"data":[
{
"type":"league",
"info":{
"name":"NBA",
"sport":"Basketball",
"website":"https://nba.com/"
}
},
{
"type":"player",
"info":{
"name":"Kawhi Leonard",
"position":"Small Forward",
"picture":"https://i.ibb.co/b5sGk6L/40a233a203be2a30e6d50501a73d3a0a8ccc131fv2-128.jpg"
}
},
{
"type":"team",
"info":{
"name":"Los Angeles Clippers",
"state":"California",
"logo":"https://logos-download.com/wp-content/uploads/2016/04/LA_Clippers_logo_logotype_emblem.png"
}
}
]
}
Your code on pastebin is too complicated, I mean this
let jsonString = """
{
"data":[
{
"type":"league",
"info":{
"name":"NBA",
"sport":"Basketball",
"website":"https://nba.com/"
}
},
{
"type":"player",
"info":{
"name":"Kawhi Leonard",
"position":"Small Forward",
"picture":"https://i.ibb.co/b5sGk6L/40a233a203be2a30e6d50501a73d3a0a8ccc131fv2-128.jpg"
}
},
{
"type":"team",
"info":{
"name":"Los Angeles Clippers",
"state":"California",
"logo":"https://logos-download.com/wp-content/uploads/2016/04/LA_Clippers_logo_logotype_emblem.png"
}
}
]
}
"""
struct Response: Decodable {
let data: [Datum]
}
struct League: Codable {
let name: String
let sport: String
let website: URL
}
struct Player: Codable {
let name: String
let position: String
let picture: URL
}
struct Team: Codable {
let name: String
let state: String
let logo: URL
}
enum Datum: Decodable {
case league(League)
case player(Player)
case team(Team)
enum DatumType: String, Decodable {
case league
case player
case team
}
private enum CodingKeys : String, CodingKey { case type, info }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DatumType.self, forKey: .type)
switch type {
case .league:
let item = try container.decode(League.self, forKey: .info)
self = .league(item)
case .player:
let item = try container.decode(Player.self, forKey: .info)
self = .player(item)
case .team:
let item = try container.decode(Team.self, forKey: .info)
self = .team(item)
}
}
}
do {
let response = try JSONDecoder().decode(Response.self, from: Data(jsonString.utf8))
let data = response.data
print(data)
// receivedData.forEach { (datum) in
// let cell = Cell()
// cell.configure(with: datum.info.rowData)
// cells.append(cell)
// }
// cells.forEach({ print($0.title, $0.subtitle) })
} catch {
print(error)
}
In the cell switch on the type
switch datum {
case .league(let league): // so something with league
case .player(let player): // so something with player
case .team(let team): // so something with team
}

Try to parse JSON Array of Arrays on Swift

Here is the json code:
{
"rows":
[[{
"text":"some text",
"type":{
"type":"inlineUrl",
"url":"https://nothing.com"
}
}],
[{
"text":"some text",
"type":{
"type":"inlineCallback",
"data":"some data"
}
},
{
"text":"some text",
"type":{
"type":"inlineCallback",
"data":"some data"
}
}
]]
}
A more abbreviated would look like:
Rows = [ [Row1], [Row2, Row3] ]
And I'm trying to get it to work like this:
struct ReplyHandler: Codable {
let rows: [RowsHandler]
enum CodingKeys: String, CodingKey {
case rows
}
}
struct RowsHandler: Codable {
let row1: [RowHandler]
let row2: [Row2Handler]
enum CodingKeys: String, CodingKey {
case row1
case row2
}
}
struct RowHandler: Codable {
let text: String
let type: TypeHandler
enum CodingKeys: String, CodingKey {
case type = "type"
case text = "text"
}
}
struct Row2Handler: Codable {
let row1: RowHandler
let row2: RowHandler
enum CodingKeys: String, CodingKey {
case row1
case row2
}
}
struct TypeHandler: Codable {
let type: String
let url: String
enum CodingKeys: String, CodingKey {
case type = "type"
case url = "url"
}
}
But Xcode compiler gives this error:
Expected to decode String but found an array instead.
Don't understand how to properly nest other arrays in the array
While posting my question, I noticed that the arrays in "type" are different: the first row have url, the second and third have data, but anyway, it's not a point.
You can simplify your model to something like this:
struct ReplyHandler: Codable {
let rows: [[RowHandler]]
}
struct RowHandler: Codable {
let text: String
let type: TypeHandler
}
struct TypeHandler: Codable {
let type: String
var url: String?
var data: String?
}
Notice that url and data properties in TypeHandler are optionals to cover both cases.
Although manual JSON parsing is useful I think that it must be much easier to use tool like this https://app.quicktype.io/ to convert JSON into Swift classes or structs
For you JSON it provides this result
// MARK: - Welcome
struct Welcome: Codable {
let rows: [[Row]]
}
// MARK: - Row
struct Row: Codable {
let text: String
let type: TypeClass
}
// MARK: - TypeClass
struct TypeClass: Codable {
let type: String
let url: String?
let data: String?
}

Decoding JSON array with codable - Swift 5

I would like to decode the json data in the following link
https://stats.nba.com/stats/assistleaders?LeagueID=00&PerMode=Totals&PlayerOrTeam=Team&Season=2019-20&SeasonType=Regular+Season
However, the rowSet data is an set of json array in inside an array. How to set up the codable struct to decode this data? I am able to set up the following struct to decode the other data.
import Foundation
struct leagueLeader: Codable {
var resource: String
var parameters: parameters
}
struct parameters: Codable {
var LeagueID: String
var PerMode: String
var StatCategory: String
var Season: String
var SeasonType: String
}
struct resultSet: Codable {
var name: String
var headers: [String]
}
This JSON structure is very unusual. It's a kind of comma separated values (CSV) which cannot be decoded implicitly into a struct.
A solution is to decode the array manually
struct RowSet: Decodable {
let rank, teamID : Int
let teamAbbreviation, teamName : String
let ast : Int
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
guard arrayContainer.count == 5 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain 5 items") }
rank = try arrayContainer.decode(Int.self)
teamID = try arrayContainer.decode(Int.self)
teamAbbreviation = try arrayContainer.decode(String.self)
teamName = try arrayContainer.decode(String.self)
ast = try arrayContainer.decode(Int.self)
}
}
As mentioned in the comments please conform to the naming convention: structs, classes and enums start with a uppercase letter, variables, properties, functions and enum cases start with lowercase letter.
The hard part is dealing with rowSet that supposed to contains multiple data types
{
"rowSet": [
[
1,
1610612756,
"PHX",
"Phoenix Suns",
1987
]
]
}
The solution is to declare an enum that each case has an associated value representing a codable data type
enum RowSet: Codable, Equatable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(RowSet.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RowSet"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
Full of model:
import Foundation
struct Leader: Codable, Equatable {
let resource: String?
let parameters: Parameters?
let resultSets: [ResultSet]?
}
struct Parameters: Codable, Equatable {
let leagueId, season, seasonType, perMode: String?
let playerOrTeam: String?
enum CodingKeys: String, CodingKey {
case leagueId = "LeagueID"
case season = "Season"
case seasonType = "SeasonType"
case perMode = "PerMode"
case playerOrTeam = "PlayerOrTeam"
}
}
struct ResultSet: Codable, Equatable {
let name: String?
let headers: [String]?
let rowSet: [[RowSet]]?
}
enum RowSet: Codable, Equatable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(RowSet.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RowSet"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}

How to parse this JSON format in swift

I have this JSON format:
{
"version":"7.0.19",
"fields": ["ID","pm","age","pm_0","pm_1","pm_2","pm_3","pm_4","pm_5","pm_6","conf","pm1","pm_10","p1","p2","p3","p4","p5","p6","Humidity","Temperature","Pressure","Elevation","Type","Label","Lat","Lon","Icon","isOwner","Flags","Voc","Ozone1","Adc","CH"],
"data":[[20,0.0,1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,97,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,null,null,null,1413,0,"Oakdale",40.603077,-111.83612,0,0,0,null,null,0.01,1]],
"count":11880
}
but I cannot work out how to use a Codable protocol to parse the json response.
this would be my desired model.
struct Point: Codable {
let pm2: String?
let latitude, longitude: Double?
let temp: String?
let iD: String?
enum CodingKeys: String, CodingKey {
case pm2 = "pm", temp = "Temperature", iD = "ID", latitude = "Lat", longitude = "Lon"
}
}
Here is a URL to the json
https://webbfoot.com/dataa.json
You can use Codable to parse this:
struct Response: Decodable {
let version: String
let fields: [String]
let data: [[QuantumValue?]]
let count: Int
}
enter code here
enum QuantumValue: Decodable {
case float(Float), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Float.self) {
self = .float(float)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
QuantumValue will handle both Float and String and ? will handle the null part.
This one is tricky and requires manual decoding. The principle would be to define a mapping between the fields you expect to decode and the properties of your object, then depending on the type of the property (String, Double, etc...), attempt to decode the data.
Second, since you have an array of points, you need some kind of container object to hold the array, for example:
struct Points {
var data: [Point] = []
}
First, some of your model properties don't match the type in the data, e.g. iD is a String, but the data has an Int. For simplicity, I'll redefine your model to match the data
struct Point {
var pm2: Int? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var temp: Int? = nil
var iD: Int? = nil
}
Now, write the manual decoder for the parent container Points:
extension Points: Decodable {
static let mapping: [String: PartialKeyPath<Point>] = [
"pm": \Point.pm2,
"Lat": \Point.latitude,
"Lon": \Point.longitude,
"Temperature": \Point.temp,
"ID": \Point.iD
]
enum CodingKeys: CodingKey { case fields, data }
private struct Dummy: Decodable {} // see below why
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let fields = try container.decode([String].self, forKey: .fields)
var data = try container.nestedUnkeyedContainer(forKey: .data)
while !data.isAtEnd {
var row = try data.nestedUnkeyedContainer()
var point = Point()
for field in fields {
let keyPath = Points.mapping[field]
switch keyPath {
case let kp as WritableKeyPath<Point, String?>:
point[keyPath: kp] = try row.decodeIfPresent(String.self)
case let kp as WritableKeyPath<Point, Int?>:
point[keyPath: kp] = try row.decodeIfPresent(Int.self)
case let kp as WritableKeyPath<Point, Double?>:
point[keyPath: kp] = try row.decodeIfPresent(Double.self)
default:
// this is a hack to skip this value
let _ = try? row.decode(Dummy.self)
}
}
self.data.append(point)
}
}
}
Once you have that, you can decode the JSON like so:
let points = try JSONDecoder().decode(Points.self, from: jsonData)
let firstPoint = points.data[0]

Resources