enum decoder json data swift - swift

I try to decode json from api, for that i use this website : https://app.quicktype.io/
this is my json from api :
{
"id":"7d37091c-f214-491a-a50c-d289f0f7e255",
"uuid":"7d37091c-f214-491a-a50c-d289f0f7e255",
"brand":"Nike",
"breadcrumbs":[
],
"browseVerticals":[
"sneakers"
],
"category":"Nike Dunk",
"charityCondition":0,
"childId":null,
"colorway":"White/Black-Total Orange",
"condition":"New",
"countryOfManufacture":"VN",
"dataType":"product",
"description":"",
"hidden":false,
"listingType":"standard",
"minimumBid":25,
"gender":"men",
"doppelgangers":[
],
"media":{
"imageUrl":"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
"smallImageUrl":"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
"thumbUrl":"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
"gallery":[
],
"hidden":false
},
"name":"Black White (2021)",
"productCategory":"sneakers",
"releaseDate":"",
"releaseTime":0,
"belowRetail":false,
"retailPrice":110,
"shoe":"Nike Dunk High",
"shortDescription":"Nike-Dunk-Low-Black-White-2021",
"styleId":"DD1399-105",
"tickerSymbol":"NK-NIDLFDXWB",
"title":"Nike Dunk High Black White (2021)",
"traits":[
{
"name":"Style",
"value":"DD1399-105",
"filterable":true,
"visible":true,
"highlight":true
},
{
"name":"Colorway",
"value":"White/Black-Total Orange",
"filterable":true,
"visible":true,
"highlight":true
},
{
"name":"Retail Price",
"value":110,
"filterable":true,
"visible":true,
"highlight":false
},
{
"name":"Featured",
"value":false,
"filterable":false,
"visible":false,
"highlight":false
}
],
"type":0,
"urlKey":"nike-dunk-high-black-white-total-orange-2021",
"year":0,
"shoeSize":null,
"market":{
"productId":0,
"skuUuid":"",
"productUuid":"7d37091c-f214-491a-a50c-d289f0f7e255",
"lowestAsk":173,
"lowestAskSize":"15",
"parentLowestAsk":0,
"numberOfAsks":196,
"hasAsks":1,
"salesThisPeriod":69,
"salesLastPeriod":0,
"highestBid":220,
"highestBidSize":"9.5",
"numberOfBids":230,
"hasBids":1,
"annualHigh":297,
"annualLow":67,
"deadstockRangeLow":238,
"deadstockRangeHigh":274,
"volatility":0.070391,
"deadstockSold":5891,
"pricePremium":1.627,
"averageDeadstockPrice":175,
"lastSale":256,
"lastSaleSize":"10.5",
"salesLast72Hours":69,
"changeValue":51,
"changePercentage":0.249544,
"absChangePercentage":0.249544,
"totalDollars":1029494,
"lastLowestAskTime":1639141012,
"lastHighestBidTime":1639160362,
"lastSaleDate":"2021-12-11T00:55:16+00:00",
"createdAt":"2021-06-29T04:46:03+00:00",
"updatedAt":1639184839,
"deadstockSoldRank":37,
"pricePremiumRank":1,
"averageDeadstockPriceRank":42,
"featured":0
},
"_tags":[
"sneakers",
"nike",
"nike",
"dunk",
"style_id|dd1399-105",
"retail_price|110",
"colorway|white/black-total orange"
],
"lock_selling":false,
"selling_countries":[
"AD",
"AE",
],
"buying_countries":[
"AD",
"AE",
"ES",
"US",
"XK",
"IC",
"IN"
],
"objectID":"7d37091c-f214-491a-a50c-d289f0f7e255"
}
this is my Preview Provider :
class DeveloperPreview {
static let instance = DeveloperPreview()
init() { }
let sneaker = Sneakers(
id:"7d37091c-f214-491a-a50c-d289f0f7e255",
uuid:"7d37091c-f214-491a-a50c-d289f0f7e255",
brand:"Nike",
breadcrumbs:[
],
browseVerticals:[
"sneakers"
],
category:"Nike Dunk",
charityCondition:0,
childID: JSONNull(),
colorway:"White/Black-Total Orange",
condition:"New",
countryOfManufacture:"VN",
dataType:"product",
welcomeDescription: "welcom",
hidden:false,
listingType:"standard",
minimumBid:25,
gender:"men",
doppelgangers:[
],
media: Media(
imageURL:"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=700&h=500&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
smallImageURL:"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=300&h=214&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
thumbURL:"https://images.stockx.com/images/Nike-Dunk-Low-Black-White-2021.jpg?fit=fill&bg=FFFFFF&w=140&h=100&auto=format,compress&trim=color&q=90&dpr=2&updated_at=1624941957",
gallery:[
],
hidden:false
),
name:"Black White (2021)",
productCategory:"sneakers",
releaseDate:"",
releaseTime:0,
belowRetail:false,
retailPrice:110,
shoe:"Nike Dunk High",
shortDescription:"Nike-Dunk-Low-Black-White-2021",
styleID:"DD1399-105",
tickerSymbol:"NK-NIDLFDXWB",
title:"Nike Dunk High Black White (2021)",
traits: [traits],
type:0,
urlKey:"nike-dunk-high-black-white-total-orange-2021",
year:0,
shoeSize: JSONNull(),
market: Market(
productID:0,
skuUUID:"",
productUUID:"7d37091c-f214-491a-a50c-d289f0f7e255",
lowestAsk:173,
lowestAskSize:"15",
parentLowestAsk:0,
numberOfAsks:196,
hasAsks:1,
salesThisPeriod:69,
salesLastPeriod:0,
highestBid:220,
highestBidSize:"9.5",
numberOfBids:230,
hasBids:1,
annualHigh:297,
annualLow:67,
deadstockRangeLow:238,
deadstockRangeHigh:274,
volatility:0.070391,
deadstockSold:5891,
pricePremium:1.627,
averageDeadstockPrice:175,
lastSale:256,
lastSaleSize:"10.5",
salesLast72Hours:69,
changeValue:51,
changePercentage:0.249544,
absChangePercentage:0.249544,
totalDollars:1029494,
lastLowestAskTime:1639141012,
lastHighestBidTime:1639160362,
lastSaleDate:"2021-12-11T00:55:16+00:00",
createdAt:"2021-06-29T04:46:03+00:00",
updatedAt:1639184839,
deadstockSoldRank:37,
pricePremiumRank:1,
averageDeadstockPriceRank:42,
featured:0
),
tags:[
"sneakers",
"nike",
"nike",
"dunk",
"style_id|dd1399-105",
"retail_price|110",
"colorway|white/black-total orange"
],
lockSelling:false,
sellingCountries:[
"AD",
"AE",
"US",
"IN"
],
buyingCountries:[
"AD",
"AE",
"IN"
],
objectID:"7d37091c-f214-491a-a50c-d289f0f7e255"
)
let traits = Trait(
name:"Style",
value: .bool(true),
filterable:true,
visible:true,
highlight:true
)
and this is my decoder :
struct Trait: Codable {
let name: String
let value: Value
let filterable, visible, highlight: Bool
}
enum Value: Codable {
case bool(Bool)
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Value.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Value"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
My error come from the preview provider :
"Cannot use instance member 'traits' within property initializer; property initializers run before 'self' is available"
enter image description here

The error is exactly pointed out by compiler.
Converting from API response, this should work as expected on error snippet.
traits: [
Trait(
name:"Style",
value: .string("DD1399-105"),
filterable:true,
visible:true,
highlight:true
),
Trait(
name:"Colorway",
value: .string("White/Black-Total Orange"),
filterable:true,
visible:true,
highlight:true
),
Trait(
name:"Retail Price",
value: .integer(110),
filterable:true,
visible:true,
highlight:false
),
Trait(
name:"Featured",
value: .bool(false),
filterable:false,
visible:false,
highlight:false
)
],

You cannot initialize properties which depend on each other on the top level of a class, you have to create the trait inside let sneaker = Sneakers( ...
Replace
traits: [traits],
with
traits: [Trait(
name:"Style",
value: .bool(true),
filterable:true,
visible:true,
highlight:true)],

Related

Swift json decoder with mixed array

Looking for help to decode irregular (for a lack of a better word) json. As an example:
[
{
"texts":
[
{
"value": "value 1"
}
],
"commentType": "someComment"
},
{
"texts":
[
{
"value": "value 2"
}
],
"commentType": "someComment2"
},
{
"texts":
[
{
"evidences":
[
{
"evidenceCode": "code 1",
},
{
"evidenceCode": "code 2",
},
{
"evidenceCode": "code 3",
},
{
"evidenceCode": "code 4",
}
],
"value": "value 3"
}
],
"commentType": "someComment3"
}
]
I can decode comment and the first two "texts":
enum CodingKeys: String, CodingKey {
case texts
case commentType
}
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decode(String.self, forKey: .commentType)
if let texts = try container.decodeIfPresent([[String: String]].self, forKey: .texts) {
for text in texts {
if let value = text["value"] {
// add to model object
}
}
}
} catch {
print(error)
}
But I get an error for the third "texts" block:
"Expected to decode String but found an array instead."
Which I understand, since now instead of an array of [String:String], it is a mixed array of [String:[String:String] and [String:String].
How do I decode value3 from that third block?
Actually this is not complicated at all because the content of texts is the same if we treat evidences as an optional array. The below models will decode the json correctly without any custom code.
struct Result: Decodable {
var texts: [TextData]
var commentType: String
}
struct TextData: Decodable {
let evidences: [Evidence]?
let value: String
}
struct Evidence: Decodable {
let evidenceCode: String
}

How to prune a tree of selective nodes without recursion

I can't solve the following problem without recursion. I get that the solution has to do with making a list of nodes to process but that's where I get stuck.
The problem is to remove all nodes from a tree with a negative value. I wrote an example in Swift below and included the recursive solution I came up with. What is the solution to the problem without using recursion?
struct Tree: Codable {
enum CodingKeys: String, CodingKey {
case children
case value
}
let children: [Tree]
let value: Int
}
let tree = Tree(children: [
Tree(children: [
Tree(children: [], value: 3),
Tree(children: [], value: -1)
], value: 5),
Tree(children: [
Tree(children: [], value: 3)
], value: -2)
], value: 12)
func prune(tree: Tree) -> Tree? {
if tree.value < 0 {
return nil
}
return Tree(children: tree.children.compactMap { child in
prune(tree: child)
}, value: tree.value)
}
let prunedTree = prune(tree: tree)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(prunedTree)
print(String(data: json, encoding: .utf8)!)
// {
// "value" : 12,
// "children" : [
// {
// "value" : 5,
// "children" : [
// {
// "value" : 3,
// "children" : [
//
// ]
// }
// ]
// }
// ]
// }

No Data when Decoding Nested Array in Json by Swift

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.

How can i parse an Json array of a list of different object using Codable?

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.

Deserialize JSON array based on nested type attribute

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.