I am trying auto parsing an API with swift 4 codable. Some of the fields in the API are big integer which Codable does not support nor Swift 4.NSNumber is not supported by Codable and UInt64 is small for it to fit. I tried with a thrid party library and made my variable within the codable to that type,but that also did not work. I tried to to make a custom class or struct which will do the conversion with only one value in container but don't know how to make the container accept Big Int type or convert it to string.
My code is below like this. Is there any solution to it?
import Foundation
import BigNumber
class PersonalizationLean:Codable {
var hubId:String?
var appId:UInt8?
var nodeId:Int?
var name:String?
var ico:String?
var icoBase64:String?
var isBin:Bool?
var lastModifiedAt:Int?
var shouldShowInUi:Bool?
var applianceType:String?
var tags:[String]?
var placeId:PlaceIdCodable?
var roomId:String?
var id:String?
var key:String?
enum CodingKeys:String,CodingKey {
case hubId
case appId
case nodeId
case name
case ico
case icoBase64
case isBin
case lastModifiedAt
case shouldShowInUi
case applianceType
case tags
case placeId
case roomId
case id
case key
}
// required init(from decoder: Decoder) throws {
// do {
// let container = try decoder.container(keyedBy: CodingKeys.self)
// self.hubId = try container.decode(String.self, forKey: .hubId)
// self.appId = try container.decode(UInt8.self, forKey: .appId)
// self.nodeId = try container.decode(Int.self, forKey: .nodeId)
// self.name = try container.decode(String.self, forKey: .name)
// self.ico = try container.decode(String.self, forKey: .ico)
// self.icoBase64 = try container.decode(String.self, forKey: .icoBase64)
// self.isBin = try container.decode(Bool.self, forKey: .isBin)
// self.lastModifiedAt = try container.decode(Int.self, forKey: .lastModifiedAt)
// self.shouldShowInUi = try container.decode(Bool.self, forKey: .shouldShowInUi)
// self.applianceType = try container.decode(String.self,forKey: .applianceType)
// self.tags = try container.decode([String].self,forKey: .tags)
//
//
// }catch {
// print(error)
// }
// }
}
class PlaceIdCodable:Codable {
var placeId:String?
required init(from decoder:Decoder) throws {
do {
let container = try decoder.singleValueContainer()
let placeIdBig = try container.decode(BInt.self) //this gives error
}catch {
print(error)
}
}
}
The library I am using is BigNumber
Use built-in Decimal which derives from NSDecimalNumber. It adopts Codable
BInt do not conform to Codable OR Decodable
to use it here it should confirm to mentioned protocol
extension BInt: Decodable {
public init(from decoder: Decoder) throws {
// Initialization goes here
}
}
Related
I have gone through many articles but still could not find a best approach to tackle this situation . I am having different models , that are used to be returned on the basis of type of cell . What is the best approach to handle with Any data type (Any consists of more than three different data models ). See my code below
import Foundation
struct OverviewWorkout : Decodable {
enum WorkoutType: String, Codable {
case workout
case coach
case bodyArea
case challenge
case title
case group
case trainer
}
var type: WorkoutType
var data : Any
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(WorkoutType.self, forKey: .type)
switch type {
case .workout, .challenge:
data = try container.decode(Workout.self, forKey: .data)
case .coach:
data = try container.decode(CoachInstruction.self, forKey: .data)
case .bodyArea:
data = try container.decode([Workout].self, forKey: .data)
case .title:
data = try container.decode(Title.self, forKey: .data)
case .group:
data = try container.decode([Workout].self, forKey: .data)
// trainer data
case .trainer:
data = try container.decode([Trainer].self, forKey: .data)
}
}
private enum CodingKeys: String, CodingKey {
case type,data
}
}
extension OverviewWorkout {
struct Title: Codable {
let title: String
}
}
You can declare the Type enum with associated values as defined below:
struct OverviewWorkout : Decodable {
var type: WorkoutType
enum WorkoutType: String, Codable {
case workout(data: Workout)
case coach(data: CoachInstruction)
case bodyArea(data: [Workout])
case title(data: Title)
case group(data: [Workout])
case trainer(data: Trainer)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(WorkoutType.self, forKey: .type)
switch type {
case .workout:
let data = try container.decode(Workout.self, forKey: .data)
self = .workout(data: data)
case .trainer:
let data = try container.decode(Trainer.self, forKey: .data)
self = .trainer(data: data)
.
.
.
}
}
}
I was short of time so couldn't compile it but I hope you this will give you an idea. Additionally, Sharing a reference article for you. [:D You might have visited already]
anyone idea about to make GSMPlace as codable from googleplaces?
what I want is, store GMSPlace data(place and name) into UserDefaults and then retrieve from UserDefaults and store back to destinationPlace.
import Foundation
import RxSwift
import RxCocoa
import GooglePlaces
class SearchViewModel {
var destinationPlace: BehaviorRelay<GMSPlace?> = BehaviorRelay(value: nil)
var isValid: Bool {
if destinationPlace.value != nil {
return true
}
return false
}
}
I don't know any of the specifics of GooglePlaces or the object type GMSPlace.
But in Swift if you want to add something to a class you can create an extension, optionally add protocols to it and add functions. In your case it would look something like this:
extension GMSPlace: Codable {
enum CodingKeys: String, CodingKey {
case propertyName1
case propertyName2
...
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
propertyName1 = try container.decode(String.self, forKey: .propertyName1)
propertyName2 = try container.decode(Int.self, forKey: .propertyName2)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(propertyName1, forKey: .propertyName1)
try container.encode(propertyName2, forKey: .propertyName2)
}
}
You will have to figure out the specifics of what properties you need to include, what types they need to map to etc.
You should also open a ticket for them somewhere to add this to the SDK themselves if its causing an issue for you
I have a base model called Requirements and another more specific model called AccountRequirements.
When I try to read the currentDeadline property, if i use Requirements it works fine. If I use AccountRequirements it comes out as nil.
I do not understand why. I'm guessing it has to do somehow with the class. I always use struct in my models but since I can not inherit from a struct I'm using class here.
class Requirements: Codable {
var commonProperty: String
// works
var currentDeadline: Int?
enum CodingKeys: String, CodingKey {
case commonProperty = "common_property"
case currentDeadline = "current_deadline"
}
}
class AccountRequirements: Requirements {
// doesnt work
var currentDeadline: Int?
enum CodingKeys: String, CodingKey {
case currentDeadline = "current_deadline"
}
}
I decode data like this:
documentReference.addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
self.error = error!.localizedDescription
return
}
self.user = try? document.data(as: Requirements.self)
}
If you want to decode it as the subclass then you need to give that class and not the superclass to document.data(as:). You also need to implement init(from:) for the subclass to decode it properly
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
currentDeadline = try container.decodeIfPresent(Int.self, forKey: .currentDeadline)
try super.init(from: decoder)
}
Below is an example with a hardcoded json value
let data = """
{ "common_property": "Some text",
"current_deadline": 42
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Requirements.self, from: data)
print(type(of: result), result.commonProperty)
let result2 = try JSONDecoder().decode(AccountRequirements.self, from: data)
print(type(of: result2), result2.commonProperty, result2.currentDeadline ?? "")
} catch {
print(error)
}
Requirements Some text
AccountRequirements Some text 42
Here is the code I am using,
struct CreatePostResponseModel : Codable{
var transcodeId:String?
var id:String = ""
enum TopLevelCodingKeys: String, CodingKey {
case _transcode = "_transcode"
case _transcoder = "_transcoder"
}
enum CodingKeys:String, CodingKey{
case id = "_id"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcode) {
self.transcodeId = transcodeId
}else if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcoder) {
self.transcodeId = transcodeId
}
}
}
Here, transcodeId is decided by either _transcode or _transcoder.
But I want id and rest of the keys (not included here) to be automatically decoded. How can I do it ?
You need to manually parse all the keys once you implement init(from:) in the Codable type.
struct CreatePostResponseModel: Decodable {
var transcodeId: String?
var id: String
enum CodingKeys:String, CodingKey{
case id, transcode, transcoder
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcode) {
self.transcodeId = transcodeId
} else if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcoder) {
self.transcodeId = transcodeId
}
}
}
In the above code,
In case you only want to decode the JSON, there is no need to use Codable. Using Decodable is enough.
Using multiple enums for CodingKey seems unnecessary here. You can use a single enum CodingKeys.
If the property name and the key name is an exact match, there is no need to explicitly specify the rawValue of that case in enum CodingKeys. So, there is no requirement of "_transcode" and "_transcoder" rawValues in TopLevelCodingKeys.
Apart from all that, you can use keyDecodingStrategy as .convertFromSnakeCase to handle underscore notation (snake case notation), i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase //here.....
let model = try decoder.decode(CreatePostResponseModel.self, from: data)
print(model)
} catch {
print(error)
}
So, you don't need to explicitly handle all the snake-case keys. It'll be handled by the JSONDecoder on its own.
This can be one of the good solution for you wherever you want you can add multiple keys for one variable:
var transcodeId:String?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
transcodeId = container.getValueFromAvailableKey(codingKeys: [CodingKeys._transcoder,CodingKeys._transcode])
} catch {
print("Error reading config file: \(error.localizedDescription)")
}
}
extension KeyedDecodingContainerProtocol{
func getValueFromAvailableKey(codingKeys:[CodingKey])-> String?{
for key in codingKeys{
for keyPath in self.allKeys{
if key.stringValue == keyPath.stringValue{
do{
return try self.decodeIfPresent(String.self, forKey: keyPath)
} catch {
return nil
}
}
}
}
return nil
}
}
Hope it helps.
The compiler-generated init(from:) is all-or-nothing. You can’t have it decode some keys and “manually” decode others.
One way to use the compiler-generated init(from:) is by giving your struct both of the possible encoded properties, and make transcodeId a computed property:
struct CreatePostResponseModel: Codable {
var transcodeId: String? {
get { _transcode ?? _transcoder }
set { _transcode = newValue; _transcoder = nil }
}
var _transcode: String? = nil
var _transcoder: String? = nil
var id: String = “”
// other properties
}
I'm trying to make inherited data model in order to parse it with JSONDecoder.
class FirstClass : Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]
}
class SecondClass : FirstClass {
let secondClassProperty1: Int
let secondClassProperty2: Int
private enum CodingKeys : String, CodingKey {
case secondClassProperty1, secondClassProperty2
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
secondClassProperty1 = try container.decode(Int.self, forKey: .secondClassProperty1)
secondClassProperty2 = try container.decode(Int.self, forKey: .secondClassProperty2)
try super.init(from: decoder)
}
}
I use this JSON for FirstClass:
{
"firstClassProperty": 123,
"arrayOfInts": [
123
]
}
and this for SecondClass:
{
"firstClassProperty": {},
"secondClassProperty1": {},
"secondClassProperty2": {}
}
How can I get rid of arrayOfInts in my subclass but let it be in superclass if keyword final doesn't work in this case?
Here's Playground. Thanks for your answers!
A quick hack is to declare it as optional. For instance:
class FirstClas: Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]?
}
That would work around a missing arrayOfInts automatically.
Manual solution. Another solution is to implement the Decodable protocol yourself — as you did in SecondClass — and decode arrayOfInts using decodeIfPresent (and use a default value otherwise).
Superclass decoding. By the way, the recommended way to forward the Decoder to the superclass is by using superDecoder() method:
...
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
You can use like this :
class FirstClass : Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]?
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstClassProperty = try container.decode(Int.self, forKey: .firstClassProperty)
arrayOfInts = try container.decodeIfPresent([Int].self, forKey: .arrayOfInts)
}
}