0

I am trying to decode a JSON object from a remote API, Xcode doesn't raise any flags but the screen remains blank, I couldn't pinpoint where the error is coming from but if I have to take I guess, I think it's something to do with my parsing. What's going wrong here? everything seems at its place

This is my ContentView.swift

import SwiftUI

struct ContentView: View {

@State var results = UserProducts(status: Bool(), userProducts: [UserProduct]())

var body: some View {
    ScrollView(.horizontal, showsIndicators: false) {
        HStack(spacing: nil) {
            ForEach(0..<results.userProducts!.count) {
                res in
                VStack(){Text(verbatim: String(format: String(),((results.userProducts![res].id ?? "NA"))))}
            }.onAppear(perform: loadShelf)
        }         
    }
    Spacer()
}).background(Color(red: 250 / 255, green: 248 / 255, blue: 244 / 255))
}

func loadShelf(){
    
    guard let apiBaseURL = URL(string: "...") else {
        print("Base URL is invalid.")
        return
    }
    
    let request = URLRequest(url: apiBaseURL)
    
    URLSession.shared.dataTask(with: request) { data, response, error in
        DispatchQueue.main.async {
            if let data = data {
                do{
                    let decoded = try JSONDecoder().decode(UserProducts.self, from: data)
                    self.results = decoded
                    print("decoded: \(decoded)")
                    //prints: UserProducts(status: nil, userProducts: nil, recommendedProducts: nil)
                }
                catch{
                    print("Fetching data failed: \(error)")
                }
            }
        }
        }.resume()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This is my Structs:

import Foundation

// MARK: - UserProducts
struct UserProducts: Codable {
    let status: Bool?
    let userProducts: [UserProduct]?

    enum CodingKeys: String, CodingKey {
        case status
        case userProducts = "user_products"
    }
}

// MARK: - UserProduct
struct UserProduct: Codable {
    let id, userID, productID: Int?
    let routineTime, createdAt, updatedAt: String?
    let archived, volume: Int?
    let deletedAt, addedBy, weeklyRoutineOne, weeklyRoutineTwo: String?
    let product: Product?
    let user: User?
    let phaseOut, refill, empty, recommended: Int?

    enum CodingKeys: String, CodingKey {
        case id
        case userID = "user_id"
        case productID = "product_id"
        case routineTime = "routine_time"
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case archived, volume
        case deletedAt = "deleted_at"
        case addedBy = "added_by"
        case weeklyRoutineOne = "weekly_routine_one"
        case weeklyRoutineTwo = "weekly_routine_two"
        case product, user
        case phaseOut = "phase_out"
        case refill, empty, recommended
    }
}

// MARK: - Product
struct Product: Codable {
    let productName, productDescription, productIngredients: String?
    let productPrice, volume: Int?
    let image, interference, activeIngredients, howToUse: String?
    let brandID, productTypeID: Int?
    let brand, type: Brand?
    let rating: JSONNull?

    enum CodingKeys: String, CodingKey {
        case productName = "product_name"
        case productDescription = "product_description"
        case productIngredients = "product_ingredients"
        case productPrice = "product_price"
        case volume, image, interference
        case activeIngredients = "active_ingredients"
        case howToUse = "how_to_use"
        case brandID = "brand_id"
        case productTypeID = "product_type_id"
        case brand, type, rating
    }
}

// MARK: - Brand
struct Brand: Codable {
    let id: Int?
    let name, createdAt, updatedAt, commission: String?
    let category: Int?

    enum CodingKeys: String, CodingKey {
        case id, name
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case commission, category
    }
}

// MARK: - User
struct User: Codable {
    let name, email: String?
    let image1, deviceToken: JSONNull?
    let account, followup: Bool?

    enum CodingKeys: String, CodingKey {
        case name, email, image1
        case deviceToken = "device_token"
        case account, followup
    }
}

// MARK: - Encode/decode helpers

class JSONNull: Codable, Hashable {

    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }

    public var hashValue: Int {
        return 0
    }

    public func hash(into hasher: inout Hasher) {
        // No-op
    }

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

Now this is the JSON model and how it's supposed to look:

{
  "status": true,
  "user_products": [
    {
      "id": 1,
      "user_id": 1,
      "product_id": 1,
      "routine_time": "",
      "created_at": "",
      "updated_at": "",
      "archived": 0,
      "volume": 1,
      "deleted_at": "",
      "added_by": "",
      "weekly_routine_one": "",
      "weekly_routine_two": "",
      "product": {
        "product_name": "",
        "product_description": "",
        "product_ingredients": "",
        "product_price": 1,
        "volume": 1,
        "image": "",
        "interference": "",
        "active_ingredients": "",
        "how_to_use": "",
        "brand_id": 1,
        "product_type_id": 1,
        "brand": {
          "id": 1,
          "name": "",
          "created_at": "",
          "updated_at": "",
          "commission": ""
        },
        "type": {
          "id": 1,
          "name": "",
          "created_at": "",
          "updated_at": "",
          "category": 1
        },
        "rating": null
      },
      "user": {
        "name": "",
        "email": "",
        "image1": null,
        "device_token": null,
        "account": false,
        "followup": false
      },
      "phase_out": 0,
      "refill": 0,
      "empty": 0,
      "recommended": 0
    }
  ]
}
11
  • 3
    I think it's something to do with my parsing + try? => Don't use try?, do a property do/try/catch, you'll see... Commented Sep 14, 2021 at 13:37
  • Then, the answer is pretty straighforward if we read the error message, or analyse your Codable struct vs the JSON you show: .decode([UserProducts].self, => .decode(UserProducts.self,... Commented Sep 14, 2021 at 13:38
  • @Larme it is still a blank screen, nothing is showing Commented Sep 14, 2021 at 13:42
  • Check first if self.results = response.first is really called, that's checking your parsing. If yes, then your issue is not in your parsing anymore, but in the rendering part... Commented Sep 14, 2021 at 13:46
  • 1
    Why are all your Codable struct properties optional? Could you print String(data: data, encoding: .utf8) to be sure about the JSON you are receiving vs the JSON you pasted which I guess you're supposed (but maybe not the real one) to receive... Commented Sep 14, 2021 at 17:32

1 Answer 1

2

You actually have two problems here.

1st, this is where your nils are coming from:

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
       try container.encodeNil()
    }

If you comment it out or make it do what you actually want it to do instead of what it's currently doing (making everything nil)... that'll bring us to problem #2

You'll have your data, but because it's all optionals, there's some unwrapping that needs to be done before it'll make much sense.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.