How to create a struct model from a complex JSON response - ios

I am trying to consume data from an api in swift, json data has successfully been delivered back to the app but the json response from my backend is very complex hence forming struct for my model is very difficult. I'm able to only retrieve the simple strings but if I add the objects and arrays everything stops working
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": []
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
struct Responder: Codable {
let type: String
let id: String
let description: String
let title: String
let promoted: Bool
let certification: String
let firstAirDate: String
let lastAirDate: String
let numberEpisodes: Int
let numberSeasons: Int
let userRating: Int?
let inUserList: Bool
let thumbnail: PackShotObj
let amazonPrime: linksObj
}
struct PackShotObj: Codable {
let packShot: [String]
}
struct linksObj: Codable {
let link: String
let format: String
}
struct genres: Codable {
let empty: String
}

Here is the code that works, decoding your json data. Note the differences between my struct models and yours. You will need to consult the docs of the server to determine which fields are optionals and adjust the code for that:
struct ContentView: View {
#State var responders: [Responder] = []
var body: some View {
List(responders) { responder in
Text(responder.title)
Text(responder.description)
Text(responder.releaseDate)
}
.onAppear {
let json = """
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": [],
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
"""
// simulated API data
let data = json.data(using: .utf8)!
do {
self.responders = try JSONDecoder().decode([Responder].self, from: data)
print("\n---> responders: \n \(responders)\n")
} catch {
print("\n---> decoding error: \n \(error)\n")
}
}
}
}
// MARK: - Responder
struct Responder: Identifiable, Codable {
let type, id, title, description: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Int?
let inUserList: Bool
let packShot, backdrop: Backdrop
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [Provider]
let appleTvPlus: [Provider]
let disneyPlus: [Provider]
let iTunes: [Provider]
let netflix: [Provider]
let youTubePremium: [Provider]
}
struct Provider: Codable {
let link, format: String
}

Just copy and paste this model to file and you are good to go.
struct Responder: Codable {
let type, id, title, welcomeDescription: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Any?
let inUserList: Bool
let packShot, backdrop: Backdrop
enum CodingKeys: String, CodingKey {
case type, id, title
case welcomeDescription = "description"
case categories, genres, links, promoted, certification, releaseDate, runTime, userRating, inUserList, packShot, backdrop
}
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [AmazonPrime]
let appleTvPlus, disneyPlus: [String]
let iTunes: [AmazonPrime]
let netflix, youTubePremium: [String]
}
// MARK: - AmazonPrime
struct AmazonPrime: Codable {
let link, format: String
}

Related

Elegant solutions to decoding a JSON nested array in swift using decodable

