Home > Back-end >  No Data when Decoding Nested Array in Json by Swift
No Data when Decoding Nested Array in Json by Swift

Time:04-09

I want to decode this data and want to display fields separately in UI. Json data I am receiving from API

{
"IsSuccess": true,
"Message": "Data Returned",
"ResponseData": [
    {
        "PackageId": 1025,
        "PackageName": "17 OH Progesterone",
        "Price": 0.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "50",
                "SampleName": "Serum",
                "ColourCode": "#FFB500"
            }
        ]
    },
    {
        "PackageId": 1916,
        "PackageName": "24 hour Albumin creatinine ratio (ACR)",
        "Price": 120.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "66",
                "SampleName": "24 hrs Urine",
                "ColourCode": "#212DC1"
            }
        ]
    },
    {
        "PackageId": 1914,
        "PackageName": "24 Hour Microalbumin Creatinine Ratio",
        "Price": 110.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "66",
                "SampleName": "24 hrs Urine",
                "ColourCode": "#212DC1"
            }
        ]
    },
    {
        "PackageId": 1913,
        "PackageName": "24 Hours Protein Creatinine Ratio (PCR) ",
        "Price": 12.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "66",
                "SampleName": "24 hrs Urine",
                "ColourCode": "#212DC1"
            }
        ]
    },
    {
        "PackageId": 936,
        "PackageName": "24 Hours Urinary Phosphorous",
        "Price": 15.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "66",
                "SampleName": "24 hrs Urine",
                "ColourCode": "#212DC1"
            }
        ]
    },
    {
        "PackageId": 937,
        "PackageName": "24 Hours Urinary Potassium",
        "Price": 2.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [
            {
                "TestSampleTypeId": "66",
                "SampleName": "24 hrs Urine",
                "ColourCode": "#212DC1"
            }
        ]
    },
   ......
   ]}

Decoding Model for the above

import Foundation


          struct PriceList {
                           let Success: Bool
                           let message: String
                           let Response: [ResponseList]?
                           }

         extension PriceList:Codable
                    {
                   enum CodingKeys: String, CodingKey 
                          {
                          case Success = "IsSuccess"
                          case message = "Message"
                          case Response = "ResponseData"
                         }

        init(from decoder:Decoder) throws {
  
                  let container = try decoder.container(keyedBy:CodingKeys.self)
                  Success = try container.decode(Bool.self,forKey: .Success)    
                  message = try container.decode(String.self,forKey: .message)
                  Response = try container.decode([ResponseList].self,forKey: .Response)
                          }
                       }


                struct ResponseList 
                         {
                             let packageID: Int
                             let packageName: String
                             let price, discountedPrice: Double
                             let type: String
                             let testPackageGroupID: Int
                             let SampleType: [SampleTypeList]?
                         }
               extension ResponseList:Decodable
                         {
                      enum CodingKeys: String, CodingKey {
                             case packageID = "PackageId"
                             case packageName = "PackageName"
                             case price = "Price"
                             case discountedPrice = "DiscountedPrice"
                             case type = "Type"
                             case testPackageGroupID = "TestPackageGroupId"
                             case SampleType= "SampleTypeList"
                         }

              init(from decoder:Decoder) throws
                             {
  
                       let container = try decoder.container(keyedBy:CodingKeys.self)
                       packageID = try container.decode(String.self,forKey: .packageID)    
                       packageName= try container.decode(String.self,forKey: .packageName)
                       price= try container.decode(Double.self,forKey: .price)
                  discountedPrice= try container.decode(Double.self,forKey:.discountedPrice)
                     type= try container.decode(String.self,forKey: .type)
          testPackageGroupID = try container.decode(String.self,forKey:  .testPackageGroupID )
             SampleType= try container.decode([SampleTypeList].self,forKey: .SampleType) 
                                   }
                        }


               struct SampleTypeList        
                                 {
                          let testSampleTypeID, sampleName, colourCode: String
                                 }
               extension SampleTypeList:Codable {
                          enum SampleKeys: String, CodingKey {
                          case testSampleTypeID = "TestSampleTypeId"
                          case sampleName = "SampleName"
                          case colourCode = "ColourCode"
                                 }
               init(from decoder:Decoder) throws 
                         {
                          let container = try decoder.container(keyedBy:SampleKeys.self)
          testSampleTypeID = try container.decode(String.self,forKey: .testSampleTypeID )    
                      sampleName = try container.decode(String.self,forKey: .sampleName )
                      colourCode = try container.decode(String.self,forKey: .colourCode)
                         }
                       }

