Swift 4 JSONDecoder optional variable - swift

I have a Codable struct myObj:
public struct VIO: Codable {
let id:Int?;
...
var par1:Bool = false; //default to avoid error in parsing
var par2:Bool = false;
}
When I do receive JSON, I don't have par1 and par2 since these variables are optional. During parsing I get an error:keyNotFound(CodingKeys(stringValue: \"par1\", intValue: nil)
How to solve this?

If you have local variables you have to specify the CodingKeys
public struct VIO: Codable {
private enum CodingKeys : String, CodingKey { case id }
let id:Int?
...
var par1:Bool = false
var par2:Bool = false
}
Edit:
If par1 and par2 should be also decoded optionally you have to write a custom initializer
private enum CodingKeys : String, CodingKey { case id, par1, par2 }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
par1 = try container.decodeIfPresent(Bool.self, forKey: .par1)
par2 = try container.decodeIfPresent(Bool.self, forKey: .par2)
}
This is Swift: No trailing semicolons

Related

How to manually decode an array of items conforming to a custom protocol?

I am trying to decode an array of items logicBlocks that conform to a custom protocol LogicBlock.
struct MyModel: Codable {
var var1: String
var var2: Int
var logicBlocks: [LogicBlock]
}
I've looked at this question which asks how to decode an array of items, however I have a protocol which contains an enum like this:
enum LogicBlockTypeID: Int, Codable {
case a = 500
case b = 501
case c = 502
}
protocol LogicBlock: Codable {
var typeID: LogicBlockTypeID { get }
}
and structs implement LogicBlock like this:
struct Struct1: LogicBlock {
var someVar = 32
var typeID = .a
}
struct Struct2: LogicBlock {
var someString = "hello"
var typeID = .b
}
struct Struct2: LogicBlock {
var typeID = .c
}
I have attempted to decode MyModel by using init(from decoder: Decoder)
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
enum codingKeys: String, CodingKey {
...
case .logicBlocks
}
init(from decoder: Decoder) {
...
var codeContainer = try values.nestedUnkeyedContainer(forKey: .code)
var parsedCode = [LogicBlock]()
while !codeContainer.isAtEnd {
let nestedDecoder = try codeContainer.superDecoder()
let block = try Program.decodeLogicBlock(from: nestedDecoder)
parsedCode.append(block)
}
}
private static func decodeLogicBlock(from decoder: Decoder) throws -> LogicBlock {
let values = try decoder.container(keyedBy: LogicBlockTypeID.self)
// SOMETHING TO DISTINGUISH WHAT TYPE WE HAVE
return ...
}
How are we able to know the type of object that we are decoding here?
You can implement a concrete type, i.e AnyLogicBlock that would expose two variables let typeID: LogicBlockTypeID and let wrapped: LogicBlock
Here is a simplified example:
enum TypeID: String, Codable {
case a, b
}
protocol MyType {
var type: TypeID { get }
}
struct MyConreteTypeA: MyType {
var type: TypeID
var someVar: Int
}
struct MyConreteTypeB: MyType {
var type: TypeID
var someString: String
}
struct AnyType: MyType, Decodable {
private enum CodingKeys: String, CodingKey {
case type, someVar, someString
}
let type: TypeID
let wrapped: MyType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(TypeID.self, forKey: .type)
switch type {
case .a:
wrapped = MyConreteTypeA(type: type, someVar: try container.decode(Int.self, forKey: .someVar))
case .b:
wrapped = MyConreteTypeB(type: type, someString: try container.decode(String.self, forKey: .someString))
}
}
}
var json = #"""
[
{ "type": "a", "someVar": 1 },
{ "type": "b", "someString": "test" }
]
"""#
let result = try! JSONDecoder().decode([AnyType].self, from: json.data(using: .utf8)!)
for item in result {
switch item.type {
case .a:
let casted = item.wrapped as! MyConreteTypeA
print("MyConreteTypeA: \(casted)")
case .b:
let casted = item.wrapped as! MyConreteTypeB
print("MyConreteTypeB: \(casted)")
}
}
Alternatively you can implement Decodable for concrete types and delegate decoding to them after determining the expected type based on type ID property.

How to write the codable in generic format [duplicate]

This question already has answers here:
What Is Preventing My Conversion From String to Int When Decoding Using Swift 4’s Codable?
(2 answers)
Closed 2 years ago.
I have understand how to make the codable wrapper class for services response structure.
But some times in server side the attribute value varies It may Int Or String.
Example
struct ResponseDataModel : Codable{
let data : DataClass?
enum CodingKey: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throw {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass.self, forKey:.data)
}
}
struct DataClass : Codable{
let id : Int
let name : String?
let age : Int?
enum CodingKey: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throw {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(Int.self, forKey:.it)
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
I would like to use generic way if id int string no matter what its it should bind to my controller with id value data.
let id : <T>
How to write the codable in generic formate.
You can do that using the following model:
struct ResponseDataModel<T: Codable>: Codable{
let data : DataClass<T>?
enum CodingKeys: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass<T>.self, forKey:.data)
}
}
struct DataClass<T: Codable>: Codable {
let id: T?
let name: String?
let age: Int?
enum CodingKeys: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(T.self, forKey:.id)
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
However, you should always known the type of the id property when you call decode(_:from:) function of JSONDecoder like this:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(ResponseDataModel<Int>.self, from: data)
print(decoded)
} catch {
print(error)
}
Or you can use the following model to always map the id as Int, even if your server sends it as String:
struct ResponseDataModel: Codable{
let data : DataClass?
enum CodingKeys: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass.self, forKey:.data)
}
}
struct DataClass: Codable {
let id: Int?
let name: String?
let age: Int?
enum CodingKeys: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try values.decodeIfPresent(Int.self, forKey:.id)
} catch DecodingError.typeMismatch {
if let idString = try values.decodeIfPresent(String.self, forKey:.id) {
id = Int(idString)
} else {
id = nil
}
}
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
First of all, here are some key points to take case when using Codable for parsing.
There is no need to every time implement enum CodingKeys if the property names and keys have exactly same name.
Also, no need to implement init(from:) if there no specific parsing requirements. Codable will handle all the parsing automatically if the models are written correctly as per the format.
So, with the above 2 improvements your ResponseDataModel looks like,
struct ResponseDataModel : Codable{
let data: DataClass?
}
Now, for DataClass you simply need to add an if-else condition to handle the Int and String cases. Implementing generics is not needed here.
Use String or Int as the type for id. And add the conditions accordingly. In the below code, I'm using id as String.
struct DataClass : Codable {
let id : String //here....
let name : String?
let age : Int?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
age = try values.decodeIfPresent(Int.self, forKey: .age)
if let id = try? values.decode(Int.self, forKey: .id) {
self.id = String(id)
} else {
self.id = try values.decode(String.self, forKey:.id)
}
}
}
As per #Joakim Danielson example provided, you can reach desired result by attempting to decode value for each type.
struct Response: Decodable {
let id: String
let name: String?
let age: Int?
private enum CodingKeys: String, CodingKey {
case data
}
private enum NestedCodingKeys: String, CodingKey {
case id
case name
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let nestedContainer = try container.nestedContainer(
keyedBy: NestedCodingKeys.self,
forKey: .data
)
if let id = try? nestedContainer.decode(Int.self, forKey: .id) {
self.id = String(id)
} else {
id = try nestedContainer.decode(String.self, forKey: .id)
}
name = try nestedContainer.decodeIfPresent(String.self, forKey: .name)
age = try nestedContainer.decodeIfPresent(Int.self, forKey: .age)
}
}
As #gcharita illustrated you can also catch DecodingError, but do-catch statement for decode(_:forKey:) would act only as an early exit, since it throws one of the following errors - typeMismatch, keyNotFound or valueNotFound for that particular key-value pair.

Decoding and converting string to double in codable

How can I decode the following JSON to a Codable object in Swift?
{"USD":"12.555", "EUR":"11.555"}
Here's the struct i'm using:
struct Prices: Codable {
var USD: Double
var EUR: Double
private enum CodingKeys: String, CodingKey {
case USD = "USD"
case EUR = "EUR"
}
init() {
self.USD = 0.0
self.EUR = 0.0
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.USD = try container.decode(Double.self, forKey: .USD)
self.EUR = try container.decode(Double.self, forKey: .EUR)
}
}
The error I'm getting is
Error typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "USD", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
I think your struct is incorrect and will be hard to maintain if you want to download more currency rates so I suggest a different approach. First create a struct for holding a currency rate
struct CurrencyRate {
let currency: String
let rate: Decimal?
}
Then decode the json as a dictionary and use map to convert it into an array of CurrencyRate
var rates = [CurrencyRate]()
do {
let result = try JSONDecoder().decode([String: String].self, from: json)
rates = result.map { CurrencyRate(currency: $0.key, rate: Decimal(string: $0.value))}
} catch {
print(error)
}
Two notes about CurrencyRate
You have two currencies in a rate so normally you also have another property named baseCurrency or otherCurrency or something similar but if that other currency always is the same you can omit it.
Depending on your use case it might also be a good idea to make a new type for currency properties, Currency
The values you're getting are Strings, so your struct should simply be
struct Prices: Codable {
let USD: String
let EUR: String
}
Depending you how you inted to use those price values, converting them to double may be inadvisable, because floating-point math is weird and Decimals may be better suited.
You have to decode the values as String and then cast it to Double. So, either Cast String to Double and provide a default value, like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.USD = Double(try container.decode(String.self, forKey: .USD)) ?? 0
self.EUR = Double(try container.decode(String.self, forKey: .EUR)) ?? 0
}
Or make the properties optional and remove the default values:
struct Prices: Codable {
var USD, EUR: Double?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.USD = Double(try container.decode(String.self, forKey: .USD))
self.EUR = Double(try container.decode(String.self, forKey: .EUR))
}
Note: Also, you don't need CodingKeys as the property names are the same as keys in the data.
Alternatively, you can use the computed property method, like this:
struct Prices: Codable {
var USDString: String
var EURString: String
private enum CodingKeys: String, CodingKey {
case USDString = "USD"
case EURString = "EUR"
}
var USD: Double? { Double(USDString) }
var EUR: Double? { Double(EURString) }
}

Swift Generic does not show nil

I have following general structure where data can be anyother codable object
struct GeneralResponse<T:Codable>: Codable {
let message: String
let status: Bool
let data: T?
enum CodingKeys: String, CodingKey {
case message = "Message"
case status = "Status"
case data = "Data"
}
}
I have Following Like response codable class which will be used as data in GeneralResponse
class ImgLike: Codable {
let id: Int?
let imageID, user: String?
#available(*, deprecated, message: "Do not use.")
private init() {
fatalError("Swift 4.1")
}
enum CodingKeys: String, CodingKey {
case id = "ID"
case imageID = "ImageID"
case user = "User"
}
}
Question 1 : When the token expires on API, The response data is empty {} still It show ImgLike object with all nil properties. Why it not show data to be nil ?
Then If I check object?.data == nil it is showing false !! So I need to check each property
Question 2 : In ImgLike If I am using custom encode function. GeneralResponse not parsed with ImgLike not parsed it shows error in catch statement
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
imageID = try values.decode(String.self, forKey: .imageID)
user = try values.decode(String.self, forKey: .user)
do {
id = Int(try values.decode(String.self, forKey: .id))
} catch {
id = try values.decode(Int.self, forKey: .id)
}
}
The equivalents of Swift-nil are JSON-null and JSON-not-set. {} is a valid dictionary in JSON and so not Swift-nil.
I guess that you mean that you get an error incase you use the custom decoder function? That‘s expected since the default decoder uses decodeIfPresent instead of decode to decode optionals since they are allowed not to be set.
And since you decode an empty dictionary {} none of the values are present/set.
Counting keys in dict to avoid decoding from JSON-{}
This CodingKey-struct accepts every key it gets.
fileprivate struct AllKeysAllowed: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.intValue = intValue
stringValue = "\(intValue)"
}
}
struct GeneralResponse<T:Codable>: Decodable {
let message: String
let status: Bool
let data: T?
enum CodingKeys: String, CodingKey {
case message = "Message"
case status = "Status"
case data = "Data"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
message = try container.decode(String.self, forKey: .message)
status = try container.decode(Bool.self, forKey: .status)
Decode .data to a container which had all keys accepted.
Then the number of keys in the JSON-dictionary is readable with dataContainer.allKeys.count.
let dataContainer = try container.nestedContainer(keyedBy: AllKeysAllowed.self, forKey: .data)
if dataContainer.allKeys.count != 0 {
data = try container.decode(T.self, forKey: .data)
} else {
data = nil
}
}
}
Note that the default Codable implementation uses decodeIfPresent instead of decode. decodeIfPresent will not throw an error even if the key is not present in the JSON. It will simply return nil. So an empty JSON dictionary has no KVPs, so all the properties are set to nil.
In your custom implementation of Codable, you are using decode, which will throw an error if the key is not found.
The reason why object?.data != nil is because object?.data is a ImgLike???. You are wrapping an optional in an optional in an optional. I see that the type of object is GeneralResponse<ImgLike?>?. This will make data's type be ImgLike??. I don't think this is your intention. You probably intended to use GeneralRepsonse<ImgLike>. You might have forgotten to unwrap an optional somewhere. You also need to unwrap the outermost optional:
if let nonNilObject = object {
// nonNilObject.data is of type ImgLike?
}
As already mentioned the decoder does not treat an empty dictionary as nil.
You can add this functionality in a generic way with a tiny protocol and an extension of KeyedDecodingContainer
public protocol EmptyDictionaryRepresentable {
associatedtype CodingKeys : RawRepresentable where CodingKeys.RawValue == String
associatedtype CodingKeyType: CodingKey = Self.CodingKeys
}
extension KeyedDecodingContainer {
public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T?
where T : Decodable & EmptyDictionaryRepresentable
{
guard contains(key) else { return nil }
let container = try nestedContainer(keyedBy: type.CodingKeyType.self, forKey: key)
return container.allKeys.isEmpty ? nil : try decode(T.self, forKey: key)
}
}
Just add EmptyDictionaryRepresentable conformance to ImgLike, the associated types are inferred.
class ImgLike: Codable, EmptyDictionaryRepresentable {
The properties in ImgLike could be even declared as non-optional

Convert nil to empty string using JSONDecoder and Swift

I am using Swift 4 and JSONDecoder. I have the following structure:
struct Customer: Codable {
var id: Int!
var cnum: String!
var cname: String!
}
Note: the fields cannot be made optional.
Now I have a JSON string:
[
{
"id": 1,
"cnum": "200",
"cname": "Bob Smith"
},
{
"id": 2,
"cnum": "201",
"cname": null
}
]
And to decode it, I use the following:
let decoder = JSONDecoder()
let customers = try decoder.decode([Customer].self, from: json)
Everything works fine except the null data gets converted to nil. My question is, what would be the easiest way to convert incoming nil to an empty string ("")?
I would like to do this with the minimum amount of code but I'm not sure about the correct approach and at what point can the nil be converted to an empty string. Thank you beforehand.
You can use backing ivars:
struct Customer: Codable {
var id: Int
var cnum: String {
get { _cnum ?? "" }
set { _cnum = newValue }
}
var cname: String {
get { _cname ?? "" }
set { _cname = newValue }
}
private var _cnum: String?
private var _cname: String?
private enum CodingKeys: String, CodingKey {
case id, _cnum = "cnum", _cname = "cname"
}
}
Due to the custom CodingKeys, the JSON decoder will actually decode to _cnum and _cname, which are optional strings. We convert nil to empty string in the property getters.
You can use decodeIfPresent method.
struct Source : Codable {
let id : String?
enum CodingKeys: String, CodingKey {
case id = "id"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? "Default value pass"
}
}
You should make a computed variable that will have the original value if it's not nil and an empty string if it is.
var cnameNotNil: String {
return cname ?? ""
}
The usual way is to write an initializer which handles the custom behavior. The null value is caught in an extra do - catch block.
struct Customer: Codable {
var id: Int
var cnum: String
var cname: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
cnum = try container.decode(String.self, forKey: .cnum)
do { cname = try container.decode(String.self, forKey: .cname) }
catch { cname = "" }
}
}
Use decodeIfPresent if the value from response might be null
cnum = try container.decode(String.self, forKey: .cnum)
cname = try container.decodeIfPresent(String.self, forKey: .cname)