Errors when using Codable - Swift - swift

I am using Codable to try to Encode JSON to a Model but I get two errors.
Value of type 'KeyedEncodingContainer' has no member 'encoder'
Here's my code:
import UIKit
struct NewCustomer : Codable {
var firstName :String
var lastName :String
private enum CodingKeys : String, CodingKey {
case firstName
case lastName
}
func encode(to encoder :Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encoder(self.firstName, forKey: .firstName) // error here
try container.encoder(self.lastName, forKey: .lastName) // error here
}
}
let customer = NewCustomer(firstName: "Jake", lastName: "Reynolds")
let encodedCustomerJSON = try!
JSONEncoder().encode(customer)
print(encodedCustomerJSON)
print(String(data: encodedCustomerJSON, encoding: .utf8)!)

Change encoder to encode on the two lines giving errors. Please note that the line above (i.e. var container...) will keep encoder.
try container.encode(self.firstName, forKey: .firstName)
try container.encode(self.lastName, forKey: .lastName)

As already mentioned it's a typo encode vs. encoder:
try container.encode(...
Practically you don't need to specify CodingKeys and the encoding method at all in this case, this is sufficient:
struct NewCustomer : Codable {
var firstName, lastName : String
}

Related

How to encode nested objects into JSON (Swift)?

I have such an object that should be encoded to JSON (My Playground example)
struct Toy: Codable {
var name: String
enum GiftKeys: String, CodingKey {
case toy = "name"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: GiftKeys.self)
try container.encode(self, forKey: .toy)
}
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
enum CodingKeys: CodingKey {
case name, id
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try favoriteToy.encode(to: encoder)
}
}
do {
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)
let encoder = JSONEncoder()
let nestedData: Data = try encoder.encode(employee)
let nestedString = String(data: nestedData, encoding: .utf8)!
print(nestedString)
}
What I am going to achieve is that each object knows how to encode itself. So, in my example when I through the employee object to the encoder, so each of the objects (employee and Toy) is responsible for its encoding.
But when I try to run the example in the Playground looks like it comes to the stuck in the line try favoriteToy.encode(to: encoder)
What is the problem here?
This line is the problem:
try container.encode(self, forKey: .toy)
The toy is trying to encode itself, so this will call encode(to:) again, causing infinite recursion. You probably meant:
try container.encode(name, forKey: .toy)
Remember that in the encode(to:) method, you are trying to answer the question of "how to encode a Toy?". If you answer with "just encode the toy itself!" That's not really answering the question, is it?
This will print:
{"name":"Teddy Bear","id":7}
Notice that the employee's name is overwritten by the toy's name, since you encoded the toy's name using the same encoder and same key, as the employee's name.
That's probably not what you intended. You probably wanted:
enum CodingKeys: CodingKey {
case name, id, toy
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try container.encode(favoriteToy, forKey: .toy)
}
// {"name":"John Appleseed","id":7,"toy":{"name":"Teddy Bear"}}
Note that all this code can be generated by the compiler, if you remove all your CodingKeys and encode methods:
struct Toy: Codable {
var name: String
}
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy
}
What I am going to achieve is that each object knows how to encode itself.
Each object does know to encode itself if you leave it alone by omitting the CodingKeys and encode(to methods
struct Toy: Encodable {
var name: String
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
}
You made two serious mistakes. If you implement encode(to you have to encode each struct member for the corresponding CodingKey rather than encoding self or calling encode(to: on a struct member.
So replace
try container.encode(self, forKey: .toy)
with
try container.encode(name, forKey: .toy)
and replace
try favoriteToy.encode(to: encoder)
with
try container.encode(favoriteToy, forKey: .favoriteToy)
and add also the missing CodingKey favoriteToy and the raw value type
private enum CodingKeys: String, CodingKey {
case name, id, favoriteToy
}
But in this case the best solution is not to implement encode(to

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.

Swift 4 - String Conversion to Capital Case During Decoding

In decoding JSON with Swift 4, I would like to convert a string during decoding into capital case. The JSON stores it as uppercase
For example
let title = "I CANT STAND THE RAIN"
print(title.capitalized)
How can I do this during the decoding process so the string is stored as capitalized in my model?
The only caveat is that I only want to capitalize one of the properties in the JSON (title) not the rest of them.
struct Book: Decodable {
let title: String
let author: String
let genre: String
init(newTitle: String, newAuthor: String, newGenre: String) {
title = newTitle
author = newAuthor
genre = newGenre
}
}
let book = try! decoder.decode(Book.self, from: jsonData)
You can provide your own custom Decodable initializer for your struct.
struct Book: Decodable {
let title: String
let author: String
let genre: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decode(String.self, forKey: .title).capitalized
author = try values.decode(String.self, forKey: .author)
genre = try values.decode(String.self, forKey: .genre)
}
enum CodingKeys: String, CodingKey {
case title, author, genre
}
}
jsonString.replace(/"\s*:\s*"[^"]/g, match => {
return match.slice(0, -1) + match[match.length - 1].toUpperCase()
})

Swift 4 JSONDecoder optional variable

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

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)