Trouble with JSON Parsing in Swift - arrays

Having a slight trouble in saving my decoded JSON data into the struct, not sure what I'm doing wrong here.
What I'm trying to do is call the sneaker database and retrieve the results and display them in a view, i can see the JSON call is working with the print but now stuck on how to get this in a way I can display it into a view.
JSON
{
"count": 1,
"results": [
{
"id": "029ddc1d-a64b-469b-b9df-bd21c84a608e",
"brand": "Jordan",
"colorway": "Deep Ocean/Sail-Cement Grey-Fire Red",
"gender": "men",
"name": "SE Sashiko",
"releaseDate": "2020-12-05",
"retailPrice": 190,
"shoe": "Jordan 4 Retro",
"styleId": "CW0898-400",
"title": "Jordan 4 Retro SE Sashiko",
"year": 2020,
"media": {
"imageUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370",
"smallImageUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370",
"thumbUrl": "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370"
}
}
]
}
Model:
struct APIResponse: Decodable {
enum CodingKeys: String, CodingKey {
case shoeResults = "results"
}
let shoeResults: [Shoe]
}
struct Shoe: Decodable {
public var id: String
public var brand: String
public var colorway: String
public var gender: String
public var name: String
public var releaseDate: String
public var retailPrice: Int
public var shoe: String
public var styleId: String
public var title: String
public var year: Int
public var media: mediaData
enum CodingKeys: String, CodingKey {
case id = "id"
case brand = "brand"
case colorway = "colorway"
case gender = "gender"
case name = "name"
case releaseDate = "releaseDate"
case retailPrice = "retailPrice"
case shoe = "shoe"
case styleId = "styleId"
case title = "title"
case year = "year"
case media = "media"
}
}
struct mediaData: Decodable {
var imageUrl: String
var smallImageUrl: String
var thumbUrl: String
}
Shoefetcher class:
public class ShoeFetcher: ObservableObject {
init(){
load()
}
func load() {
let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&styleId=cw0898-400")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
DispatchQueue.main.async {
print(decodedLists)
}
}else {
print("No Data")
}
}catch DecodingError.keyNotFound(let key, let context) {
Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
} catch let error as NSError {
NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
}
}.resume()
}
}
output of decodedlists:
APIResponse(shoeResults: [Sneakers.Shoe(id: "029ddc1d-a64b-469b-b9df-bd21c84a608e", brand: "Jordan", colorway: "Deep Ocean/Sail-Cement Grey-Fire Red", gender: "men", name: "SE Sashiko", releaseDate: "2020-12-05", retailPrice: 190, shoe: "Jordan 4 Retro", styleId: "CW0898-400", title: "Jordan 4 Retro SE Sashiko", year: 2020, media: SneakerNews.mediaData(imageUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370", smallImageUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370", thumbUrl: "https://images.stockx.com/images/Air-Jordan-4-Retro-SE-Deep-Ocean-Product.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1609439370"))])
Edit:
updated the shoefetcher class:
public class ShoeFetcher: ObservableObject {
#Published var shoeResults: [Shoe] = [] //added
init(){
load()
}
func load() {
let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&styleId=cw0898-400")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
DispatchQueue.main.async {
self.shoeResults = decodedLists.shoeResults //added
print(self.shoeResults)
}
}else {
print("No Data")
}
}catch DecodingError.keyNotFound(let key, let context) {
Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
} catch let error as NSError {
NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
}
}.resume()
}
}
added a ContentView to just see if I can display something off the JSON file.
struct ContentView: View {
#ObservedObject var fetcher = ShoeFetcher()
#State var Shoes: Shoe
var body: some View {
VStack {
Text("Shoes")
Text(Shoes.brand)
}
}
}
errors I'm getting is Missing argument for a parameter 'Shoes' in call in my SneakersApp.swift file
import SwiftUI
#main
struct SneakersApp: App {
var body: some Scene {
WindowGroup {
ContentView() //error here
}
}
}
I have a feeling I need to initialise the JSON variables but cant seem to workout where/how I do that

