Try to parse JSON Array of Arrays on Swift - arrays

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?
}

Related

Decode in Swift a JSON response to an array of struct using top level data

I have a JSON response from an API that looked like this:
{
"data": {
"author_id": "wxyz",
"author_name": "Will",
"language": "English",
"books": [
{"book_id":"abc1", "book_name":"BookA"},
{"book_id":"def2", "book_name":"BookB"},
{"book_id":"ghi3", "book_name":"BookC"}
]
}
}
Currently my Book structs looks like this:
struct Book: Codable {
let book_id: String
let book_name: String
}
But I would like to have a Book like this (with data from top level):
struct Book: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}
Using Codable (/decoding custom types), how can I transform the JSON response above directly to a list of books (while some of the data comes from top level object)?
When I use decode I can automatically decode the books array to an array of Book:
let books = try decoder.decode([Book].self, from: jsonData)
But I cannot find the way to pass the author name and id, or the language because it's in the top level
You might be able to do so with a custom init(decoder:), but another way is to hide the internal implementation where you stick to the JSON Model, and use lazy var or computed get. Lazy var will be load only once, it depends if you keep or not the root.
struct Root: Codable {
//Hidde,
private let data: RootData
//Hidden
struct RootData: Codable {
let author_id: String
let author_name: String
let language: String
let books: [RootBook]
}
//Hidden
struct RootBook: Codable {
let book_id: String
let book_name: String
}
lazy var books: [Book] = {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: $0.book_id, name: $0.book_name, author: author, language: data.language)
}
}()
var books2: [Book] {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: $0.book_id, name: $0.book_name, author: author, language: data.language)
}
}
}
//Visible
struct Book: Codable {
let id: String
let name: String
let author: Author
let language: String
}
//Visible
struct Author: Codable {
let id: String
let name: String
}
Use:
do {
var root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books)") //Since it's a lazy var, it need to be mutable
} catch {
print("Error: \(error)")
}
or
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books2)")
} catch {
print("Error: \(error)")
}
Side note: The easiest way is to stick to the JSON Model indeed. Now, it might be interesting also to have internal model, meaning, your have your own Book Class, that you init from Root. Because tomorrow, the JSON might change (change of server, etc.). Then the model used for your views (how to show them) might also be different...
Separate your layers, wether you want to use MVC, MVVM, VIPER, etc.
EDIT:
You can with an override of init(decoder:), but does it make the code clearer? I found it more difficult to write than the previous version (meaning, harder to debug/modify?)
struct Root2: Codable {
let books: [Book2]
private enum TopLevelKeys: String, CodingKey {
case data
}
private enum SubLevelKeys: String, CodingKey {
case books
case authorId = "author_id"
case authorName = "author_name"
case language
}
private enum BoooKeys: String, CodingKey {
case id = "book_id"
case name = "book_name"
}
init(from decoder: Decoder) throws {
let topContainer = try decoder.container(keyedBy: TopLevelKeys.self)
let subcontainer = try topContainer.nestedContainer(keyedBy: SubLevelKeys.self, forKey: .data)
var bookContainer = try subcontainer.nestedUnkeyedContainer(forKey: .books)
var books: [Book2] = []
let authorName = try subcontainer.decode(String.self, forKey: .authorName)
let authorid = try subcontainer.decode(String.self, forKey: .authorId)
let author = Author(id: authorid, name: authorName)
let language = try subcontainer.decode(String.self, forKey: .language)
while !bookContainer.isAtEnd {
let booksubcontainer = try bookContainer.nestedContainer(keyedBy: BoooKeys.self)
let bookName = try booksubcontainer.decode(String.self, forKey: .name)
let bookId = try booksubcontainer.decode(String.self, forKey: .id)
books.append(Book2(book_id: bookId, book_name: bookName, author: author, language: language))
}
self.books = books
}
}
struct Book2: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}

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
}

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
}

Parsing array with {"Name": String, "Value": String} structure in Swift 4

I need to parse in Swift a data structure similar to this (based on a JSON):
[
{
"Name": "uniquename",
"Value": "John"
},
{
"Name": "locale",
"Value": "UK"
},
]
I stored this node in a struct like this
struct Rowset : Decodable {
var row: LoggedUserSession
init(loggedUser: [LoggedUserSession]){
self.row = loggedUser[0]
}
enum CodingKeys: String, CodingKey{
case row = "Row"
}
}
I prepared a similar struct to all the data I need to extract from the array but I don't know how to iterate on that and return the value when the name string match my case.
struct LoggedUserSession : Decodable {
var username: String;
var locale: String;
init(username: String, locale: String) {
// In JS I would embedd an iterator here and return the values
self.username = username
self.locale = locale
}
enum CodingKeys: String, CodingKey {
case username = "uniquename"
case locale = "locale"
}
}
If I understand what you are saying correctly, you want to parse an array of LoggedUserSession JSONs into a swift array of LoggedUserSessions. If that's the case, then you're almost there.
For completeness, I'm going to interpret the JSON you posted as follows so that it is valid:
{"loggedUserSessions":
[
{
"uniquename": "John",
"locale": "UK"
}
]
}
Your LoggedUserSession object is implemented correctly, so now all you need is the array parsing part. You can do that with the following struct:
struct SessionList: Decodable {
let sessions: [LoggedUserSession]
}
Calling this with JSONDecoder and your JSON data should serialize your list into an array that you can access via the SessionList's sessions property.

Swift parsing JSON Arrays gives zero

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)
}

Resources