This is the code written in playground:

 var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "xx.xx.xxx.x"
urlComponents.path = "/api/test/home"
urlComponents.queryItems = [URLQueryItem(name: "pricelistGroupId",value: "12")]

let url = urlComponents.url

var request = URLRequest(url: url!)


request.addValue("application/json", forHTTPHeaderField: 
"Accept")
request.addValue("Basic \(authToken)", forHTTPHeaderField: 
 "Authorization")


let task = URLSession.shared.dataTask(with: request)
{
    (data, response, error) in
   
    if let error = error 
    {
    print("Error \(error)")
    return
    }
    if let response = response as? HTTPURLResponse {
    print("Response \(response.statusCode)")
   
    }
   if let data = data
 {

  let dataString = String(data:data, encoding: .utf8)
  print(dataString)
  let json = try? JSONDecoder().decode(PriceList.self,from:data)
 print(json)
}
}

print(dataString) is printing the data. However, no data for print(json) it is showing nil in playground.

ResponseList init is stuck at 259 times (right side playground tab showing all process) whereas SampleTypeList is stuck at 346 times.

If I remove ? (optional) from [ResponseList]? and [SampleTypeList]? it showing "Cannot get unkeyed decoding container -- found null value instead."

Please, ignore typo errors.

Program is stuck at where it is finding null for eg two instances mainly

SampleTypeList = null (occurs many times in JSON) testPackageGroupID = null

CodePudding user response:

try this, works for me:

extension ResponseList: Codable {  // <-- here not Decodable
    
    enum CodingKeys: String, CodingKey {
        case packageID = "PackageId"
        case packageName = "PackageName"
        case price = "Price"
        case discountedPrice = "DiscountedPrice"
        case type = "Type"
        case testPackageGroupID = "TestPackageGroupId"
        case SampleType = "SampleTypeList"
    }
    
    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy:CodingKeys.self)
        packageID = try container.decode(Int.self,forKey: .packageID)  // <-- here Int
        packageName = try container.decode(String.self,forKey: .packageName)
        price = try container.decode(Double.self,forKey: .price)
        discountedPrice = try container.decode(Double.self,forKey:.discountedPrice)
        type = try container.decode(String.self,forKey: .type)
        testPackageGroupID = try container.decode(Int.self,forKey: .testPackageGroupID ) // <-- here Int
        SampleType = try container.decode([SampleTypeList].self,forKey: .SampleType)
    }
}

EDIT:

Here is the code I used to show decoding the given json data works with my answer.

import SwiftUI
import Foundation

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct PriceList {
    let Success: Bool
    let message: String
    let Response: [ResponseList]?
}

extension PriceList: Codable {
    
    enum CodingKeys: String, CodingKey {
        case Success = "IsSuccess"
        case message = "Message"
        case Response = "ResponseData"
    }
    
    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy:CodingKeys.self)
        Success = try container.decode(Bool.self,forKey: .Success)
        message = try container.decode(String.self,forKey: .message)
        Response = try container.decode([ResponseList].self,forKey: .Response)
    }
}

struct ResponseList {
    let packageID: Int
    let packageName: String
    let price, discountedPrice: Double
    let type: String
    let testPackageGroupID: Int
    let SampleType: [SampleTypeList]?
}

extension ResponseList: Codable {  // <-- here not Decodable
    
    enum CodingKeys: String, CodingKey {
        case packageID = "PackageId"
        case packageName = "PackageName"
        case price = "Price"
        case discountedPrice = "DiscountedPrice"
        case type = "Type"
        case testPackageGroupID = "TestPackageGroupId"
        case SampleType = "SampleTypeList"
    }
    
    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy:CodingKeys.self)
        packageID = try container.decode(Int.self,forKey: .packageID)  // <-- here Int
        packageName = try container.decode(String.self,forKey: .packageName)
        price = try container.decode(Double.self,forKey: .price)
        discountedPrice = try container.decode(Double.self,forKey:.discountedPrice)
        type = try container.decode(String.self,forKey: .type)
        testPackageGroupID = try container.decode(Int.self,forKey: .testPackageGroupID ) // <-- here Int
        SampleType = try container.decode([SampleTypeList].self,forKey: .SampleType)
    }
}