You've done all of the hard work of getting the data from the API and decoding it into structs.
To display it in a view, you could do something like this:
struct ContentView : View {
#StateObject var fetcher = ShoeFetcher()
var body: some View {
VStack {
if let response = fetcher.apiResponse {
ForEach(response.shoeResults, id: \.id) { shoe in
Text(shoe.name)
Text(shoe.brand)
Text(shoe.title)
Text("\(shoe.year)")
Text("\(shoe.retailPrice)")
}
}
}
}
}
Your current API call is only returning one Shoe in the array, so the ForEach call isn't doing much, but it would certainly be important if you had more results.

Related

Swift enum opaque return type

I've been experimenting with this code example from John Sundell and want to be able to get an enum value from the array.
protocol ItemVariant: Decodable {
var title: String { get }
var imageURL: URL { get }
}
struct Video: ItemVariant {
var title: String
var imageURL: URL
var url: URL
var duration: String
var resolution: String
}
struct Recipe: ItemVariant {
var title: String
var imageURL: URL
var text: String
var ingredients: [String]
}
enum Item {
case video(Video)
case recipe(Recipe)
}
extension Item: Decodable {
struct InvalidTypeError: Error {
var type: String
}
private enum CodingKeys: CodingKey {
case type
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "video":
self = .video(try Video(from: decoder))
case "recipe":
self = .recipe(try Recipe(from: decoder))
default:
throw InvalidTypeError(type: type)
}
}
}
struct ItemCollection: Decodable {
var items: [Item]
}
let json = """
{
"items": [
{
"type": "video",
"title": "Making perfect toast",
"imageURL": "https://image-cdn.com/toast.png",
"url": "https://videoservice.com/toast.mp4",
"duration": "00:12:09",
"resolution": "720p"
},
{
"type": "recipe",
"title": "Tasty burritos",
"imageURL": "https://image-cdn.com/burritos.png",
"text": "Here's how to make the best burritos...",
"ingredients": [
"Tortillas",
"Salsa",
]
}
]
}
"""
let result = try JSONDecoder().decode(ItemCollection.self, from: json.data(using: .utf8)!)
print(result.items)
I want to add the following:
extension Item {
var value: some ItemVariant {
switch self {
case .recipe(let recipe):
return recipe
case .video(let video):
return video
}
}
}
Allow for something like this:
print(result.items.first { $0.value is Video })
But I end up with this compiler error:
function declares an opaque return type, but the return statements in its body do not have matching underlying types
I'm not sure how I can resolve this error and find an elegant way to retrieve a enum value from an enum array. Appreciate any help.

Value of type xxx has no member xxx Error in SwiftuI?

