Why is this JSON decode failing? - json

I have done JSON decoding before, but for some reason I can't figure this out. In the playground, it simply crashes with no explanation so I decided to put it into a single view project to trace the problem.
The initializer isn't being called at all. Tried decoding with the super class and got the same thing. Would love another pair of eyes on this.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = """
{"key": 5, "accountKey": "checking", "amount": 100000, "type": "deposit", "date": "2019-03-05T15:29:32Z", "locationKey", "Payroll", isReconciled: false}
"""
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let transaction = try? decoder.decode(BasicTransaction.self, from: jsonData)
print(transaction?.locationKey)
}
}
Here are the supporting definitions:
enum TransactionType: CaseIterable {
case purchase
case deposit
case ccPayment
}
extension TransactionType: RawRepresentable {
typealias RawValue = String
init(rawValue: Self.RawValue) {
switch rawValue {
case "deposit": self = .deposit
case "payment": self = .ccPayment
default: self = .purchase
}
}
var rawValue: String {
switch self {
case .purchase: return "purchase"
case .deposit: return "deposit"
case .ccPayment: return "payment"
}
}
}
class MoneyTransaction: Decodable {
var key = UUID().uuidString
var type = TransactionType.purchase
var accountKey = ""
var ccAccountKey: String? = nil
var recurringTransactionKey: String? = nil
var isRecurring: Bool { !(recurringTransactionKey ?? "").isEmpty }
var locationKey: String? = nil
var addressKey: String? = nil
var categoryKey: String? = nil
var note: String? = nil
var amount: Double = 0
private enum MoneyTransactionKey: String, CodingKey {
case key, type, accountKey, ccAccountKey, recurringTransactionKey, locationKey, addressKey, categoryKey, note, amount, isNew
}
required init(from decoder: Decoder) throws {
print("initializing MoneyTransaction")
let container = try decoder.container(keyedBy: MoneyTransactionKey.self)
key = try container.decode(String.self, forKey: .key)
accountKey = try container.decode(String.self, forKey: .accountKey)
let rawAmount = try container.decode(Int.self, forKey: .amount)
amount = Double(rawAmount / 100)
type = try TransactionType(rawValue: container.decode(String.self, forKey: .type))
recurringTransactionKey = try container.decodeIfPresent(String.self, forKey: .recurringTransactionKey)
locationKey = try container.decodeIfPresent(String.self, forKey: .locationKey)
categoryKey = try container.decodeIfPresent(String.self, forKey: .categoryKey)
note = try container.decodeIfPresent(String.self, forKey: .note)
ccAccountKey = try container.decodeIfPresent(String.self, forKey: .ccAccountKey)
addressKey = try container.decodeIfPresent(String.self, forKey: .addressKey)
}
}
class BasicTransaction: MoneyTransaction {
var date = Date()
var checkNumber: Int? = nil
var isReconciled = false
private enum BasicTransactionKey: String, CodingKey {
case date, checkNumber, isReconciled
}
required init(from decoder: Decoder) throws {
print("initializing BasicTransaction")
try super.init(from: decoder)
let container = try decoder.container(keyedBy: BasicTransactionKey.self)
date = try container.decode(Date.self, forKey: .date)
checkNumber = try container.decodeIfPresent(Int.self, forKey: .checkNumber)
isReconciled = try container.decode(Bool.self, forKey: .isReconciled)
}
}

The json is incorrect it's like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey", << key with no value
"Payroll", << key with no value
isReconciled: false << key with no ""
}
It would be like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey":"",
"Payroll":"",
"isReconciled": false
}
then BasicTransaction
class BasicTransaction: Codable {
let key: Int
let accountKey: String
let amount: Int
let type: String
let date: Date
let locationKey, payroll: String
let isReconciled: Bool
enum CodingKeys: String, CodingKey {
case key, accountKey, amount, type, date, locationKey
case payroll = "Payroll"
case isReconciled
}
init(key: Int, accountKey: String, amount: Int, type: String, date: Date, locationKey: String, payroll: String, isReconciled: Bool) {
self.key = key
self.accountKey = accountKey
self.amount = amount
self.type = type
self.date = date
self.locationKey = locationKey
self.payroll = payroll
self.isReconciled = isReconciled
}
}

Related

Decode JSON Double to CGFloat in Swift

EDIT: The Code works, the issue was with the imported data set, which did not classify empty fields as number, but as string instead.
My app needs to import values from a JSON file in a similar construct as shown below
[
{
"id": 1,
"string": "Text String",
"int": 6,
"cgfloat": 1.1,
}
]
Import:
id = try container.decode(Int.self, forKey: .id)
string = try container.decode(String.self, forKey: .string)
int = try container.decode(Int.self, forKey: .int)
I have no clue how I can read the value 1.1, neither Double nor Float, CGFloat, Int, nor String works here.
Do I need to change my data structure or is there a way to interpret this value properly in Swift?
this crashes the app:
let cgfloat = try container.decode(CGFloat.self, forKey: . cgfloat)
As requested the full code:
struct Monster: Codable, Comparable {
enum MonsterStatus: String, Identifiable, Codable, Hashable {
var id: Self {
return self
}
case Normal = "Normal"
case Attack = "Attack"
case Hit = "Hit"
case Defend = "Defend"
case Dead = "Dead"
}
enum MonsterType: String, Identifiable, Codable, Hashable {
var id: Self {
return self
}
case Undead = "Undead"
case Human = "Human"
case Dinosaur = "Dinosaur"
case Dwarf = "Dwarf"
case Elf = "Elf"
case Wisp = "Wisp"
case Ghost = "Ghost"
case Beast = "Beast"
case Snake = "Snake"
case Giant = "Giant"
case Demon = "Demon"
case Dragon = "Dragon"
case Error = "Error"
}
struct ImagesCatalog: Equatable, Codable {
var Normal: String
var Attack: String
var Defend: String
var Hit: String
var Dead: String
}
static func < (lhs: Monster, rhs: Monster) -> Bool {
return lhs.id < rhs.id
}
static func == (lhs: Monster, rhs: Monster) -> Bool {
return lhs.id == rhs.id
}
var id: Int
let name: String
let image: String = ""
var imagePrefix:String = ""
var strength: Int = 4
var life: Int = 1
var fleeChance: Int = 0
var isBoss: Bool = false
var flying: Bool = false
var mage: Bool = false
var venomous: Bool = false
var giant: Bool = false
var carnivore: Bool = false
var herbivore: Bool = false
var type: MonsterType
var location: HordeGameData.Location
var isFaceUp: Bool = false
var soundAppear: String = "skeletonWalk4.mp3"
var soundHit: String = "skeletonHit1.mp3"
var soundDefend: String = "skeletonEmerge1.mp3"
var soundAttack: String = "skeletonHit4.mp3"
var soundDead: String = "skeletonShatter1.mp3"
var playSound: Bool = true
let images: ImagesCatalog
var imagePic: String
var scaleFactor: CGFloat = 0.5
var active: Bool = false
var slash: Bool = false
var description: String = ""
var status: MonsterStatus = .Normal
func returnColor() -> Color {
if isFaceUp && isBoss {
return .red
} else {
return .red
}
}
func provideID() -> String {
return String(self.id)
}
mutating func playSoundToggle() {
self.playSound.toggle()
}
mutating func reduceStrengthBy(_ amount: Int) {
if strength > amount {
self.strength -= amount
}
}
mutating func defineImage(status: MonsterStatus, slash: Bool) {
self.status = status
switch status {
case .Normal: imagePic = images.Normal
if playSound {
Sound.play(file: self.soundAppear)
}
case .Attack: imagePic = images.Attack
if playSound {
Sound.play(file: self.soundAttack)
}
case .Defend: imagePic = images.Defend
if playSound {
Sound.play(file: self.soundDefend)
}
case .Hit : imagePic = images.Hit
if playSound {
Sound.play(file: self.soundHit)
}
case .Dead : imagePic = images.Dead
if playSound {
Sound.play(file: self.soundDead)
}
}
self.slash = slash
}
mutating func nextImage() {
switch self.status {
case .Normal: defineImage(status: .Attack, slash: false)
case .Attack: defineImage(status: .Defend, slash: false)
case .Defend: defineImage(status: .Hit, slash: false)
case .Hit : defineImage(status: .Dead, slash: false)
case .Dead : defineImage(status: .Normal, slash: false)
}
}
mutating func setID(_ id: Int) {
self.id = id
}
mutating func reduceLife() {
life -= 1
}
mutating func addLife() {
life += 1
}
func provideLife() -> String {
var lifeString = ""
for _ in 0..<life {
lifeString.append("💜")
}
return lifeString
}
///
private enum CodingKeys: String, CodingKey {
case id
case name
case imagePrefix
case strength
case life
case fleeChance
case isBoss
case flying
case mage
case venomous
case giant
case carnivore
case herbivore
case type
case location
case soundAppear
case soundHit
case soundDefend
case soundAttack
case soundDead
case images
case scaleFactor
case description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
imagePrefix = try container.decode(String.self, forKey: .imagePrefix)
strength = try container.decode(Int.self, forKey: .strength)
life = try container.decode(Int.self, forKey: .life)
fleeChance = try container.decode(Int.self, forKey: .fleeChance)
isBoss = try container.decode(Bool.self, forKey: .isBoss)
flying = try container.decode(Bool.self, forKey: .flying)
mage = try container.decode(Bool.self, forKey: .mage)
venomous = try container.decode(Bool.self, forKey: .venomous)
giant = try container.decode(Bool.self, forKey: .giant)
carnivore = try container.decode(Bool.self, forKey: .carnivore)
herbivore = try container.decode(Bool.self, forKey: .herbivore)
let monsterTypeValue = try container.decode(String.self, forKey: .type)
type = MonsterType(rawValue: monsterTypeValue) ?? .Error
let locationValue = try container.decode(String.self, forKey: .location)
location = HordeGameData.Location(rawValue: locationValue) ?? .Error
soundAppear = try container.decode(String.self, forKey: .soundAppear)
soundHit = try container.decode(String.self, forKey: .soundHit)
soundDefend = try container.decode(String.self, forKey: .soundDefend)
soundAttack = try container.decode(String.self, forKey: .soundAttack)
soundDead = try container.decode(String.self, forKey: .soundDead)
images = .init(Normal: imagePrefix + "Normal",
Attack: imagePrefix + "Attack",
Defend: imagePrefix + "Defend",
Hit: imagePrefix + "Hit",
Dead: imagePrefix + "Dead")
imagePic = imagePrefix + "Normal"
// let stringScale = try container.decode(CGFloat.self, forKey: .scaleFactor)
// print(stringScale)
// scaleFactor = stringScale.CGFloatValue() ?? 0.5
description = try container.decode(String.self, forKey: .description)
print("Monster \(id): \(name) imported")
}
}
Decode funtion
class MonsterInventory {
static let shared = MonsterInventory()
var staticItems: [Monster] = []
private init() {
self.parseMonsterJson()
}
private func parseMonsterJson() {
guard let monsterJsonFileUrl = Bundle.main.url(forResource: "Monsters", withExtension: "json") else {
fatalError("Unable to find file")
}
do {
let content = try Data(contentsOf: monsterJsonFileUrl)
let jsonDecoder = JSONDecoder()
staticItems = try jsonDecoder.decode([Monster].self, from: content)
} catch let error {
print(error.localizedDescription)
staticItems = []
}
}
func fetchMonsterWithID(_ id: Int) -> Monster {
return staticItems.filter{$0.id == id}.first ?? staticItems.filter{$0.id == 10}.first!
}
func fetchMonsterWithName(_ name: String) -> Monster {
return staticItems.filter{$0.name == name}.first ?? staticItems.filter{$0.id == 10}.first!
}
}
here is an excerpt of the JSON:
[
{
"id": 1,
"name": "Skeleton Warrior",
"strength": 6,
"life": 1,
"fleeChance": 0,
"isBoss": false,
"flying": false,
"mage": false,
"venomous": false,
"giant": false,
"carnivore": false,
"herbivore": false,
"type": "Undead",
"location": "Plains",
"soundAppear": "skeletonWalk4.mp4",
"soundHit": "skeletonHit1.mp3",
"soundDefend": "swordSwoosh2.m4a",
"soundAttack": "swordSwoosh1.m4a",
"soundDead": "skeletonShatter2.mp3",
"imagePrefix": "SkeletonWarrior",
"imageNormal": "SkeletonWarriorNormal",
"imageAttack": "SkeletonWarriorAttack",
"imageDefend": "SkeletonWarriorDefend",
"imageHit": "SkeletonWarriorHit",
"imageDead": "SkeletonWarriorDead",
"scaleFactor": 1.1,
"description": "A weak skeleton fighter."
},
...
]
I'm unsure why are you decoding key by key... I think you should create a struct unless you really need to decode key by key (like
flanker mentioned in a comment to this answer).
If you do need to go key by key you need to provide more details on how are you decoding the JSON
This is the struct that fits your JSON
struct Elm: Codable {
var id: Int
var string: String
var int: Int
var cgfloat: CGFloat // also can be Double and Float
}
You can also change the variables names to follow the camelCase naming.
struct Elm: Codable {
var id: Int
var string: String
var int: Int
var cgFloat: Double
private enum CodingKeys : String, CodingKey {
case id, string, int, cgFloat = "cgfloat"
}
}
This is how you can decode the object.
let decoder = JSONDecoder()
let obj = try! decoder.decode([Elm].self, from: json.data(using: .utf8)!)
In this case the root of your JSON is an array so for that case instead of using Elm.self we use [Elm].self to decode to the array
This way Swift maps the JSON Key to the matching variable name (or using the Coding Keys like I demonstrated in the second struct)
This was tested in Playgrounds
UPDATE 1
Based on your updated question I now see why the individual decodings, but assuming that the key that you are having problems with is the scaleFactor which is the only Float number in the sample the code you provided works with no issues at all.
// [...]
// uncommented your code
scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor)
let _scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor) // both work
description = try container.decode(String.self, forKey: .description)
print("Monster \(id): \(name) imported")
Now you can double check if the reading of the file is being done correctly (if you can read the rest of the file it probably is)

Swift Json Decoder, unable to decode nested objects array

I have the following json which contains a user, and the positions they hold in office. I can decode the user disregarding the positions just fine, but when I try to decode the array of positions it fails. I have try making seperate enums for the position and using nested containers but nothing is working.
The JSON
[
{
"firstName": "jon",
"id": "CE30BCF5-1335-4767-BD20-53F4EDE950CD",
"title": "citizen",
"username": "jon1",
"positions": [
{
"id": "3332BC0E-0DA5-4B90-A836-4CF91B872B05",
"name": "mayor",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
},
{
"name": "governor",
"id": "199761E2-BCC2-4EC9-93CE-C4E7F4FB9277",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
}
],
"imageURLString": "jon1image",
"civicRating": 5,
"lastName": "samson"
}
]
User Model
import Foundation
struct New_User: Codable {
var id: UUID?
var username: String
var firstName: String
var lastName: String
var title: String
var imageURLString: String?
var civicRating: Double
var positions: [New_Position] = []
private enum UserKeys: String, CodingKey {
case id
case username
case firstName
case lastName
case imageURLString
case civicRating
case title
case positions
}
}
extension New_User {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: UserKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username)
self.title = try container.decode(String.self, forKey: .title)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.imageURLString = try container.decode(String?.self, forKey: .imageURLString)
self.civicRating = try container.decode(Double.self, forKey: .civicRating)
self.positions = try container.decode([New_Position].self, forKey: .positions)
}
}
Position Model
import Foundation
struct New_Position: Codable {
var id: UUID?
var jurisdiction: New_Jurisdiction
var name: String
private enum PositionKeys: String, CodingKey {
case id
case jurisdiction
case name
}
}
extension New_Position {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: PositionKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.jurisdiction = try container.decode(New_Jurisdiction.self, forKey: .jurisdiction)
print("decoded")
}
}
Jurisdiction Model
import Foundation
struct New_Jurisdiction: Codable {
var id: UUID?
var name: String
var scope: String
private enum JurisdictionKeys: String, CodingKey {
case id
case name
case scope
}
}
extension New_Jurisdiction {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: JurisdictionKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.scope = try container.decode(String.self, forKey: .scope)
print("decoded")
}
}

ignore null object in array when parse with Codable swift

i'm parsing this API with swift Codable
"total": 7,
"searchResult": [
null,
{
"name": "joe"
"family": "adam"
},
null,
{
"name": "martin"
"family": "lavrix"
},
{
"name": "sarah"
"family": "mia"
},
null,
{
"name": "ali"
"family": "abraham"
}
]
with this PaginationModel:
class PaginationModel<T: Codable>: Codable {
var total: Int?
var data: T?
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = try container.decodeIfPresent(T.self, forKey: .data)
}
}
and User Model:
struct User: Codable {
var name: String?
var family: String?
}
i call jsonDecoder like this to parse API json:
let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: json)
now my problem is null in searchResult Array. it parsed correctly and when i access to data in paginationModel i found null in array.
how can i ignore all null when parsing API, and result will be an array without any null
In the first place, I would advise to always consider PaginationModel to be composed from arrays. You don't have to pass [User] as the generic type, you can just pass User. Then the parser can use the knowledge that it parses arrays and handle null automatically:
class PaginationModel<T: Codable>: Codable {
var total: Int?
var data: [T]?
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = (try container.decodeIfPresent([T?].self, forKey: .data))?.compactMap { $0 }
}
}
You might want to remove optionals here and use some default values instead:
class PaginationModel<T: Codable>: Codable {
var total: Int = 0
var data: [T] = []
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = (try container.decodeIfPresent(Int.self, forKey: .total)) ?? 0
self.data = ((try container.decodeIfPresent([T?].self, forKey: .data)) ?? []).compactMap { $0 }
}
}
Simple solution, filter data after decoding
let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: data)
responseObject.data = responseObject.data?.filter{$0 != nil}
You may add an array type check within decode :
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = try container.decodeIfPresent(T.self, forKey: .data)
//add the following:
if let array = self.data as? Array<Any?> {
self.data = ( array.compactMap{$0} as? T)
}
}
Note, you can just define the decodable variable that may be null/nil as [Float?] (or whatever type), with the optional '?' inside the array brackets.

Parsing JSON with wildcard keys

I'm parsing a poorly designed JSON structure in which I can expect to find values being reused as keys pointing to further data. Something like this
{"modificationDate" : "..."
"type" : "...",
"version" : 2,
"manufacturer": "<WILDCARD-ID>"
"<WILDCARD-ID>": { /* known structure */ } }
WILDCARD-ID can be just about anything at runtime, so I can't map it to a field in a struct somewhere at compile time. But once I dereference that field, its value has known structure, at which point I can follow the usual procedure for mapping JSON to structs.
I've found myself going down this path
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
let manDict = json["manufacturer"]
let data = NSKeyedArchiver.archivedData(withRootObject: manDict)
// now you have data!
but this seems very circuitous, which makes me think that maybe there's a cleaner way of accomplishing this?
You can use custom keys with Decodable, like so:
let json = """
{
"modificationDate" : "...",
"type" : "...",
"version" : 2,
"manufacturer": "<WILDCARD-ID>",
"<WILDCARD-ID>": {
"foo": 1
}
}
""".data(using: .utf8)!
struct InnerStruct: Decodable { // just as an example
let foo: Int
}
struct Example: Decodable {
let modificationDate: String
let type: String
let version: Int
let manufacturer: String
let innerData: [String: InnerStruct]
enum CodingKeys: String, CodingKey {
case modificationDate, type, version, manufacturer
}
struct CustomKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = "\(intValue)";
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
// extract all known properties
let container = try decoder.container(keyedBy: CodingKeys.self)
self.modificationDate = try container.decode(String.self, forKey: .modificationDate)
self.type = try container.decode(String.self, forKey: .type)
self.version = try container.decode(Int.self, forKey: .version)
self.manufacturer = try container.decode(String.self, forKey: .manufacturer)
// get the inner structs with the unknown key(s)
var inner = [String: InnerStruct]()
let customContainer = try decoder.container(keyedBy: CustomKey.self)
for key in customContainer.allKeys {
if let innerValue = try? customContainer.decode(InnerStruct.self, forKey: key) {
inner[key.stringValue] = innerValue
}
}
self.innerData = inner
}
}
do {
let example = try JSONDecoder().decode(Example.self, from: json)
print(example)
}
You can capture the idea of "a specific, but currently unknown key" in a struct:
struct StringKey: CodingKey {
static let modificationDate = StringKey("modificationDate")
static let type = StringKey("type")
static let version = StringKey("version")
static let manufacturer = StringKey("manufacturer")
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.init(stringValue) }
init?(intValue: Int) { return nil }
init(_ stringValue: String) { self.stringValue = stringValue }
}
With that, decoding is straightforward, and only decodes the structure that matches the key:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringKey.self)
modificationDate = try container.decode(String.self, forKey: .modificationDate)
type = try container.decode(String.self, forKey: .type)
version = try container.decode(Int.self, forKey: .version)
manufacturer = try container.decode(String.self, forKey: .manufacturer)
// Decode the specific key that was identified by `manufacturer`,
// and fail if it's missing
manufacturerData = try container.decode(ManufacturerData.self,
forKey: StringKey(manufacturer))
}

Decoding two different JSON responses in one model class using Codable

Based on the requirement I got two different kinds of response from api. That is
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
another response
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
Here both json values are decoding in same model class. Kindly help me to resolve this concern.
is it is possible to set value in single variable like qty and quantity both are stored in same variable based on key param availability
Here's an approach that lets you have only one property in your code, instead of two Optionals:
Define a struct that contains all the properties you need, with the names that you'd like to use in your code. Then, define two CodingKey enums that map those properties to the two different JSON formats and implement a custom initializer:
let json1 = """
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
""".data(using: .utf8)!
let json2 = """
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
""".data(using: .utf8)!
struct DecodingError: Error {}
struct Model: Decodable {
let storename: String
let quantity: Int
let id: Int
let price: Double
enum CodingKeys1: String, CodingKey {
case storename = "shopname"
case quantity
case id
case price
}
enum CodingKeys2: String, CodingKey {
case storename
case quantity = "qty"
case id
case price = "amount"
}
init(from decoder: Decoder) throws {
let container1 = try decoder.container(keyedBy: CodingKeys1.self)
let container2 = try decoder.container(keyedBy: CodingKeys2.self)
if let storename = try container1.decodeIfPresent(String.self, forKey: CodingKeys1.storename) {
self.storename = storename
self.quantity = try container1.decode(Int.self, forKey: CodingKeys1.quantity)
self.id = try container1.decode(Int.self, forKey: CodingKeys1.id)
self.price = try container1.decode(Double.self, forKey: CodingKeys1.price)
} else if let storename = try container2.decodeIfPresent(String.self, forKey: CodingKeys2.storename) {
self.storename = storename
self.quantity = try container2.decode(Int.self, forKey: CodingKeys2.quantity)
self.id = try container2.decode(Int.self, forKey: CodingKeys2.id)
self.price = try container2.decode(Double.self, forKey: CodingKeys2.price)
} else {
throw DecodingError()
}
}
}
do {
let j1 = try JSONDecoder().decode(Model.self, from: json1)
print(j1)
let j2 = try JSONDecoder().decode(Model.self, from: json2)
print(j2)
} catch {
print(error)
}
Handling different key names in single model
Below are two sample json(dictionaries) that have some common keys (one, two) and a few different keys (which serve the same purpose of error).
Sample json:
let error_json:[String: Any] = [
"error_code": 404, //different
"error_message": "file not found", //different
"one":1, //common
"two":2 //common
]
let failure_json:[String: Any] = [
"failure_code": 404, //different
"failure_message": "file not found", //different
"one":1, //common
"two":2 //common
]
CommonModel
struct CommonModel : Decodable {
var code: Int?
var message: String?
var one:Int //common
var two:Int? //common
private enum CodingKeys: String, CodingKey{ //common
case one, two
}
private enum Error_CodingKeys : String, CodingKey {
case code = "error_code", message = "error_message"
}
private enum Failure_CodingKeys : String, CodingKey {
case code = "failure_code", message = "failure_message"
}
init(from decoder: Decoder) throws {
let commonValues = try decoder.container(keyedBy: CodingKeys.self)
let errors = try decoder.container(keyedBy: Error_CodingKeys.self)
let failures = try decoder.container(keyedBy: Failure_CodingKeys.self)
///common
self.one = try commonValues.decodeIfPresent(Int.self, forKey: .one)!
self.two = try commonValues.decodeIfPresent(Int.self, forKey: .two)
/// different
if errors.allKeys.count > 0{
self.code = try errors.decodeIfPresent(Int.self, forKey: .code)
self.message = try errors.decodeIfPresent(String.self, forKey: .message)
}
if failures.allKeys.count > 0{
self.code = try failures.decodeIfPresent(Int.self, forKey: .code)
self.message = try failures.decodeIfPresent(String.self, forKey: .message)
}
}
}
Below extension will help you to convert your dictionary to data.
public extension Decodable {
init(from: Any) throws {
let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: data)
}
}
Testing
public func Test_codeble(){
do {
let err_obj = try CommonModel(from: error_json)
print(err_obj)
let failed_obj = try CommonModel(from: failure_json)
print(failed_obj)
}catch let error {
print(error.localizedDescription)
}
}
Use like
struct modelClass : Codable {
let amount : Float?
let id : Int?
let price : Float?
let qty : Int?
let quantity : Int?
let shopname : String?
let storename : String?
enum CodingKeys: String, CodingKey {
case amount = "amount"
case id = "id"
case price = "price"
case qty = "qty"
case quantity = "quantity"
case shopname = "shopname"
case storename = "storename"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
amount = try values.decodeIfPresent(Float.self, forKey: .amount)
id = try values.decodeIfPresent(Int.self, forKey: .id)
price = try values.decodeIfPresent(Float.self, forKey: .price)
qty = try values.decodeIfPresent(Int.self, forKey: .qty)
quantity = try values.decodeIfPresent(Int.self, forKey: .quantity)
shopname = try values.decodeIfPresent(String.self, forKey: .shopname)
storename = try values.decodeIfPresent(String.self, forKey: .storename)
}
}