struct SampleTypeList {
    let testSampleTypeID, sampleName, colourCode: String
}

extension SampleTypeList:Codable {
    
    enum SampleKeys: String, CodingKey {
        case testSampleTypeID = "TestSampleTypeId"
        case sampleName = "SampleName"
        case colourCode = "ColourCode"
    }
    
    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy:SampleKeys.self)
        testSampleTypeID = try container.decode(String.self,forKey: .testSampleTypeID )
        sampleName = try container.decode(String.self,forKey: .sampleName )
        colourCode = try container.decode(String.self,forKey: .colourCode)
    }
}

struct ContentView: View {
    
    @State var priceList: PriceList?
    
    var body: some View {
        Text(priceList?.message ?? "no data")
            .onAppear {
                let jsonString  = """
                {
                "IsSuccess": true,
                "Message": "Data Returned",
                "ResponseData": [
                    {
                        "PackageId": 1025,
                        "PackageName": "17 OH Progesterone",
                        "Price": 0.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "50",
                                "SampleName": "Serum",
                                "ColourCode": "#FFB500"
                            }
                        ]
                    },
                    {
                        "PackageId": 1916,
                        "PackageName": "24 hour Albumin creatinine ratio (ACR)",
                        "Price": 120.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "66",
                                "SampleName": "24 hrs Urine",
                                "ColourCode": "#212DC1"
                            }
                        ]
                    },
                    {
                        "PackageId": 1914,
                        "PackageName": "24 Hour Microalbumin Creatinine Ratio",
                        "Price": 110.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "66",
                                "SampleName": "24 hrs Urine",
                                "ColourCode": "#212DC1"
                            }
                        ]
                    },
                    {
                        "PackageId": 1913,
                        "PackageName": "24 Hours Protein Creatinine Ratio (PCR) ",
                        "Price": 12.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "66",
                                "SampleName": "24 hrs Urine",
                                "ColourCode": "#212DC1"
                            }
                        ]
                    },
                    {
                        "PackageId": 936,
                        "PackageName": "24 Hours Urinary Phosphorous",
                        "Price": 15.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "66",
                                "SampleName": "24 hrs Urine",
                                "ColourCode": "#212DC1"
                            }
                        ]
                    },
                    {
                        "PackageId": 937,
                        "PackageName": "24 Hours Urinary Potassium",
                        "Price": 2.00,
                        "DiscountedPrice": 1.0,
                        "Type": "Test",
                        "TestPackageGroupId": 3,
                        "SampleTypeList": [
                            {
                                "TestSampleTypeId": "66",
                                "SampleName": "24 hrs Urine",
                                "ColourCode": "#212DC1"
                            }
                        ]
                    }
                   ]
                }
                """
                let data = jsonString.data(using: .utf8)
                priceList = try? JSONDecoder().decode(PriceList.self, from: data!)
                print("\n--> priceList: \(priceList) \n")
            }
    }
}

EDIT-2:

If you can have this in your json data:

"TestPackageGroupId": null,
"SampleTypeList": null
                    

then try this approach to decode your json data:

struct ResponseList {
    let packageID: Int
    let packageName: String
    let price, discountedPrice: Double
    let type: String
    let testPackageGroupID: Int? // <--- here optional
    let SampleType: [SampleTypeList]?  // <--- here optional
}

extension ResponseList: Codable {  // <-- here not Decodable
    
    enum CodingKeys: String, CodingKey {
        case packageID = "PackageId"
        case packageName = "PackageName"
        case price = "Price"
        case discountedPrice = "DiscountedPrice"
        case type = "Type"
        case testPackageGroupID = "TestPackageGroupId"
        case SampleType = "SampleTypeList"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy:CodingKeys.self)
        packageID = try container.decode(Int.self,forKey: .packageID)  // <-- here Int
        packageName = try container.decode(String.self,forKey: .packageName)
        price = try container.decode(Double.self,forKey: .price)
        discountedPrice = try container.decode(Double.self,forKey:.discountedPrice)
        type = try container.decode(String.self,forKey: .type)
        // --- here
        testPackageGroupID = try container.decodeIfPresent(Int.self,forKey: .testPackageGroupID)
        SampleType = try container.decodeIfPresent([SampleTypeList].self,forKey: .SampleType)
    }

}

