How to parse this JSON and create struct for - ios

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.

Related

How to create a struct model from a complex JSON response

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
}

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.

Swift decoding JSON which has two possible types for the same field [duplicate]

This question already has answers here:
How to deal with completely dynamic JSON responses
(3 answers)
Closed 2 years ago.
I am dealing with this JSON using Alamofire and Codable:
[
{
"pID": "37229890-dcd8-36c4-bb63-e7b174aafeb7",
"type": "FIRST",
"content": {
"id": "ff64",
"ret": {
"name": "A",
"logoUrl": "hpng"
},
"amo": {
"value": 120.00,
"currency": "EUR"
},
"s": {
"value": 1.20,
"currency": "EUR"
},
"datetime": "",
"p": [
{
"ti": "",
"pr": {
"value": 120.00,
"currency": "EUR"
},
"pic": "string"
}
]
}
},
{
"pID": "37229890-dcd8-36c4-bb63-e7b174aafeb7",
"type": "RATE",
"content": "Rate this app"
}
]
As you can see, te value of the type "content" can be a simple String or a Struct.
I have tried a custom decoder and having a top struct but I am not able to achieve a solution for this problem.
What is the best way to solve this problem?
Is this what you are expecting?
let json = """
[
{
"type": "type1",
"content": {
"id": "ff64",
"title": "a title"
}
},
{
"type": "type2",
"content": "Rate this app"
}
]
"""
struct Type1: Decodable {
let id: String
let title: String
}
typealias Type2 = String
enum Content: Decodable {
case type1(Type1)
case type2(Type2)
enum ContentType: String, Decodable {
case type1
case type2
}
enum Keys: String, CodingKey {
case type
case content
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let type = try container.decode(ContentType.self, forKey: .type)
switch type {
case .type1:
let content = try container.decode(Type1.self, forKey: .content)
self = .type1(content)
case .type2:
let content = try container.decode(Type2.self, forKey: .content)
self = .type2(content)
}
}
}
let result = try JSONDecoder().decode([Content].self, from: json.data(using: .utf8)!)
You can apply AnyCodable custom decoder approach that can decode your needed types e.g.:
struct AnyCodable : Codable {
let value: Any
func encode(to encoder: Encoder) throws {
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let str = try? container.decode(String.self) {
value = str
} else if let content = try? container.decode(Content.self) {
value = content
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value cannot be decoded!")
}
}
}
struct Content : Codable {
let id: String
}
struct Item : Codable {
let content: AnyCodable
}
How to use:
do {
let items = try JSONDecoder().decode([Item].self, from: json.data(using: .utf8)!);
for item in items {
if let content = item.content.value as? String {
print(content)
}
else if let content = item.content.value as? Content {
print(content.id)
}
}
}
catch {
print(error)
}

Swift 5 + Alamofire 5.*: Decode data with same root object at the top

My problem is that I have JSON object got from the server like this one:
{
"data": [
{
"id": 1,
"name": "at",
"amount": 446,
"createdAt": "25/04/2020",
"updatedAt": "25/04/2020"
},
{
"id": 2,
"name": "iste",
"amount": 872,
"createdAt": "25/04/2020",
"updatedAt": "25/04/2020"
}
]
}
And I have Codable struct that decodes this object:
struct Expense: Codable, Identifiable {
var id: Int
var name: String
var amount: String
var createdAt: String
var updatedAt: String
}
Also, I have class with a static method that will do the AF request, also I'm using FuturePromise library for hendling completionof the request:
struct RequestAPI {
#discardableResult
static func callAndDecode<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder()) -> Future<T> {
return Future(operation: { completion in
AF.request(route).responseDecodable(decoder: decoder, completionHandler: { (response: DataResponse<T, AFError>) in
switch response.result {
case .success(let value):
completion(.success(value))
case .failure(let error):
print(error.localizedDescription)
completion(.failure(error))
}
})
})
}
}
Problem is that I have a root "data" parameter that sometimes is present and sometimes not.
I know that there is a solution that I can create a Result codable Model that will be the parent of the Expense Model, but that does not approach that I want, because what will happen if I will have 20 different models I'll have to create 20 deferent root Models?
Yes, I can do it with CodingKeys but that is a little bit hacky and too much of code for this simple task.
So the best approach is to add something like this:
struct ExpensesList: Codable {
var data: [Expense]
}
But for me, it is a problem that I will always have 'data' root, so then for any model I will have some 'List' model.
Is there a better approach that is not hacky or this is the only one.
Maybe to send a child model to one data model, but how to recognize it in views,...?
Thank you in advance.
If I understand you correctly, you can make your Root structure generic, and should also make the data property optional.
struct Foo: Decodable {
let foo: Int
}
struct Bar: Decodable {
let bar: String
}
struct Root<T: Decodable>: Decodable {
let data: [T]?
}
typealias FooList = Root<Foo>
typealias BarList = Root<Bar>
let fooData = """
{ "data": [ { "foo": 42 } ] }
""".data(using: .utf8)!
let barData = """
{ "data": [ { "bar": "baz" } ] }
""".data(using: .utf8)!
do {
let foos = try JSONDecoder().decode(FooList.self, from: fooData)
let bars = try JSONDecoder().decode(BarList.self, from: barData)
} catch {
print(error)
}

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

Resources