Swift4 makes JSON serialization easier than ever. If you want an object to be encodable, Adapt to Encodable protocols. If you want an object to be decodable, adapt to Decodable protocols. Codable protocols is for objects that both encodable and decodable.

Automatic Encoding and Decoding

1
2
3
4
struct Employee: Codable {
var name: String
var id: Int
}

Now this class can be serialize or deserialize to JSON. Isn’t that convenient? Also if you have an object as a property inside another object, conform to Codable too.

1
2
3
4
5
6
7
8
9
10
struct Employee: Codable {
var name: String
var id: Int
var salary: Salary
}

struct Salary: Codable {
var amount: int
var tax: Int
}

The above class can also be serialize or deserialize to JSON.

Encoder and Decoder

Encoder works to turn Encodable objects to JSON serialized string. Decoder works to turn JSON serialized string into Decodable objects.

Here is how you encode:

1
2
3
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(object)
let jsonString = String(data: jsonData, encoding: .utf8)

Here is how you decode:

1
2
let jsonDecoder = JSONDecoder()
let object = try jsonDecoder.decode(Object.self, from: object)

Renaming Properties With CodingKeys

If you want to have different keys then your property name in your serialized string, you will have to implement a CodingKey enum. Note that you will need to include all properties in the enum even if you don’t plan to rename all of them.

1
2
3
4
5
6
7
8
9
10
11
struct Employee: Codable {
var name: String
var id: Int
var salary: Salary

enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case salary
}
}

Manual Encoding and Decoding

If in some case you want to alter the structure of the JSON not just rename the properties, you will need to write your own encoding and decoding logic.

For example, you don’t want to have the following JSON:

1
2
3
4
5
6
7
{
"name": "Jerry"
"employeeId": 763246
"salary": {
"amount": 85000
"tax": 25000
}

Instead you want the following JSON:

1
2
3
4
5
6
{
"name": "Jerry"
"employeeId": 763246
"amount": 85000
"tax": 25000
}

You will have to change the following in CodingKeys, the key enum case contains the key for the json. So you need to add case amount and case tax since they are both the key to the serialized string.

1
2
3
4
5
6
7
8
9
10
11
12
struct Employee: Codable {
var name: String
var id: Int
var salary: Salary

enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case amount
case tax
}
}

For encode:

1
2
3
4
5
6
7
8
9
extension Employee: Encodable {
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(salary.amount, forKey: .amount)
try container.encode(salary.tax, forKey: .tax)
}
}

For decode

1
2
3
4
5
6
7
8
9
10
extension Employee: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
id = try container.decode(Int.self, forKey: .id)
let amount = try container.decode(Int.self, forKey: .amount)
let tax = try container.decode(Int.self, forKey: .tax)
salary = Salary(amount: amount, tax: tax)
}
}

Inheritance of Encodable / Decodable

You have to override the encode or init:decoder in your subclass and call super before the regular instructions.

For example of encode: Code sample from Stack Overflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class BasicData {
let a: String
let b: Int

private enum CodingKeys: String, CodingKey {
case a, b
}

override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.a, forKey: .a)
try container.encode(self.b, forKey: .b)
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_a = try container.decode(String.self, forKey .a)
_b = try container.decode(Int.self, forkey .b)
}
}

// In its subclass, you will need to override both encode and init:from.
class AdditionalData: BasicData {
let c: String
let d: Int

init(c: String, d: Int) {
self.c = c
self.d = d
}

private enum CodingKeys: String, CodingKey {
case c, d
}

override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.c, forKey: .c)
try container.encode(self.d, forKey: .d)

try super.encode(to: encoder) // don't forget to call super
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_c = try container.decode(String.self, forKey .c)
_d = try container.decode(Int.self, forkey .d)

try super.init(from: decoder) // don't forget to call super
}
}

NOTE: When doing research, I found that there is some resource out there that passes container.superEncoder and container.superDecoder into the super class methods. For example: swift4 - Using Decodable in Swift 4 with Inheritance - Stack Overflow. However, when I try in my QuestIt project, I found that it will create a new subentry called super: { ... } in serialized JSON, but those are just redundant data because everything inside that super section is already exists in outside super. So that’s not a very good solution. I recommand to pass in encoder or decoder directly to super.