Likewise for any other elements that can have null.

CodePudding user response:

This code is enough:

struct PriceList: Decodable {
    let success: Bool
    let message: String
    let response: [ResponseList]

    enum CodingKeys: String, CodingKey {
        case success = "IsSuccess"
        case message = "Message"
        case response = "ResponseData"
    }
}


struct ResponseList: Decodable {
    let packageID: Int
    let packageName: String
    let price, discountedPrice: Double
    let type: String
    let testPackageGroupID: Int?
    let sampleType: [SampleTypeList]?

    enum CodingKeys: String, CodingKey {
        case packageID = "PackageId"
        case packageName = "PackageName"
        case price = "Price"
        case discountedPrice = "DiscountedPrice"
        case type = "Type"
        case testPackageGroupID = "TestPackageGroupId"
        case sampleType = "SampleTypeList"
    }
}


struct SampleTypeList: Decodable {
    let testSampleTypeID: String
    let sampleName: String
    let colourCode: String
    enum CodingKeys: String, CodingKey {
        case testSampleTypeID = "TestSampleTypeId"
        case sampleName = "SampleName"
        case colourCode = "ColourCode"
    }
}

What I did fix, what I didn't like from your sample code:

  • Please name your variables starting with a lowercase, it's convention, and is easier to read (if everyone follow the same convention/standards).
  • Make your code compilable for us. It's not that hard, but if anyone would want to help you, it be much easier for us to just copy/paste your code and test it and not fix everything in it. You'll have better chances to get an answer. There is a Cannot assign value of type 'String' to type 'Int' because packageID is set as an Int and trying to be decoded as a String, there are missing spaces: variable= instead of variable =, etc. It's annoying to have us to fix that to be able to work.
  • You printed the JSON, that's a good point, to test it, there is no need anymore of the Web API Call, see following sample
  • You said that some values can be null, so please provide a JSON with these sample, cut if needed, see the JSON I used as sample, I checked on a JSON online validator that is was valid, and that's all. Since you that that 2 values could be null, I used all possibilities: one with none null, one with one null, one with the other null, and one with both null. Then, for each of these, I put the values as optional.
  • I removed all the init(from decoder:) as they are useless. If you decode each value with let container = try decoder.container(keyedBy:CodingKeys.self); variable = try container.decode(VariableType.self,forKey: .correspondingCodingKeyInTheEnumCase), that code is already done internally by Apple when compiling.

With sample test:

let jsonStr = """
{
"IsSuccess": true,
"Message": "Data Returned",
"ResponseData": [{
        "PackageId": 1025,
        "PackageName": "17 OH Progesterone",
        "Price": 0.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": [{
            "TestSampleTypeId": "50",
            "SampleName": "Serum",
            "ColourCode": "#FFB500"
        }]
    },
    {
        "PackageId": 1916,
        "PackageName": "24 hour Albumin creatinine ratio (ACR)",
        "Price": 120.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": 3,
        "SampleTypeList": null
    },
    {
        "PackageId": 1914,
        "PackageName": "24 Hour Microalbumin Creatinine Ratio",
        "Price": 110.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": null,
        "SampleTypeList": [{
            "TestSampleTypeId": "66",
            "SampleName": "24 hrs Urine",
            "ColourCode": "#212DC1"
        }]
    },
    {
        "PackageId": 1913,
        "PackageName": "24 Hours Protein Creatinine Ratio (PCR) ",
        "Price": 12.00,
        "DiscountedPrice": 1.0,
        "Type": "Test",
        "TestPackageGroupId": null,
        "SampleTypeList": null
    }
]
}
"""

do {
    let priceList = try JSONDecoder().decode(PriceList.self, from: Data(jsonStr.utf8))
    print(priceList)
} catch {
    print("Error while decoding: \(error)")
}
  • Related