Currently working with an API, and while I have successfully gotten it to decode the full result, I am only interested in the Entities/identifier portion. While I have gotten it working and get what I want/need I feel like this could be done better and more elegant and maybe in a single step. Any insight/suggestions appreciated.
JSON returned from API:
{
"count": 4596,
"entities": [
{
"facet_ids": [
"contact",
"siftery",
"investor",
"ipqwery",
"aberdeen",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"bombora",
"key_event"
],
"identifier": {
"uuid": "468bef9f-2f50-590e-6e78-62e3adb05aa1",
"value": "Citi",
"image_id": "v1417152861/pdgwqt8ddecult5ktvdf.jpg",
"permalink": "citigroup",
"entity_def_id": "organization"
},
"short_description": "Citigroup is a diversified financial services holding company that provides various financial products and services."
},
{
"facet_ids": [
"contact",
"siftery",
"investor",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"key_event"
],
"identifier": {
"uuid": "031a344b-c2b9-e60b-d950-1ae062026fde",
"value": "Citi",
"image_id": "yzlzhjqpparamrswaqa1",
"permalink": "citi-2",
"entity_def_id": "organization"
},
"short_description": "CITi is an NPO supporting the ICT sector in Western Cape."
},
{
"facet_ids": [
"contact",
"siftery",
"semrush",
"company",
"rank",
"builtwith",
"bombora"
],
"identifier": {
"uuid": "7ce45379-957c-49c5-bca2-c9ffd521f7da",
"value": "CITI",
"image_id": "qbkqndm7d0wgbogxjcrs",
"permalink": "citi-f7da",
"entity_def_id": "organization"
},
"short_description": "CITI trusted gateway to project-based change expertise that major organisations need to thrive, change and innovate."
}
]
}
Structs:
struct Entity: Decodable, Identifiable
{
var id: String
var companyName: String
var permalink: String
var imageID: String
init(from entity: Entities.Entity) throws
{
self.id = entity.identifier?.uuid ?? ""
self.companyName = entity.identifier?.value ?? ""
self.permalink = entity.identifier?.permalink ?? ""
self.imageID = entity.identifier?.image_id ?? ""
}
}
struct Entities: Decodable
{
var count:Int?
var entities: [Entity]?
struct Entity: Decodable
{
var facet_ids:[String]?
var identifier:Identifier?
var short_description:String?
}
struct Identifier:Decodable
{
var permalink:String?
var value:String?
var image_id:String?
var entity_def_id:String?
var uuid:String?
}
}
Call to decode:
if let data = data{
do {
let businessEntities = try decoder.decode(Entities.self, from: data)
let entities:[Entity] = try businessEntities.entities!.compactMap{
entity in
do
{
return try Entity(from: entity)
}
}
Thinking you are just interested in the Entities/identifier, you can simplify your model. Here's an example:
typealias Entities = [Entitie]
struct Entitie: Codable {
let facetIDS: [String]
let identifier: Identifier
let shortDescription: String
enum CodingKeys: String, CodingKey {
case facetIDS = "facet_ids"
case identifier
case shortDescription = "short_description"
}
}
struct Identifier: Codable {
let uuid, value, imageID, permalink: String
let entityDefID: String
enum CodingKeys: String, CodingKey {
case uuid, value
case imageID = "image_id"
case permalink
case entityDefID = "entity_def_id"
}
}
You can access entities object and decode it like this:
guard let data = data,
let jsonDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let entitiesObj = jsonDict["entities"] else { return }
do {
let entitiesData = try JSONSerialization.data(withJSONObject: entitiesObj)
let result = try JSONDecoder().decode(Entities.self, from: entitiesData)
} catch {
print(error.localizedDescription)
}
Obs: I created the CodingKeys because I use camelCase in my projects, you can left it in snake_case just remember to replace variables.

get position in a api swift Codable and Model data

i am getting data from an api like this
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
I have my model like this
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
}
But I dont know how to define the "internData", I tried with another Model "InterData" and define id and name like the PersonData, but i get an error, i tried also with [String:Any] but i get an error for the Codable protocol
I am using
let resP = try JSONSerialization.jsonObject(with: data, options: .init()) as? [String: AnyObject]
print("resP", )
in my script of Service/Network
Thanks if somebody knows
You can't use [String:Any] type in case of Codable. you need to create an another model of InternData, which is used by PersonData.
Code:
JSON Data :
let jsonData =
"""
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
"""
// Models
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
let internData : InternData?
}
// New model
struct InternData : Codable {
let id : String?
let name : String?
}
// Parsing
do {
let parseRes = try JSONDecoder().decode([PersonData].self, from: Data(jsonData.utf8))
print(parseRes)
}
catch {
print(error)
}

Use Codable And Decodable in API Call

I'm trying to call API using Codable and i want to access all dictionary, arrays from API.
Is This Possible from codable?
API Response eg:
{
"status": true,
"logo": "https://abc.png",
"data": [
{
"crumb": {
"Menu": {
"navigate": "Home",
},
},
"path": "2",
"type": "type0",
"orientation": [
{
"name": "All",
}
],
},
]
}
The API response you've posted is invalid JSON (it has a bunch of trailing commas that make it illegal). This needs to be changed on the producer's side, and when you've done that, you can use this struct to access the data:
struct Entry: Codable {
let status: Bool
let logo: String
let data: [Datum]
}
struct Datum: Codable {
let crumb: Crumb
let path, type: String
let orientation: [Orientation]
}
struct Crumb: Codable {
let menu: Menu
enum CodingKeys: String, CodingKey {
case menu = "Menu"
}
}
struct Menu: Codable {
let navigate: String
}
struct Orientation: Codable {
let name: String
}

Modelling an 'object' type to decode JSON

