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
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)")
}
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.
I have an json array with a list of item, that has different property.
{
"items": [
{
"id": "1",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "2",
"name": "name",
"propertyOfA": "12",
"productype": "a"
},
{
"id": "3",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "1",
"name": "name",
"propertyOfB": "1243",
"productype": "b"
},
{
"id": "1",
"name": "name",
"propertyOfC": "1243",
"propertyOfC2": "1243",
"productype": "c"
}
]
}
What i usually do is parse it into something like :
struct RequestData: Codable {
let items: [Item]
}
enum ProductType: String, Codable {
case a = "A"
case b = "B"
case c = "C"
}
struct Item: Codable {
let id, name: String
let propertyOfA: String?
let productype: String
let propertyOfB: String?
let propertyOfC: String?
let propertyOfC2: String?
}
But if this array keep growing, its gonna end up with a Item with a huge amount of optional property,
so i want each object have its own dedicated class
adding some kind of bridge inside codable parsings,
what i want to archive is something like :
struct RequestData: Codable {
let items: Items
}
struct items: Codable {
let arrayA: [A]
let arrayB = [B]
let arrayC = [C]
}
struct A: Codable {
let id: String,
let name: String,
let propertyOfA: String,
let producttype: Productype
}
struct B {
...
}
struct C {
...
}
can i do this with Codable ?
A reasonable solution is an enum with associated values because the type can be determined by the productype key. The init method first decodes the productype with a CodingKey then in a switch it decodes (from a singleValueContainer) and associates the proper type/value to the corresponding case.
enum ProductType: String, Codable {
case a, b, c
}
struct Root : Codable {
let items : [Product]
}
struct ProductA : Codable {
let id, name: String
let productype: ProductType
let propertyOfA : String
}
struct ProductB : Codable {
let id, name: String
let productype: ProductType
let propertyOfB : String
}
struct ProductC : Codable {
let id, name: String
let productype: ProductType
let propertyOfC, propertyOfC2 : String
}
enum Product : Codable {
case a(ProductA), b(ProductB), c(ProductC)
enum CodingKeys : String, CodingKey { case productype }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ProductType.self, forKey: .productype)
let singleContainer = try decoder.singleValueContainer()
switch type {
case .a : self = .a(try singleContainer.decode(ProductA.self))
case .b : self = .b(try singleContainer.decode(ProductB.self))
case .c : self = .c(try singleContainer.decode(ProductC.self))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .a(let productA): try container.encode(productA)
case .b(let productB): try container.encode(productB)
case .c(let productC): try container.encode(productC)
}
}
}
And decode
let jsonString = """
{
"items": [
{
"id": "1",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "2",
"name": "name",
"propertyOfA": "12",
"productype": "a"
},
{
"id": "3",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "1",
"name": "name",
"propertyOfB": "1243",
"productype": "b"
},
{
"id": "1",
"name": "name",
"propertyOfC": "1243",
"propertyOfC2": "1243",
"productype": "c"
}
]
}
"""
do {
let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
print(result)
} catch { print(error)}
To read the enum values use also a switch.
Consider this example JSON:
{
"sections": [{
"title": "Sign up",
"rows": [
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
},
{
"type": "textField",
"placeholder": "confirmPassword"
},
{
"type": "button",
"placeholder": "Register!"
}
]
}]
}
Let's say I wanted to parse the JSON above into the following models (I know it doesn't compile due to the Row protocol not corresponding to Decodable):
enum RowType: String, Codable {
case textField
case image
case button
}
protocol Row: Codable {
var type: RowType { get }
}
struct TextFieldRow: Row {
let type: RowType
let placeholder: String
let value: String
enum CodingKey: String {
case type
case placeholder
case value
}
}
struct ImageRow: Row {
let type: RowType
let imageURL: URL
enum CodingKey: String {
case type
case imageURL
}
}
struct ButtonRow: Row {
let type: RowType
let title: String
enum CodingKey: String {
case type
case title
}
}
struct Section: Codable {
let rows: [Row]
let title: String
enum CodingKey: String {
case rows
case title
}
}
struct Response: Codable {
let sections: [Section]
enum CodingKey: String {
case sections
}
}
// Parsing the response using the Foundation JSONDecoder
let data: Data // From network
let decoder = JSONDecoder()
do {
let response = try decoder.decode(Response.self, from: data)
} catch {
print("error: \(error)")
}
Is there a way to make the Swift code above Codable compliant?
I know you can manually solve this by first grabbing each Row's type string and then creating the right type of Row model as well as changing them from structs to classes and letting the Row protocol be a superclass instead. But is there a way that requires less manual labour?
Using an enum with associated value is the best option:
Consider this enum:
enum Row: Decodable {
case textField(TextFieldRow)
case image(ImageRow)
// and other cases
case unknown
enum CodingKeys: String, CodingKey {
case type
}
public init(from decoder: Decoder) throws {
do {
let selfContainer = try decoder.singleValueContainer()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
let type = try typeContainer.decode(String.self, forKey: .type)
switch type {
case "textField": self = .textField( try selfContainer.decode(TextFieldRow.self) )
case "Image": self = .image( try selfContainer.decode(ImageRow.self) )
// and other cases
default: self = .unknown
}
}
}
}
With these changes:
struct TextFieldRow: Decodable {
let placeholder: String?
let value: String?
}
struct ImageRow: Decodable {
let imageURL: URL
}
// and so on
Now this will decode like a charm:
// Minmal testing JSON
let json = """
[
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
}
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
print( try! decoder.decode([Row].self, from: json) )
You can now add any other case you need to the decoder to build your application builder app.