I've been struggling with this for ages and I can't figure out why I can't pass this at all.
I have an array that look like this:
{
"status": "OK",
"tickets": {
"open_tickets": [
{
"uuid": "XXXXXXX",
"package": {
"title": "XXXX",
"buyer": {
"name": "XXX",
"family_name": null,
"email": "XXXXX",
"buyer_profile": {
"telephone": "5435345"
}
}
}
}
],
"closed_tickets": []
}
}
I'm trying access the nested array(s). specifically, open_tickets.
So, I've tried this:
I've placed this in at the top of my view between the struct and body:
#ObservedObject var fetcherL = FetcherL()
And this is now I try to read that data from a remote URL:
public class FetcherL: ObservableObject {
#Published var ticket = [Open_Tickets]()
init(){
load()
}
func load() {
let url = URL(string: "HTTPS://xxxxxxx")
var request = URLRequest(url: url!)
URLSession.shared.dataTask(with: request) {(data,response,error) in
do {
if let d = data {
let res = try JSONDecoder().decode(RootO.self, from: d)
DispatchQueue.main.async {
//self.ticket = res.tickets.open_tickets
self.ticket = res.tickets.open_tickets
}
}else {
print("No Data")
}
} catch {
print (error)
}
}.resume()
}
}
struct RootO: Codable {
let tickets: [Tickets]
enum CodingKeys: String, CodingKey {
case tickets = "tickets"
}
}
struct Tickets: Codable {
let open_tickets: [Open_Tickets]
enum CodingKeys: String, CodingKey {
case open_tickets = "open_tickets"
}
}
struct Open_Tickets: Codable{
let ticket_number: String
enum CodingKeys: String, CodingKey {
case ticket_number = "ticket_number"
}
}
But this code Is giving this error and stops me from compiling my app:
Value of type '[Tickets]' has no member 'open_tickets'
Can someone please advice on this?
The reason you are getting the error is in your Root0:
struct RootO: Codable {
let tickets: [Tickets]
enum CodingKeys: String, CodingKey {
case tickets = "tickets"
}
}
You are expecting your Root0 to have a field called tickets expecting an array of Tickets, but the tickets attribute of the response is a dictionary of [String: [Tickets]
Here is a working solution:
let json = """
{
"status": "OK",
"tickets": {
"open_tickets": [
{
"uuid": "XXXXXXX",
"package": {
"title": "XXXX",
"buyer": {
"name": "XXX",
"family_name": null,
"email": "XXXXX",
"buyer_profile": {
"telephone": "5435345"
}
}
}
}
],
"closed_tickets": []
}
}
""".data(using: .utf8)!
struct RootO: Codable {
let tickets: [String: [Ticket]]
var openTickets: [Ticket] {
if let data = tickets["open_tickets"] {
return data
} else {
return []
}
}
var closedTickets: [Ticket] {
if let data = tickets["closed_tickets"] {
return data
} else {
return []
}
}
}
struct Ticket: Codable {
let ticketNumber: String
enum CodingKeys: String, CodingKey {
case ticketNumber = "uuid"
}
}
let decoder = JSONDecoder()
let decoded = try! decoder.decode(RootO.self, from: json)
print("Open tickets = \(decoded.openTickets.count)")
print("Closed tickets = \(decoded.closedTickets.count)")
Some advice:
Use singular nouns for your struct Ticket not Tickets.
You do not need to provide the coding keys if the attribute name is identical
Change RootO structure and try it, "tickets" is object not an array
struct RootO: Codable {
let tickets: Tickets
enum CodingKeys: String, CodingKey {
case tickets = "tickets"
}
}

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
}

Accessing embedded JSON using decodable in Swift 4

