I am trying to fetch a random user from an api and I created a struct called result to match the json from the api.
This is what I came up with but I am sure this is not the right way to do it!
When I run this I get [] as a result so I want to know how I can change this code in order to match the json
#State var results : result?
struct result : Codable {
var results : [user]
struct user : Codable{
var gender : String
var name : name
var location : location
var email:String
struct name : Codable{
var title : String
var first : String
var last : String
}
struct location : Codable {
let street : street
let city : String
let state : String
let country : String
let postcode : Int
struct street :Codable {
var number : String
var name : String
}
}
struct login : Codable {
var uuid : String
var username : String
var password : String
}
}
}
This is how the Json looks
{
"results": [
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
},
"location": {
"street": "9278 new road",
"city": "kilcoole",
"state": "waterford",
"postcode": "93027",
"coordinates": {
"latitude": "20.9267",
"longitude": "-7.9310"
},
"timezone": {
"offset": "-3:30",
"description": "Newfoundland"
}
},
"email": "brad.gibson#example.com",
"login": {
"uuid": "155e77ee-ba6d-486f-95ce-0e0c0fb4b919",
"username": "silverswan131",
"password": "firewall",
"salt": "TQA1Gz7x",
"md5": "dc523cb313b63dfe5be2140b0c05b3bc",
"sha1": "7a4aa07d1bedcc6bcf4b7f8856643492c191540d",
"sha256": "74364e96174afa7d17ee52dd2c9c7a4651fe1254f471a78bda0190135dcd3480"
},
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
},
"registered": {
"date": "2002-05-21T10:59:49.966Z",
"age": 17
},
"phone": "011-962-7516",
"cell": "081-454-0666",
"id": {
"name": "PPS",
"value": "0390511T"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "IE"
}
],
"info": {
"seed": "fea8be3e64777240",
"results": 1,
"page": 1,
"version": "1.3"
}
}
You want to learn how to do this on your own but there are tools on the web that can easily help you create a struct from a JSON. This is a really good one.
You were close so make sure you dissect the code and notice the differences
import SwiftUI
class ResultsViewModel: ObservableObject{
let json = """
{
"results": [
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
},
"location": {
"street": "9278 new road",
"city": "kilcoole",
"state": "waterford",
"postcode": "93027",
"coordinates": {
"latitude": "20.9267",
"longitude": "-7.9310"
},
"timezone": {
"offset": "-3:30",
"description": "Newfoundland"
}
},
"email": "brad.gibson#example.com",
"login": {
"uuid": "155e77ee-ba6d-486f-95ce-0e0c0fb4b919",
"username": "silverswan131",
"password": "firewall",
"salt": "TQA1Gz7x",
"md5": "dc523cb313b63dfe5be2140b0c05b3bc",
"sha1": "7a4aa07d1bedcc6bcf4b7f8856643492c191540d",
"sha256": "74364e96174afa7d17ee52dd2c9c7a4651fe1254f471a78bda0190135dcd3480"
},
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
},
"registered": {
"date": "2002-05-21T10:59:49.966Z",
"age": 17
},
"phone": "011-962-7516",
"cell": "081-454-0666",
"id": {
"name": "PPS",
"value": "0390511T"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "IE"
}
],
"info": {
"seed": "fea8be3e64777240",
"results": 1,
"page": 1,
"version": "1.3"
}
}
""".data(using: .utf8)
var result: JSONObject? {
var decoded: JSONObject? = nil
do{
decoded = try JSONDecoder().decode(JSONObject.self, from: json!)
}catch{
print(error)
}
return decoded
}
}
struct ResultsView: View {
#StateObject var vm: ResultsViewModel = ResultsViewModel()
var body: some View {
if vm.result == nil || vm.result?.results.first == nil{
Text("nil")
}else{
VStack{
Text(vm.result!.results.first!.name.first)
Text(vm.result!.results.first!.name.last)
}
}
}
}
// MARK: - JSONObject
struct JSONObject: Codable {
let results: [Result]
let info: Info
}
// MARK: - Info
struct Info: Codable {
let seed: String
let results, page: Int
let version: String
}
// MARK: - Result
struct Result: Codable {
let gender: String
let name: Name
let location: Location
let email: String
let login: Login
let dob, registered: Dob
let phone, cell: String
let id: ID
let picture: Picture
let nat: String
}
// MARK: - Dob
struct Dob: Codable {
let date: String
let age: Int
}
// MARK: - ID
struct ID: Codable {
let name, value: String
}
// MARK: - Location
struct Location: Codable {
let street, city, state, postcode: String
let coordinates: Coordinates
let timezone: Timezone
}
// MARK: - Coordinates
struct Coordinates: Codable {
let latitude, longitude: String
}
// MARK: - Timezone
struct Timezone: Codable {
let offset, timezoneDescription: String
enum CodingKeys: String, CodingKey {
case offset
case timezoneDescription = "description"
}
}
// MARK: - Login
struct Login: Codable {
let uuid, username, password, salt: String
let md5, sha1, sha256: String
}
// MARK: - Name
struct Name: Codable {
let title, first, last: String
}
// MARK: - Picture
struct Picture: Codable {
let large, medium, thumbnail: String
}
Related
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.
This question already has answers here:
Sorting a Swift array by ordering from another array
(10 answers)
Closed 3 years ago.
In my json result, there is odds array. And inside of it, there will be 8 types of bet. And i want to sort the array by bet type as i want to show. Eg. first "name": "3Way Result", second another one, third "over/under" etc..
Here is my json result from server.
{
"success": true,
"result": [
{
"league_id": 5,
"localTeam": {"data": {}},
"visitorTeam": {"data": {}},
"scores": {},
"time": {"starting_at": {},},
"league": {"data": {"coverage": {}}},
"odds": [
{
"id": 12,
"name": "Over/Under",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "Over",
"value": "2.00",
"extra": null,
"probability": "50%",
"dp3": "2.000",
"american": 100,
"handicap": null,
"total": "2.5",
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
},
{
"label": "Under",
"value": "1.80",
"probability": "55.56%",
"dp3": "1.800",
}
]
}
}
]
}
},
{
"id": 1,
"name": "3Way Result",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1",
"value": "2.10",
"extra": null,
"probability": "47.62%",
"dp3": "2.100",
"american": 110,
},
{
"label": "X",
"value": "3.30",
"extra": null,
"probability": "30.3%",
"dp3": "3.300",
"american": 229,
},
{
"label": "2",
"value": "3.60",
}
]
}
}
]
}
},
{
"id": 975909,
"name": "Correct Score",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1:0",
"value": "7.50",
"extra": null,
"probability": "13.33%",
"dp3": "7.500",
"american": 650,
"factional": null,
"handicap": null,
"total": null,
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
"last_update": {
"date": "2020-02-20 10:59:06.746514",
"timezone_type": 3,
"timezone": "UTC"
}
},
{
"label": "2:0",
"value": "10.00",
},
]
}
}
]
}
},
],
"tipsters": 2
}
]
}
so it is not alphabetically, datetime or anytype i could access. How could i do ?
Update: I have added model.
struct BetterMatchResults: APIModel, Codable {
var success: Bool?
var result: [BetterMatch]?
}
struct BetterMatch: APIModel, Codable {
var id,_id: String?
var localTeam, visitorTeam: BetterTeam?
var spId, league_id, seasonID: Int?
var winningOddsCalculated: Bool?
var time: BetterTime?
var league: BetterLeague?
var createdAt, updatedAt: String?
var odds: [BetterOdd]!
var resultID: String?
var tipsters: Int?
var stats_url: String?
}
struct BetterLeague : APIModel, Codable {
var data: LeagueData?
}
struct LeagueData : APIModel, Codable{
var id: Int?
var active: Bool?
//var legacyID, countryID: Int?
var logo_path: String?
var name: String?
//var isCup: Bool?
}
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// MARK: - Bookmaker
struct BetterBookmaker : APIModel, Codable {
var data: [BetterBookmakerDatum]?
}
// MARK: - BookmakerDatum
struct BetterBookmakerDatum : APIModel, Codable {
var id: Int?
var name: String?
var odds: BetterOdds?
}
// MARK: - Odds
struct BetterOdds : APIModel, Codable {
var data: [BetterOddsDatum]?
}
class BetterOddsDatum: APIModel , Codable {
var label: String?
//var extra: NSNull?
//var probability, dp3: String?
var american: Int?
//var factional, handicap: NSNull?
var total: String?
var winning: Bool?
var stop: Bool?
var bookmakerEventID: Int?
//private var odd: Double
public var value: String?
init() {
}
}
If I understand your question correctly you want to be able sort the data based on betting type but the value of betting types are not sortable if used as a String variable. The solution would be to converting them into enum types with raw values and then sorting the array based on those raw values. Here is an example:
// Create a BetType for your datas
enum BetType: Int {
case overUnder = 0
case threeWayResult = 1 // 3WayResult
...
}
// Update your BetterOdd
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var betType: BetType = .overUnder // or put your default value here
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// Loop your BetterMatch property's odds value after fetching datas.
for i in 0..<betterMatch.odds {
if betterMatch.odds[i].name == "over/under" {
betterMatch.odds[i].betType = .overUnder
}
... // Do the same for other types as well in else if blocks
}
Another alternative would be to add a function for getting the type in BetterOdd
struct BetterOdd ... {
... // Your properties
func getBetType() -> BetType {
if name == "over/under" {
return .overUnder
} else if name == "3WayResult" {
return .threeWayResult
}
... // Other cases
}
}
Finnaly for sorting you can do:
let result = betterMatch.odds.sorted({ $0.betType.rawValue > $1.betType.rawValue })
// or if you used the function solution
let result = betterMatch.odds.sorted({ $0.getBetType().rawValue > $1.getBetType().rawValue })
Since you are using a Codable approach you might need to loop the array and set the betType values based on name values.
i changed model
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
//var betType: BetType = .threeWayResult
enum BetType:Int, APIModel, Codable {
case threeWayResult = 7
case overUnder = 6
case doubleChance = 5
case bothTeamsToScore = 4
case threeWayResultFirstHalf = 3
case threeWayResultSecondHalf = 2
case correctScore = 1
case hTfTdouble = 0
}
//
func getBetType() -> BetType {
if name == "3Way Result" {
return .threeWayResult
} else if name == "Over/Under" {
return .overUnder
} else if name == "Double Chance" {
return .doubleChance
} else if name == "Both Teams To Score" {
return .bothTeamsToScore
} else if name == "3Way Result 1st Half" {
return .threeWayResultFirstHalf
} else if name == "3Way Result 2nd Half"{
return .threeWayResultSecondHalf
} else if name == "Correct Score"{
return .correctScore
} else if name == "HF/FT Double" {
return .hTfTdouble
} else {
return .correctScore
}
}
}
and then :
let matchOddsArray = match.odds!
let result = matchOddsArray.sorted(by: { $0.betType.rawValue > $1.betType.rawValue})
let bet = result[indexPath.row]
works perfect.
First of all, mark result in BetterMatchResults and odds in BetterMatch as var, i.e.
struct BetterMatchResults: Codable {
let success: Bool
var result: [BetterMatch] //here....
}
struct BetterMatch: Codable {
var odds: [BetterOdd] //here....
//rest of the parameters...
}
Next, while parsing your JSON data, use map(_:) and sorted(by:) to modify the result and odds in the betterMatchResults, i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var betterMatchResults = try JSONDecoder().decode(BetterMatchResults.self, from: data)
betterMatchResults.result = betterMatchResults.result.map {(betterMatch) in
var match = betterMatch
match.odds = match.odds.sorted(by: { $0.name < $1.name })
return match
}
//Test the response...
print(betterMatchResults.result.map({ $0.odds.map({ $0.name }) })) //[["3Way Result", "Correct Score", "Over/Under"]]
} catch {
print(error)
}
I have a struct type which lists different types which must be decoded from JSON and I cannot find any information on how to specify more than one type in my enum called CodingKeys. In the innermost dictionary sometimes one key:value pair will be [String:String] and another key:value pair will be [String:Int]
I have tried specifying just String and CodingKey as in the code snippet below but Xcode reports 2 error messages.
struct JSONSiteData : Codable {
let destinationAddresses : [String]
let originAddresses : [String]
let rows : [ [String : [[String:[[String: [String:Any]]]]]] ]
let status : String
}
enum CodingKeys : String, CodingKey {
case destinationAddresses = "destination_addresses"
case originAddresses = "origin_addresses"
case rows
case status
}
I get the following error messages from Xcode;
Type 'JSONSiteData' does not conform to protocol 'Decodable'
Type 'JSONSiteData' does not conform to protocol 'Encodable'
Here is my JSON;
{
"destination_addresses": [
"1 Dunwell Ln, Bolam, Darlington DL2 2UW, UK",
"Unnamed Road, Newton Aycliffe DL5 6QZ, UK",
"Preston Manor Farm, Preston le Skerne, Newton Aycliffe DL5 6JH, United Kingdom",
"6 Middridge Farms, Middridge, Newton Aycliffe DL5 7JQ, UK",
"1 The Gardens, Hunwick, Crook DL15 0XW, UK"
],
"origin_addresses": [
"42 Drovers Way, Dunstable LU6 1AW, UK"
],
"rows": [
{
"elements": [
{
"distance": {
"text": "220 mi",
"value": 353731
},
"duration": {
"text": "3 hours 45 mins",
"value": 13475
},
"status": "OK"
},
{
"distance": {
"text": "222 mi",
"value": 356696
},
"duration": {
"text": "3 hours 45 mins",
"value": 13471
},
"status": "OK"
},
{
"distance": {
"text": "222 mi",
"value": 358053
},
"duration": {
"text": "3 hours 46 mins",
"value": 13545
},
"status": "OK"
},
{
"distance": {
"text": "225 mi",
"value": 361421
},
"duration": {
"text": "3 hours 49 mins",
"value": 13768
},
"status": "OK"
},
{
"distance": {
"text": "229 mi",
"value": 369280
},
"duration": {
"text": "3 hours 57 mins",
"value": 14238
},
"status": "OK"
}
]
}
],
"status": "OK"
}
You can try this,
// To parse the JSON, add this file to your project and do:
//
// let jsonSiteData = try? newJSONDecoder().decode(JSONSiteData.self, from: jsonData)
import Foundation
struct JSONSiteData: Codable {
let destinationAddresses, originAddresses: [String]
let rows: [Row]
let status: String
enum CodingKeys: String, CodingKey {
case destinationAddresses = "destination_addresses"
case originAddresses = "origin_addresses"
case rows, status
}
}
struct Row: Codable {
let elements: [Element]
}
struct Element: Codable {
let distance, duration: Distance
let status: String
}
struct Distance: Codable {
let text: String
let value: Int
}
Refer this link to generate models from JSON Strings.
https://app.quicktype.io/
In terms of Codable there are no different types.
Decode these structs
struct JSONSiteData : Decodable {
let destinationAddresses : [String]
let originAddresses : [String]
let rows : [Row]
let status : String
}
struct Row : Decodable {
let elements : [Element]
}
struct Element : Decodable {
let distance : Item
let duration : Item
let status : String
}
struct Item : Decodable {
let text : String
let value : Int
}
I have been trying to find a struct inside the list of nested structs. Can anyone help me on this?
struct Places: PlacesProtocol {
private(set) var id: String
private(set) var name: String
private(set) var childPlaces: [PlacesProtocol]?
init(json: JSON) {
self.name = json[“Name”]
self.id = json[“Id”]
self. childPlaces = json[“ChildPlaces”].arrayValue.map { Places(json: $0) }
}
JSON:
{
"Id": "1",
"Name": "Place 1",
"ChildPlaces": [{
"Id": "12",
"Name": "Place 2",
"ChildPlaces": [{
"Id": "123",
"Name": "Place 3",
"ChildPlaces": [{
"Id": "1234",
"Name": "Place 4",
"ChildPlaces": null
}]
}, {
"Id": "13",
"Name": "Place 5",
"ChildPlaces": null
}]
}]
}
I have tried this:
nestedStruct.filter { $0.id == "13" }
I am able to parse this JSON in to the nested structure and I am trying to find a struct with Id. I have tried filter but it just filters only the first layer of the nested struct. Is there a way I can build recursive filter to find the struct which is deep inside the nested struct.
You could use a recursive function to perform a depth first search. Here's a rough example:
extension Place {
depthFirstSearch(where closure: (Place) -> Bool) -> Place? {
if closure(self) { return self }
else {
return self.chlidPlaces.first(where: {
$0.depthFirstSearch(where: closure)
})
}
}
}
let placeID13 = mainPlace.depthFirstSearch(where: { $0.id == "13" })