I want to show car data in tableview , and there is 2 type of history model. As you can see in my response 1st object of car have history and other object have None.
How to make Structure of History json model in structure of VehicalModel , how to access that model and map with the help of alamofire. And How to check the history is available or not if available then store in model and show in tableview.
This is my Response
{
"response": "success",
"account_type": "2",
"car_data": [
{
"registration_no": "Lzq 2233",
"engincc": "600 - 999",
"enginccID": "1",
"vehicleID": "32",
"history": [
{
"packages": "",
"date_time": "2018-12-22 00:40:55",
"bill_amount": "7098",
"bill_discount": "133.0571251",
"bill_paid": "36070"
}
]
},
{
"registration_no": "ghfdhhh",
"engincc": "1500 - 1799",
"enginccID": "3",
"vehicleID": "33",
"history": "None"
}
]
}
This is My Model
struct VehicleDataModel {
var registrationNo : String?
var engineCC: String?
var engineCCID: String?
var vehicleID: String?
var history: [HistoryModel]
struct HistoryModel {
var packages: String
var billDiscount : String
var dateTime: String
var billPaid: String
var billAmount: String
}
}
This is my Call API Function:
func callApi() {
let url = "http://esspk.net/production/20m/Api/getVehicleApi"
let userID = UserDefaults.standard.integer(forKey: "user_id")
let param = ["user_id" : userID]
print(param)
ServerCall.makeCallWitoutFile(url, params: param, type: Method.POST, currentView: nil) { (response) in
if let json = response {
print(json)
if let carData = json["car_data"].array
{
//let vehicalObj = VehicleDataModel()
for cData in carData {
let regNo = cData["registration_no"].string
let enginCC = cData["engincc"].string
let enginID = cData["enginccID"].string
let vehicleID = cData["vehicleID"].string
let history = cData["history"].arrayObject
// let vech = VehicleDataModel(registrationNo: regNo, engineCC: enginCC, engineCCID: enginID, vehicleID: vehicle, history: history)
// self.vehicalModel.append(vech)
// let vech = VehicleDataModel.init(registrationNo: regNo, engineCC: enginCC, engineCCID: enginID, vehicleID: vehicleID, history: VehicleDataModel.HistoryModel( )
}
self.myVehicleTblView.reloadData()
}
}
}
}
First Make Model Class.
class Welcome: Codable {
let response, accountType: String
let carData: [CarDatum]
enum CodingKeys: String, CodingKey {
case response
case accountType = "account_type"
case carData = "car_data"
}
init(response: String, accountType: String, carData: [CarDatum]) {
self.response = response
self.accountType = accountType
self.carData = carData
}
}
class CarDatum: Codable {
let registrationNo, engincc, enginccID, vehicleID: String
let history: HistoryUnion
enum CodingKeys: String, CodingKey {
case registrationNo = "registration_no"
case engincc, enginccID, vehicleID, history
}
init(registrationNo: String, engincc: String, enginccID: String, vehicleID: String, history: HistoryUnion) {
self.registrationNo = registrationNo
self.engincc = engincc
self.enginccID = enginccID
self.vehicleID = vehicleID
self.history = history
}
}
enum HistoryUnion: Codable {
case historyElementArray([HistoryElement])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([HistoryElement].self) {
self = .historyElementArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(HistoryUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for HistoryUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .historyElementArray(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
class HistoryElement: Codable {
let packages, dateTime, billAmount, billDiscount: String
let billPaid: String
enum CodingKeys: String, CodingKey {
case packages
case dateTime = "date_time"
case billAmount = "bill_amount"
case billDiscount = "bill_discount"
case billPaid = "bill_paid"
}
init(packages: String, dateTime: String, billAmount: String, billDiscount: String, billPaid: String) {
self.packages = packages
self.dateTime = dateTime
self.billAmount = billAmount
self.billDiscount = billDiscount
self.billPaid = billPaid
}
}
Or you can also Use below Class
struct Welcome {
let response, accountType: String
let carData: [CarDatum]
}
struct CarDatum {
let registrationNo, engincc, enginccID, vehicleID: String
let history: HistoryUnion
}
enum HistoryUnion {
case historyElementArray([HistoryElement])
case string(String)
}
struct HistoryElement {
let packages, dateTime, billAmount, billDiscount: String
let billPaid: String
}
Then I recommended to use the Alamofire for Call the API.
func request() {
let url = URL(string: "my url")
Alamofire.request(url!).responseJSON {(response) in
switch (response.result) {
case .success:
if let data = response.data {
do {
let response = try JSONDecoder().decode([Welcome].self, from: data)
self.arrList = response
DispatchQueue.main.async {
//Print Responce
}
} catch {
print(error.localizedDescription)
}
}
case .failure( let error):
print(error)
}
}
}
}
Hope This Works.
Related
"Key 'CodingKeys(stringValue: "row", intValue: nil)' not found: No
value associated with key CodingKeys(stringValue: "row", intValue:
nil) ("row"). codingPath: [CodingKeys(stringValue: "schoolInfo",
intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)] "
I am getting these errors
I'm doing API communication for school information
Here's the JSON response
{
"schoolInfo": [
{
"head": [
{
"list_total_count": 1
},
{
"RESULT": {
"CODE": "INFO-000",
"MESSAGE": "정상 처리되었습니다."
}
}
]
},
{
"row": [
{
"ATPT_OFCDC_SC_CODE": "F10",
"ATPT_OFCDC_SC_NM": "광주광역시교육청",
"SD_SCHUL_CODE": "7401173",
"SCHUL_NM": "정암초등학교",
"ENG_SCHUL_NM": "Jeong-Am Elementary School",
"SCHUL_KND_SC_NM": "초등학교",
"LCTN_SC_NM": "광주광역시",
"JU_ORG_NM": "광주광역시서부교육지원청",
"FOND_SC_NM": "공립",
"ORG_RDNZC": "62254 ",
"ORG_RDNMA": "광주광역시 광산구 첨단과기로 104",
"ORG_RDNDA": "(월계동)",
"ORG_TELNO": "062-970-4104",
"HMPG_ADRES": "http://jungam.gen.es.kr",
"COEDU_SC_NM": "남여공학",
"ORG_FAXNO": "062-972-3949",
"HS_SC_NM": null,
"INDST_SPECL_CCCCL_EXST_YN": "N",
"HS_GNRL_BUSNS_SC_NM": "일반계",
"SPCLY_PURPS_HS_ORD_NM": null,
"ENE_BFE_SEHF_SC_NM": "전기",
"DGHT_SC_NM": "주간",
"FOND_YMD": "19960301",
"FOAS_MEMRD": "19961002",
"LOAD_DTM": "20230115"
}
]
}
]
}
Here's the struct
struct schoolResponse: Codable {
let schoolInfo: [schoolRow]
}
struct schoolRow: Codable {
let row: [schoolsInfo]
}
struct schoolsInfo: Codable {
var schoolName: String
var schoolCode: String
var officeCode: String
private enum CodingKeys: String, CodingKey {
case schoolName = "SCHUL_NM"
case schoolCode = "SD_SCHUL_CODE"
case officeCode = "ATPT_OFCDC_SC_CODE"
}
}
Here's the function
protocol SchoolInfoProtocol: AnyObject {
var schoolData: PublishSubject<[schoolRow]> { get set }
}
class SchoolNameViewModel: BaseViewModel {
weak var delegate: SchoolInfoProtocol?
var schoolAddress: [schoolsInfo] = []
func fetchSchoolName(schoolName: String) {
let provider = MoyaProvider<SchoolNameAPI>()
provider.request(.schools(schoolName: schoolName, apiKey: "e6f3e10be1b1426cbcfb2be62afff409")) { (result) in
switch result {
case .success(let response):
let responseData = response.data
do {
let decoded = try JSONDecoder().decode(schoolResponse.self, from: responseData).schoolInfo
// self.delegate?.schoolData.onNext(decoded)
print(decoded)
} catch let DecodingError.dataCorrupted(context) {
print(context)
} catch let DecodingError.keyNotFound(key, context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch let DecodingError.valueNotFound(value, context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch let DecodingError.typeMismatch(type, context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
case .failure(let error):
print(error.localizedDescription)
print("1")
}
}
}
}
I want to solve this problem
I created a root model
Your structs don't match the JSON. Try this instead:
struct SchoolResponse: Codable {
let schoolInfo: [SchoolInfo]
}
struct SchoolInfo: Codable {
let head: [Head]?
let row: [Row]?
}
struct Head: Codable {
let listTotalCount: Int?
let result: Result?
enum CodingKeys: String, CodingKey {
case listTotalCount = "list_total_count"
case result = "RESULT"
}
}
struct Result: Codable {
let code, message: String
enum CodingKeys: String, CodingKey {
case code = "CODE"
case message = "MESSAGE"
}
}
struct Row: Codable {
let atptOfcdcScCode, atptOfcdcScNm, sdSchulCode, schulNm: String
let engSchulNm, schulKndScNm, lctnScNm, juOrgNm: String
let fondScNm, orgRdnzc, orgRdnma, orgRdnda: String
let orgTelno: String
let hmpgAdres: String
let coeduScNm, orgFaxno: String
let hsScNm: String?
let indstSpeclCccclExstYn, hsGnrlBusnsScNm: String
let spclyPurpsHsOrdNm: String?
let eneBfeSehfScNm, dghtScNm, fondYmd, foasMemrd: String
let loadDtm: String
enum CodingKeys: String, CodingKey {
case atptOfcdcScCode = "ATPT_OFCDC_SC_CODE"
case atptOfcdcScNm = "ATPT_OFCDC_SC_NM"
case sdSchulCode = "SD_SCHUL_CODE"
case schulNm = "SCHUL_NM"
case engSchulNm = "ENG_SCHUL_NM"
case schulKndScNm = "SCHUL_KND_SC_NM"
case lctnScNm = "LCTN_SC_NM"
case juOrgNm = "JU_ORG_NM"
case fondScNm = "FOND_SC_NM"
case orgRdnzc = "ORG_RDNZC"
case orgRdnma = "ORG_RDNMA"
case orgRdnda = "ORG_RDNDA"
case orgTelno = "ORG_TELNO"
case hmpgAdres = "HMPG_ADRES"
case coeduScNm = "COEDU_SC_NM"
case orgFaxno = "ORG_FAXNO"
case hsScNm = "HS_SC_NM"
case indstSpeclCccclExstYn = "INDST_SPECL_CCCCL_EXST_YN"
case hsGnrlBusnsScNm = "HS_GNRL_BUSNS_SC_NM"
case spclyPurpsHsOrdNm = "SPCLY_PURPS_HS_ORD_NM"
case eneBfeSehfScNm = "ENE_BFE_SEHF_SC_NM"
case dghtScNm = "DGHT_SC_NM"
case fondYmd = "FOND_YMD"
case foasMemrd = "FOAS_MEMRD"
case loadDtm = "LOAD_DTM"
}
}
(remove or rename properties as needed)
I have an API that will sometimes return a specific key value (in this case id) in the JSON as an Int and other times it will return that same key value as a String. How do I use codable to parse that JSON?
struct GeneralProduct: Codable {
var price: Double!
var id: String?
var name: String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
I keep getting this error message: Expected to decode String but found a number instead. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
edit/update:
You can also simply assign nil to your id when your api returns 0:
do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
This is a possible solution with MetadataType, the nice thing is that can be a general solution not for GeneralProduct only, but for all the struct having the same ambiguity:
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
this is the test:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}
Seamlessly decoding from either Int or String into the same property requires writing some code.
However, thanks to a (somewhat) new addition to the language,(property wrappers), you can make it quite easy to reuse this logic wherever you need it:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
#Flexible var id: Int // note this is an Int
var name: String
}
The property wrapper and its supporting code can be implemented like this:
#propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
Original answer
You can use a wrapper over a string that knows how to decode from any of the basic JSON data types: string, number, boolean:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
You can then use this new type in your struct. One minor disadvantage would be that consumer of the struct will need to make another indirection to access the wrapped string. However that can be avoided by declaring the decoded RelaxedString property as private, and use a computed one for the public interface:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
private enum CodingKeys: String, CodingKey {
case price = "p"
case _id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self._id = id.map(RelaxedString.init)
self.name = name
}
}
Advantages of the above approach:
no need to write custom init(from decoder: Decoder) code, which can become tedious if the number of properties to be decoded increase
reusability - RelaxedString can be seamlessly used in other structs
the fact that the id can be decoded from a string or an int remains an implementation detail, consumers of GeneralProduct don't know/care that the id can come from a string or an int
the public interface exposes string values, which keeps the consumer code simple as it will not have to deal with multiple types of data
I created this Gist which has a ValueWrapper struct that can handle
the following types
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23
Based on #Cristik 's answer, I come with another solution using #propertyWrapper.
#propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
And usage is
struct SomeDTO: Codable {
#StringForcible var id: String?
}
Also works like -I think-
struct AnotherDTO: Codable {
var some: SomeDTO?
}
I need to serialise a JSON response of some what below type. which has different type
[{
"type": "respiration_rate",
"value": 45
}
{ "type": "blood_pressure",
"value": { hg: 50 ,mm:120 }
}]
My class for serialising the upper json is
class Template: Codable {
var type: String?
var value: Double?
private enum CodingKeys: String, CodingKey {
case type
case value
}
}
How can serialise the value wether it be double or object dynamically?
Here is the code you need:
class Template: Codable {
let type: String?
let value: Value?
private enum CodingKeys: String, CodingKey {
case type
case value
}
typealias ValueDictionary = Dictionary<String, Int>
enum Value: Codable {
case double(Double)
case object(ValueDictionary)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Double.self) {
self = .double(x)
return
}
if let x = try? container.decode(ValueDictionary.self) {
self = .object(x)
return
}
throw DecodingError.typeMismatch(Value.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ValueUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let x):
try container.encode(x)
case .object(let x):
try container.encode(x)
}
}
}
}
You can use an enum with associated values instead of a class, as the JSON format seems be heterogenous.
enum Value: Decodable {
case respirationRate(Double)
case bloodPressure(hg: Double, mm: Double)
private struct BloodPresure: Decodable {
let hg: Double
let mm: Double
}
private enum CodingKeys: String, CodingKey {
case type
case value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch try container.decode(String.self, forKey: .type) {
case "respiration_rate":
self = try .respirationRate(container.decode(Double.self, forKey: .value))
case "blood_pressure":
let details = try container.decode(BloodPresure.self, forKey: .value)
self = .bloodPressure(hg: details.hg, mm: details.mm)
case let type: throw DecodingError.dataCorruptedError(forKey: .value, in: container, debugDescription: "Invalid type: \(type)")
}
}
}
Decoding your json is now easy:
let json = """
[{
"type": "respiration_rate",
"value": 45
},
{ "type": "blood_pressure",
"value": { "hg": 50 ,"mm":120 }
}]
"""
do {
let values = try JSONDecoder().decode([Value].self, from: json.data(using: .utf8)!)
print(values)
} catch {
print(error)
}
How can I append into an array using JSON Model Class.
Here is my JSON API Request: https://developer.github.com/v3/search/
import Foundation
typealias GitDecode = [GitDecodeElement]
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Item: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String
let size, stargazersCount, watchersCount: Int
let language: String
let forksCount, openIssuesCount: Int
let masterBranch, defaultBranch: String
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case masterBranch = "master_branch"
case defaultBranch = "default_branch"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
And here I have my class Model where I'm saying what I want to extract from that response:
import Foundation
struct Git: Codable{
let totalCount: Int
let items: GitItem
init ( totalCount: Int,
itemID: Int, itemDescription: String,
ownerID: Int, ownerAvatarURL: String) {
self.totalCount = totalCount
self.items = GitItem(id: itemID, description: itemDescription, owner: GitOwner(id: ownerID, avatarURL: ownerAvatarURL))
}
}
struct GitItem: Codable{
let id: Int
let description: String
let owner: GitOwner
}
struct GitOwner: Codable {
let id: Int
let avatarURL: String
}
Now I'm stuck when I try to append into my array all my custom properties because itemID, itemDescription, ownerID and ownerAvatarURL are in different classes.
Here is how I'm trying to get all the that properties from JSON using JSONDecoder:
import UIKit
class MainViewController: UIViewController {
var gitRepositoriesArray = [Git]()
override func viewDidLoad() {
super.viewDidLoad()
}
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://developer.github.com/v3/search/")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount,
itemID: <#T##Int#> , itemDescription: <#T##String#>, ownerID: <#T##Int#>, ownerAvatarURL: <#T##String#>))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
}
Complete working playground code:
Makes a search against the API for Swift related repos.
Parses them, adds them to the array and prints out some basic information on each one. (fullName, name, avatarUrl
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Repo]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Repo: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String?
let size, stargazersCount, watchersCount: Int
let language: String?
let forksCount, openIssuesCount: Int
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
var gitRepositoriesArray = [Repo]()
// Download Git Repositories from API
func parseGitRepositories() {
let url = URL(string: "https://api.github.com/search/repositories?q=topic:swift+topic:ios")
var request = URLRequest(url: url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
do {
let gitRepositoriesList = try JSONDecoder().decode(GitDecodeElement.self, from: data!)
gitRepositoriesArray = gitRepositoriesArray + gitRepositoriesList.items
print(gitRepositoriesArray.count)
for repo in gitRepositoriesList.items {
print("\(repo.fullName) - \(repo.name) - \(repo.owner.avatarURL)")
}
} catch {
let str = String(data: data!, encoding: .utf8)
print(str)
print(error)
}
}.resume()
}
parseGitRepositories()
PlaygroundPage.current.needsIndefiniteExecution = true
Output:
30
justjavac/free-programming-books-zh_CN - free-programming-books-zh_CN - https://avatars1.githubusercontent.com/u/359395?v=4
dkhamsing/open-source-ios-apps - open-source-ios-apps - https://avatars0.githubusercontent.com/u/4723115?v=4
matteocrippa/awesome-swift - awesome-swift - https://avatars2.githubusercontent.com/u/475463?v=4
xitu/gold-miner - gold-miner - https://avatars2.githubusercontent.com/u/10482599?v=4
lkzhao/Hero - Hero - https://avatars1.githubusercontent.com/u/3359850?v=4
ReactiveX/RxSwift - RxSwift - https://avatars1.githubusercontent.com/u/6407041?v=4
realm/realm-cocoa - realm-cocoa - https://avatars0.githubusercontent.com/u/7575099?v=4
CocoaPods/CocoaPods - CocoaPods - https://avatars1.githubusercontent.com/u/1189714?v=4
CosmicMind/Material - Material - https://avatars1.githubusercontent.com/u/10069574?v=4
// rest truncated
Notice how I use less models than you do in your code. There is no need to duplicate code, just use the parts you want, when you want them.
There is some problem in you Git structure. I have corrected the initializer like this:
struct Git: Codable{
let totalCount: Int
var items = [GitItem]()
init(totalCount: Int, items: [Item]) {
self.totalCount = totalCount
for item in items {
self.items.append(GitItem(id: item.id, description: item.description, owner: GitOwner(id: item.owner.id, avatarURL: item.owner.avatarURL)))
}
}
So your parsing method will be changed accordingly:
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount, items: eachRepo.items))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
Also note the change of url to https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc. The current url that your are using in your code is just the html page with examples. It doesn't return proper json results.
I'm currently working with Codable types in my project and facing an issue.
struct Person: Codable
{
var id: Any
}
id in the above code could be either a String or an Int. This is the reason id is of type Any.
I know that Any is not Codable.
What I need to know is how can I make it work.
Quantum Value
First of all you can define a type that can be decoded both from a String and Int value.
Here it is.
enum QuantumValue: Decodable {
case int(Int), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
Person
Now you can define your struct like this
struct Person: Decodable {
let id: QuantumValue
}
That's it. Let's test it!
JSON 1: id is String
let data = """
{
"id": "123"
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
JSON 2: id is Int
let data = """
{
"id": 123
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
UPDATE 1 Comparing values
This new paragraph should answer the questions from the comments.
If you want to compare a quantum value to an Int you must keep in mind that a quantum value could contain an Int or a String.
So the question is: what does it mean comparing a String and an Int?
If you are just looking for a way of converting a quantum value into an Int then you can simply add this extension
extension QuantumValue {
var intValue: Int? {
switch self {
case .int(let value): return value
case .string(let value): return Int(value)
}
}
}
Now you can write
let quantumValue: QuantumValue: ...
quantumValue.intValue == 123
UPDATE 2
This part to answer the comment left by #Abrcd18.
You can add this computed property to the Person struct.
var idAsString: String {
switch id {
case .string(let string): return string
case .int(let int): return String(int)
}
}
And now to populate the label just write
label.text = person.idAsString
Hope it helps.
Codable needs to know the type to cast to.
Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.
Otherwise the only way I can think of solving your issue currently is to use generics like below.
struct Person<T> {
var id: T
var name: String
}
let person1 = Person<Int>(id: 1, name: "John")
let person2 = Person<String>(id: "two", name: "Steve")
I solved this issue defining a new Decodable Struct called AnyDecodable, so instead of Any I use AnyDecodable. It works perfectly also with nested types.
Try this in a playground:
var json = """
{
"id": 12345,
"name": "Giuseppe",
"last_name": "Lanza",
"age": 31,
"happy": true,
"rate": 1.5,
"classes": ["maths", "phisics"],
"dogs": [
{
"name": "Gala",
"age": 1
}, {
"name": "Aria",
"age": 3
}
]
}
"""
public struct AnyDecodable: Decodable {
public var value: Any
private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
public init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyDecodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)
You could extend my struct to be AnyCodable if you are interested also in the Encoding part.
Edit: I actually did it.
Here is AnyCodable
struct AnyCodable: Decodable {
var value: Any
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
init(value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
extension AnyCodable: Encodable {
func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}
You can test it With the previous json in this way in a playground:
let stud = try! JSONDecoder().decode(AnyCodable.self, from: jsonData)
print(stud.value as! [String: Any])
let backToJson = try! JSONEncoder().encode(stud)
let jsonString = String(bytes: backToJson, encoding: .utf8)!
print(jsonString)
If your problem is that it's uncertain the type of id as it might be either a string or an integer value, I can suggest you this blog post: http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
Basically I defined a new Decodable type
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
From now on, your Person object would be
struct Person: Decodable {
var id: UncertainValue<Int, String>
}
you will be able to access your id using id.value
Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.
Eg:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
To make key as Any, I like all above answers. But when you are not sure which data type your server guy will send then you use Quantum class (as above), But Quantum type is little difficult to use or manage. So here is my solution to make your decodable class key as a Any data type (or "id" for obj-c lovers)
class StatusResp:Decodable{
var success:Id? // Here i am not sure which datatype my server guy will send
}
enum Id: Decodable {
case int(Int), double(Double), string(String) // Add more cases if you want
init(from decoder: Decoder) throws {
//Check each case
if let dbl = try? decoder.singleValueContainer().decode(Double.self),dbl.truncatingRemainder(dividingBy: 1) != 0 { // It is double not a int value
self = .double(dbl)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw IdError.missingValue
}
enum IdError:Error { // If no case matched
case missingValue
}
var any:Any{
get{
switch self {
case .double(let value):
return value
case .int(let value):
return value
case .string(let value):
return value
}
}
}
}
Usage :
let json = "{\"success\":\"hii\"}".data(using: .utf8) // response will be String
//let json = "{\"success\":50.55}".data(using: .utf8) //response will be Double
//let json = "{\"success\":50}".data(using: .utf8) //response will be Int
let decoded = try? JSONDecoder().decode(StatusResp.self, from: json!)
print(decoded?.success) // It will print Any
if let doubleValue = decoded?.success as? Double {
}else if let doubleValue = decoded?.success as? Int {
}else if let doubleValue = decoded?.success as? String {
}
You can replace Any with an enum accepting an Int or a String:
enum Id: Codable {
case numeric(value: Int)
case named(name: String)
}
struct Person: Codable
{
var id: Id
}
Then the compiler will complain about the fact that Id does not conform to Decodable. Because Id has associated values you need to implement this yourself. Read https://littlebitesofcocoa.com/318-codable-enums for an example of how to do this.
Thanks to Luka Angeletti's answer (https://stackoverflow.com/a/48388443/7057338) i've changed enum to struct so we can use it more easily
struct QuantumValue: Codable {
public var string: String?
public var integer: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let int = try? container.decode(Int.self) {
self.integer = int
return
}
if let string = try? container.decode(String.self) {
self.string = string
return
}
throw QuantumError.missingValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
try container.encode(integer)
}
enum QuantumError: Error {
case missingValue
}
func value() -> Any? {
if let s = string {
return s
}
if let i = integer {
return i
}
return nil
}
}
First of all, as you can read in other answers and comments, using Any for this is not good design. If possible, give it a second thought.
That said, if you want to stick to it for your own reasons, you should write your own encoding/decoding and adopt some kind of convention in the serialized JSON.
The code below implements it by encoding id always as string and decoding to Int or String depending on the found value.
import Foundation
struct Person: Codable {
var id: Any
init(id: Any) {
self.id = id
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
if let idstr = try container.decodeIfPresent(String.self, forKey: .id) {
if let idnum = Int(idstr) {
id = idnum
}
else {
id = idstr
}
return
}
fatalError()
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
try container.encode(String(describing: id), forKey: .id)
}
enum Keys: String, CodingKey {
case id
}
}
extension Person: CustomStringConvertible {
var description: String { return "<Person id:\(id)>" }
}
Examples
Encode object with numeric id:
var p1 = Person(id: 1)
print(String(data: try JSONEncoder().encode(p1),
encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"1"}
Encode object with string id:
var p2 = Person(id: "root")
print(String(data: try JSONEncoder().encode(p2),
encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"root"}
Decode to numeric id:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!))
// <Person id:2>
Decode to string id:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!))
// <Person id:admin>
An alternative implementation would be encoding to Int or String and wrap the decoding attempts in a do...catch.
In the encoding part:
if let idstr = id as? String {
try container.encode(idstr, forKey: .id)
}
else if let idnum = id as? Int {
try container.encode(idnum, forKey: .id)
}
And then decode to the right type in multiple attempts:
do {
if let idstr = try container.decodeIfPresent(String.self, forKey: .id) {
id = idstr
id_decoded = true
}
}
catch {
/* pass */
}
if !id_decoded {
do {
if let idnum = try container.decodeIfPresent(Int.self, forKey: .id) {
id = idnum
}
}
catch {
/* pass */
}
}
It's uglier in my opinion.
Depending on the control you have over the server serialization you can use either of them or write something else adapted to the actual serialization.
Here your id can be any Codable type:
Swift 4.2
struct Person<T: Codable>: Codable {
var id: T
var name: String?
}
let p1 = Person(id: 1, name: "Bill")
let p2 = Person(id: "one", name: "John")
Swift 5
This is an update about the best answer (IMHO) from Luca Angeletti, so to perform your request:
enum PersonAny: Codable {
case int(Int), string(String) // Insert here the different type to encode/decode
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw AnyError.missingValue
}
enum AnyError:Error {
case missingValue
}
}
// Your declaration
struct Person: Codable
{
var id: PersonAny
}
There is a corner case which is not covered by Luca Angeletti's solution.
For instance, if Cordinate's type is Double or [Double], Angeletti's solution will cause an error: "Expected to decode Double but found an array instead"
In this case, you have to use nested enum instead in Cordinate.
enum Cordinate: Decodable {
case double(Double), array([Cordinate])
init(from decoder: Decoder) throws {
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self = .double(double)
return
}
if let array = try? decoder.singleValueContainer().decode([Cordinate].self) {
self = .array(array)
return
}
throw CordinateError.missingValue
}
enum CordinateError: Error {
case missingValue
}
}
struct Geometry : Decodable {
let date : String?
let type : String?
let coordinates : [Cordinate]?
enum CodingKeys: String, CodingKey {
case date = "date"
case type = "type"
case coordinates = "coordinates"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
date = try values.decodeIfPresent(String.self, forKey: .date)
type = try values.decodeIfPresent(String.self, forKey: .type)
coordinates = try values.decodeIfPresent([Cordinate].self, forKey: .coordinates)
}
}