I am trying to access a particular an embedded array of dictionaries to create my swift objects. I am unsure about how to access that array in the JSON dictionary.
Here is the definition of my Swift object = StarWarsPeople
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var weight: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Here is my APIClient class:
class StarWarsPeopleAPIClient
{
class func getStarWarsPeopleInformation (page: Int, completion:#escaping ([StarWarsPeople])-> ()) throws {
let starWarsPeopleURL = "https://swapi.co/api/people/?page=\(page)"
let convertedStarWarsPeopleURL = URL(string: starWarsPeopleURL)
guard let unwrappedConvertedStarWarsPeopleURL = convertedStarWarsPeopleURL else { print("unwrappedConvertedStarWarsPeopleURL did not unwrap"); return}
let request = URLRequest(url: unwrappedConvertedStarWarsPeopleURL)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode([StarWarsPeople].self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
task.resume()
}
}
Here is my Json, it is the results array that I would like to access, it is an array of dictionaries, which I need to iterate over to create my StarWarsPeople Object.
{
"count": 87,
"next": "url",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "url",
"films": [
"url",
"url",
"url",
"url",
"url"
],
"species": [
"url"
],
"vehicles": [
"url",
"url"
Please read the JSON. You are ignoring the enclosing object
struct Root: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results : [StarWarsPeople]
}
struct StarWarsPeople: Decodable {
private enum CodingKeys: String, CodingKey {
case name, height, mass
case hairColor = "hair_color", skinColor = "skin_color"
case eyeColor = "eye_color", birthYear = "birth_year", gender
}
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
}
...
let root = try JSONDecoder().decode(Root.self, from: unwrappedData)
let starWarsPeopleDataArray = root.results
...
Notes:
A struct is sufficient.
Map the snake_cased keys to camelCased properties.
In almost all cases the properties can be declared as constants (let).
Don't declare all properties schematically as optional. Declare only those as optional which corresponding key can be missing or the value can be null.
Simply define a wrapper struct that holds the results property from the JSON response.
struct ApiResponse: Decodable {
results: [StarWarsPeople]
}
and later use
let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: unwrappedData)
let starWarsPeopleDataArray = apiResponse.results
to parse it.
Results you are trying to fetch is actually present in results keys. Also we need to use properties same as parameter name( we can use CodingKeys enum too for overriding this).
So, first parse outer JSON , In new struct say StarWarsPeopleParent
class StarWarsPeopleParent: Decodable {
var count: Int?
var results: [StarWarsPeople]?
}
Update your StarWarsPeople struct's properties as:
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var mass: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Then parse it like:
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode(StarWarsPeopleParent.self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
Hope this is fine for you to understand.

How to parse array in Swift 3

I have some JSON but it's in a matrix and I do not know how to get the values separately. For example, get the last name.
This is the JSON structure:
{
"login": true,
"token": "m5CnQ",
"usuario": [
{
"names": "Sergio Alejandro",
"las_name": "Rosado",
}
]
}
This is my code:
do {
print("recibimos respuesta")
let responseString = String(data: data, encoding: .utf8)
print("respuesta : \(responseString)")
if let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: String] {
DispatchQueue.main.async {//proceso principal
var nombre = json["nombre"]//constante
print("respuestanom : \(nombre)")
}
}
} catch let parseError {//manejamos el error
print("error al parsear: \(parseError)")
let responseString = String(data: data, encoding: .utf8)
print("respuesta : \(responseString)")
}
}
task.resume()
have you tried usuario[0].las_name
If you are on Swift 4, use the new Decodable protocol:
First define your data model:
struct MyDataModel: Decodable {
struct User: Decodable {
var names: String
var lastName: String
private enum CodingKeys: String, CodingKey {
case names, lastName = "las_name"
}
}
var login: Bool
var token: String
var user: [User]
private enum CodingKeys: String, CodingKey {
case login, token, user = "usuario"
}
}
Then the decoding is really simple:
do {
let model = try JSONDecoder().decode(MyDataModel.self, from: data)
print(model)
} catch let parseError {
print(parseError)
}
i resolved my problem this way:
JSON:
{
"login": "TRUE",
"token": "eyJ0eXAiOiJKV12la70",
"usuario": [
{
"id": "36",
"id_empresa": "1",
"nombres": "Sergio Alejandro",
"apellido_paterno": "Rosado",
"apellido_materno": "Dzul",
"zona_horaria": "America/Merida",
"id_perfil": "1",
"correo": "russelalexis123#gma.com",
"username": "empleadodemo",
"passwd": "*A4B6157319038724E3560894F7F932C8886EBFCF",
"activo": "1",
"fh_captura": "2017-02-20 21:02:55",
"domicilio": "",
"cp": "",
"telefono": "",
"usuario": "1",
"no_licencia": null,
"fecha_expedicion": null,
"fecha_vigencia": null
}
]
}
code
struct MyDataModel: Decodable {
struct User: Decodable {
var id: String?
var id_empresa: String?
var nombres: String?
var apellido_paterno: String?
var apellido_materno: String?
var zona_horaria: String?
var id_perfil: String?
var correo: String?
var username: String?
var activo: String?
var fh_captura: String?
var domicilio: String?
var cp: String?
var telefono: String?
var usuario: String?
private enum CodingKeys: String, CodingKey {
case id="id", id_empresa="id_empresa", nombres="nombres", apellido_paterno="apellido_paterno", apellido_materno="apellido_materno", zona_horaria="zona_horaria", id_perfil="id_perfil", correo="correo", username="username", activo="activo", fh_captura = "fh_captura", domicilio="domicilio", cp="cp", telefono="telefono", usuario="usuario"
}
}
var login: String
var token: String
var user: [User]
private enum CodingKeys: String, CodingKey {
case login, token, user = "usuario"
}
}
print("envar solicitud")
let url = URL(string: "http://webservice")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type");
request.httpMethod = "POST"
let postString = "usuario=jose&password=1234"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {//si existe un error se termina la funcion
print("solicitud fallida \(error)")//manejamos el error
return //rompemos el bloque de codigo
}
do {
let model = try JSONDecoder().decode(MyDataModel.self, from: data)
print(model.user[0].correo)
} catch let parseError {
print(parseError)
}
}
task.resume()

Resources