Swift parsing JSON Arrays gives zero - arrays

JSON output
{"inputs":{"lat":"29.93","lon":"-95.61","system_capacity":"30.30","azimuth":"180","tilt":"40","array_type":"1","module_type":"1","losses":"10"},"errors":[],"warnings":[],"version":"1.0.1","ssc_info":{"version":45,"build":"Linux 64 bit GNU/C++ Jul 7 2015 14:24:09"},"station_info":{"lat":29.93000030517578,"lon":-95.62000274658203,"elev":41.0,"tz":-6.0,"location":"None","city":"","state":"Texas","solar_resource_file":"W9562N2993.csv","distance":964},"outputs":{"ac_monthly":[3480.57373046875,3440.078369140625,3992.6513671875,3977.071533203125,4074.91357421875,3701.75,3897.655517578125,4248.00390625,4023.283447265625,4157.29931640625,3605.156005859375,3342.12890625],"poa_monthly":[139.791015625,140.18896484375,164.8218536376953,164.47149658203125,173.2971649169922,159.90576171875,169.84793090820312,186.20114135742188,173.14492797851562,176.2291717529297,148.28318786621094,136.62326049804688],"solrad_monthly":[4.509387493133545,5.006748676300049,5.316833972930908,5.4823832511901855,5.590230941772461,5.3301920890808105,5.4789652824401855,6.00648832321167,5.77149772644043,5.684812068939209,4.94277286529541,4.407201766967773],"dc_monthly":[3644.867919921875,3606.52001953125,4179.85107421875,4158.3193359375,4252.9140625,3865.03369140625,4069.092041015625,4432.62744140625,4198.369140625,4336.99609375,3767.055419921875,3490.091064453125],"ac_annual":45940.55859375,"solrad_annual":5.293959140777588,"capacity_factor":17.308107376098633}}{"inputs":{"lat":"29.93","lon":"-95.61","system_capacity":"30.30","azimuth":"180","tilt":"40","array_type":"1","module_type":"1","losses":"10"},"errors":[],"warnings":[],"version":"1.0.1","ssc_info":{"version":45,"build":"Linux 64 bit GNU/C++ Jul 7 2015 14:24:09"},"station_info":{"lat":29.93000030517578,"lon":-95.62000274658203,"elev":41.0,"tz":-6.0,"location":"None","city":"","state":"Texas","solar_resource_file":"W9562N2993.csv","distance":964},"outputs":{"ac_monthly":[3480.57373046875,3440.078369140625,3992.6513671875,3977.071533203125,4074.91357421875,3701.75,3897.655517578125,4248.00390625,4023.283447265625,4157.29931640625,3605.156005859375,3342.12890625],"poa_monthly":[139.791015625,140.18896484375,164.8218536376953,164.47149658203125,173.2971649169922,159.90576171875,169.84793090820312,186.20114135742188,173.14492797851562,176.2291717529297,148.28318786621094,136.62326049804688],"solrad_monthly":[4.509387493133545,5.006748676300049,5.316833972930908,5.4823832511901855,5.590230941772461,5.3301920890808105,5.4789652824401855,6.00648832321167,5.77149772644043,5.684812068939209,4.94277286529541,4.407201766967773],"dc_monthly":[3644.867919921875,3606.52001953125,4179.85107421875,4158.3193359375,4252.9140625,3865.03369140625,4069.092041015625,4432.62744140625,4198.369140625,4336.99609375,3767.055419921875,3490.091064453125],"ac_annual":45940.55859375,"solrad_annual":5.293959140777588,"capacity_factor":17.308107376098633}}
I am using the following class.
class PVClass
{
struct Top : Decodable {
var Outputs: OutputsJSON?
var Inputs: InputsJSON?
enum CodingKeys : String, CodingKey {
case Inputs = "inputs"
case Outputs = "outputs"
}
}
struct InputsJSON: Decodable {
var lat: String?
var lon: String?
enum CodingKeys : String, CodingKey {
case lat = "lat"
case lon = "lon"
}
}
struct OutputsJSON: Decodable {
var dcMonthly: DC_MonthlyJSON?
struct DC_MonthlyJSON: Decodable {
var DC_Monthly: [String]?
enum CodingKeys : String, CodingKey {
case DC_Monthly = "dc_monthly"
}
}
}
}
I access the JSON. I can print the 'lat' variable value but dc_monthly array gives me 0. I know there are 12 elements in the array. Even if I print DC_Monthly.count the value is 0. How can I access the array elements?
let decoder = JSONDecoder()
let jsonData = try decoder.decode(PVClass.Top.self, from: data!)
print("Lat: ", jsonData.Inputs?.lat ?? "0")
print("dc_monthly: ", jsonData.Outputs?.dcMonthly?.DC_Monthly ?? "0")

Your class definition does not match the structure of your JSON. Try this instead:
struct Top: Codable {
let inputs: Inputs
let outputs: Outputs
}
struct Inputs: Codable {
let lat, lon: String
}
struct Outputs: Codable {
let dcMonthly: [Double]
enum CodingKeys: String, CodingKey {
case dcMonthly = "dc_monthly"
}
}
do {
let top = try JSONDecoder().decode(Top.self, from: data)
print(top.outputs.dcMonthly.count) // 12
} catch {
print(error)
}

Related

Decode a JSON file that contains string keys each of which contains one key value pair into [(Date,Double)]

I am trying to determine how to read a JSON file that consist of a series of strings and underneath each is one key value pair into [(Date,Double)]. I have been able to do so by manually adding a main key "Time Series (Daily)" to the top of the JSON file and the structs below return [(Date,Double)]. I would like to be able to eliminate the step of adding "Time Series (Daily)" to the JSON file but still return [(Date,Double)]. Any insight on how to achieve these results would be appreciated.
{
"Time Series (Daily)": { // this entire line is manually added to JSON file
"20200803": {
"NAV": 173.94769
},
"20200804": {
"NAV": 174.57441
},
struct PrincipalTimeSeriesData {
var timeSeriesDaily: [(Date, Double)]
}
extension PrincipalTimeSeriesData: Decodable {
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
timeSeriesDaily = try container
.decode([String:PrincipalTimeSeriesDaily].self, forKey: .timeSeriesDaily)
.map { (dateFormatterPrin.date(from: $0)!, $1.close) }
}
}
struct PrincipalTimeSeriesDaily {
let close: Double
}
extension PrincipalTimeSeriesDaily: Decodable {
enum CodingKeys: String, CodingKey {
case close = "NAV"
}
}
You can decode the data as [String:PrincipalTimeSeriesDaily] and then map the keys/values of the resulting Dictionary to your desired format:
let jsonData = """
{
"20200803": {
"NAV": 173.94769
},
"20200804": {
"NAV": 174.57441
},
}
""".data(using: .utf8)!
let dateFormatterPrin = DateFormatter()
dateFormatterPrin.dateFormat = "yyyyMMdd"
struct PrincipalTimeSeriesDaily {
let close: Double
}
extension PrincipalTimeSeriesDaily: Decodable {
enum CodingKeys: String, CodingKey {
case close = "NAV"
}
}
do {
let decoded = try JSONDecoder().decode([String:PrincipalTimeSeriesDaily].self, from: jsonData)
let converedToDateKeysArray = decoded.map { item -> (Date,Double) in
(dateFormatterPrin.date(from: item.key)!,item.value.close)
}.sorted { $0.0 < $1.0 }
print(converedToDateKeysArray)
} catch {
print(error)
}

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]

Parsing nested json file with decoder

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
}

Resources