I'm making multiple calls to the Apple Music API which is returning an array of Resource objects. The Resource object has a property called attributes of type object which varies depending on whether it is a Song, Playlist, or Album.
How do I model the resource object so I can decode the JSON and create Song, Playlist, and Album objects?
struct Resource {
let id : String?
let type : String?
let href : String?
let attributes : "What goes here?"
}
Update 1:
I tried Joe's second recommendation and set up my structs like so:
struct Resource: Decodable{
enum Attribute {
case song(Song), playlist(Playlist)
}
let id : String?
let type : String?
let href : String?
let attributes: Attribute?
enum CodingKeys : CodingKey{
case id, type, href, attributes
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
type = try values.decode(String.self, forKey: .type)
href = try values.decode(String.self, forKey: .href)
switch type {
case "library-songs":
attributes = try .song(values.decode(Song.self, forKey: .attributes))
case "library-playlists":
attributes = try .playlist(values.decode(Playlist.self, forKey: .attributes))
default:
attributes = nil
}
}
}
struct Song : Decodable {
let playParams : PlayParams?
let trackNumber : Int?
let durationInMillis : Int?
let name : String?
let albumName : String?
let artwork : Artwork?
let contentRating : String?
let artistName : String?
}
struct Artwork : Decodable {
let width : Int?
let height : Int?
let url : String?
}
struct PlayParams : Decodable {
let id : String?
let kind : String?
let isLibrary : Bool?
}
The sample JSON looks like this for song:
{
"data": [
{
"id": "i.4YBNbl3IXVJQRM",
"type": "library-songs",
"href": "/v1/me/library/songs/i.4YBNbl3IXVJQRM",
"attributes": {
"albumName": "\"Awaken, My Love!\"",
"artwork": {
"width": 1200,
"height": 1200,
"url": "https://is5-ssl.mzstatic.com/image/thumb/Music71/v4/00/d0/d7/00d0d743-b0de-31d8-09eb-0796269bb555/UMG_cvrart_00044003187658_01_RGB72_1800x1800_16UMGIM77118.jpg/{w}x{h}bb.jpg"
},
"durationInMillis": 326933,
"playParams": {
"id": "i.4YBNbl3IXVJQRM",
"kind": "song",
"isLibrary": true
},
"artistName": "Childish Gambino",
"trackNumber": 6,
"name": "Redbone",
"contentRating": "explicit"
}
},
{
"id": "i.mmpeOrZiLqoKOv",
"type": "library-songs",
"href": "/v1/me/library/songs/i.mmpeOrZiLqoKOv",
"attributes": {
"albumName": "Funk Wav Bounces Vol. 1",
"artwork": {
"width": 1200,
"height": 1200,
"url": "https://is5-ssl.mzstatic.com/image/thumb/Music127/v4/8b/37/23/8b372308-f764-d03a-5bda-a7a456292547/886446469607.jpg/{w}x{h}bb.jpg"
},
"durationInMillis": 272659,
"playParams": {
"id": "i.mmpeOrZiLqoKOv",
"kind": "song",
"isLibrary": true
},
"artistName": "Calvin Harris",
"trackNumber": 4,
"name": "Rollin (feat. Future & Khalid)",
"contentRating": "explicit"
}
},
{
"id": "i.JL1aVxNtzmYDJG",
"type": "library-songs",
"href": "/v1/me/library/songs/i.JL1aVxNtzmYDJG",
"attributes": {
"albumName": "The Weekend (Funk Wav Remix) - Single",
"artwork": {
"width": 1200,
"height": 1200,
"url": "https://is4-ssl.mzstatic.com/image/thumb/Music118/v4/aa/d5/e5/aad5e5e3-dff5-7d8f-5747-b695ad9f2299/886446852157.jpg/{w}x{h}bb.jpg"
},
"durationInMillis": 171806,
"playParams": {
"id": "i.JL1aVxNtzmYDJG",
"kind": "song",
"isLibrary": true
},
"artistName": "SZA & Calvin Harris",
"trackNumber": 1,
"name": "The Weekend (Funk Wav Remix)"
}
}
]
}
When I decode the data using:
let resource = try? JSONDecoder().decode([Resource].self, from: data!)
print(resource!)
I get a fatal error: Unexpectedly found nil while unwrapping an Optional value
Update 2:
I figured out the issue. The extra layer on top was screwing up the decoding. I added another level:
struct Welcome: Codable {
let data: [Datum]
}
struct Datum: Codable {
let id, type, href: String
let attributes: Attributes
}
struct Attributes: Codable {
let albumName: String
let artwork: Artwork
let durationInMillis: Int
let playParams: PlayParams
let artistName: String
let trackNumber: Int
let name: String
let contentRating: String?
}
struct Artwork: Codable {
let width, height: Int
let url: String
}
struct PlayParams: Codable {
let id, kind: String
let isLibrary: Bool
}
This just works for songs but I will try reimplementing for both songs and playlists.

How to parse this JSON and create struct for

I have a JSON:
[
{
"Men": {
"image": "/url.png",
"Jeans": [
{
"name": "asd",
"about": "sadvc",
"image": "/urls.sjd"
},
{
"name": "asd",
"about": "sadvc",
"image": "/urls.sjd"
},
{
"name": "asd",
"about": "sadvc",
"image": "/urls.sjd"
}
]
},
"Women": {
"image": "/url2.jpeg",
"All": {}
}
}
]
How to create the struct for "step by step" going into the tableview?
First View - Change sex - Women or men.
Second - Change type - jeans or other...
Thirst - collection view with jeans (name, about and price).
Now, i have struct
struct Clothe: Decodable {
let about: String
let name: String
let image: String
}
And func for downloading JSON
var clothes = [Clothe]()
public func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https...bla-bla/ULRhere.json")
let request = URLRequest(url: url!, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 120.0)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if error == nil {
do {
self.clothes = try JSONDecoder().decode([Clothe].self, from: data!)
print(self.clothes)
DispatchQueue.main.async {
completed()
}
} catch {
print("JSON Error")
}
}
}.resume()
}
let json = """{"Men": {"image": "/url.png","Jeans": [{"name": "asd","about": "sadvc","image": "/urls.sjd"},{"name": "asd","about": "sadvc","image": "/urls.sjd"},{"name": "asd","about": "sadvc","image": "/urls.sjd"}]},"Women": {"image": "/url2.jpeg","All": {}}}""".data(using: .utf8)!
struct Cloth: Decodable {
let Men : MenStruct?
let Women : WomanStruct?}
struct MenStruct: Decodable {
let image: String?
let Jeans: [JeansStruct]?}
struct JeansStruct: Decodable {
let name: String?
let about: String?
let image: String?}
struct WomanStruct: Decodable {
let image: String?}
func executeJson(){
do {
let cloth = try JSONDecoder().decode(Cloth.self, from: json)
print(cloth)
}catch {
print("JSON Error")
}}
executeJson()
Cloth(Men: Optional(__lldb_expr_88.MenStruct(image: Optional("/url.png"), Jeans: Optional([__lldb_expr_88.JeansStruct(name: Optional("asd"), about: Optional("sadvc"), image: Optional("/urls.sjd")), __lldb_expr_88.JeansStruct(name: Optional("asd"), about: Optional("sadvc"), image: Optional("/urls.sjd")), __lldb_expr_88.JeansStruct(name: Optional("asd"), about: Optional("sadvc"), image: Optional("/urls.sjd"))]))), Women: Optional(__lldb_expr_88.WomanStruct(image: Optional("/url2.jpeg"))))
This is not a good JSON design. I would suggest not using data values ("Men" and "Women" or "Jeans" vs another type of clothing) as keys to your dictionary.
Also, I'd suggest that the response from your web service return a dictionary with keys like a success value (a Boolean that indicates whether the result was successful or not) and a result key (for the contents of the response). This way, if there is an error, the basic structure of the response will be the same (but a successful response will include result key and a failure might include an error message or error code).
Anyway, I'd suggest something like:
{
"success": true,
"result": [{
"name": "Men",
"image": "men.png",
"productLine": [{
"name": "Jeans",
"image": "jeans.png",
"products": [{
"name": "Slim fit",
"about": "Slim Fit Jeans",
"image": "slim.png"
},
{
"name": "Bell Bottom",
"about": "Cool bell bottom jeans",
"image": "bellbottom.png"
},
{
"name": "Acid Wash",
"about": "Acid wash jeans",
"image": "acid.png"
}
]
}]
}, {
"name": "Women",
"image": "women.jpeg"
}]
}
Then you can set up logical model entities:
struct Product: Codable {
let about: String
let name: String
let image: String
}
struct ProductLine: Codable {
let name: String
let image: String
let products: [Product]?
}
struct CustomerCategory: Codable {
let name: String
let image: String
let productLine: [ProductLine]?
}
Then you'd process the response like so:
func processResponse(_ data: Data) {
struct ResponseObject: Codable {
let success: Bool
let errorCode: Int?
let result: [CustomerCategory]?
}
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: data)
guard responseObject.success, let customerCategories = responseObject.result else {
// handle server error here
return
}
print(customerCategories)
} catch {
print(error)
}
}
This allows you to add new customer categories (e.g. children) or product lines (e.g. things other than jeans) without affecting the basic interface with the server.
In your other question, you've changed the nature of the data being returned, but, again, I'd advise against putting data attributes in the keys of your dictionaries.

Resources