Trying to write a post in English. 👻

Now Apple support parsing JSON directly in Swift. I write an example to show how to do.
Download Demo

Before Swift 4

1
2
3
4
5
6
7
// Fake datas
let aBand: [String: Any] = [
"id": 23,
"name": "Nirvana"
]
// Assume we got a response data from an api
let data = try JSONSerialization.data(withJSONObject: aBand, options: .prettyPrinted)

Object Code:

1
2
3
4
5
6
7
8
9
10
struct Band {
let id: Int
let name: String?
// we need a initializer to deal with the JSON in swift 2/3 or ObjC
init(json: [String: Any]) {
id = json["id"] as? Int ?? -1
name = json["name"] as? String ?? ""
}
}

Here is what we do in Swift 2/3 or ObjC:

1
2
3
4
5
6
7
8
9
do {
guard let dict = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
throw NSError()
}
let band = Band(json: dict)
}
catch let error {
print(error)
}

Swift 4

Dictionary structure

1
2
3
4
let aBand: [String: Any] = [
"id": 23,
"name": "Nirvana"
]
1
2
3
4
5
6
7
8
9
10
11
12
struct Band: Decodable {
let id: Int
let name: String
}
do {
let band = try JSONDecoder().decode(Band.self, from: data)
print(band)
}
catch let error {
print(error)
}

Result: Band(id: 23, name: "Nirvana")

Array structure

1
2
3
4
5
6
7
8
9
10
let bands: [[String: Any]] = [
[
"id": 23,
"name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles"
]
]

Just change Band.self to [Band].self

1
let band = try JSONDecoder().decode([Band].self, from: data)

Result: [JsonDecode.Band(id: 23, name: "Nirvana"), JsonDecode.Band(id: 22, name: "The Beatles")]

Nested structure

1
2
3
4
5
6
7
8
9
10
11
12
13
let type: [String: Any] = [
"name": "Rock",
"examples": [
[
"id": 23,
"name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles"
]
]
]

Create a new type:

1
2
3
4
struct MusicType: Decodable {
let name: String
let examples: [Band]
}
1
2
3
4
5
6
7
8
9
10
11
12
struct Band: Decodable {
let id: Int
let name: String
}
do {
let band = try JSONDecoder().decode(MusicType.self, from: data)
print(band)
}
catch let error {
print(error)
}

Result: MusicType(name: "Rock", examples: [JsonDecode.Band(id: 23, name: "Nirvana"), JsonDecode.Band(id: 22, name: "The Beatles")])

Optional field

What if we got a JSON like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let type: [String: Any] = [
"name": "Rock",
"examples": [
[
"id": 23,
// "name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles",
"country": "UK"
]
]
]

OK, obviously the decoder will ignore the “country” key, but can not find the key “name”, we will get a error:

1
2
3
4
5
keyNotFound(CodingKeys(stringValue: "name", intValue: nil),
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "examples", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)],
debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").",
underlyingError: nil))

All we need to do is to make the name property to Optional:

1
2
3
4
struct Band: Decodable {
let id: Int
let name: String?
}

Result: MusicType(name: "Rock", examples: [JsonDecode.Band(id: 23, name: nil), JsonDecode.Band(id: 22, name: Optional("The Beatles"))])

Custom keys

If the field in JSON is not what we want:

1
2
3
4
let aBand: [String: Any] = [
"id": 23,
"band_name": "Nirvana"
]

We need to define a nested enum in our type called “CodingKeys” (or use a typealias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are decoded with.

1
2
3
4
5
6
7
8
9
struct Band: Decodable {
let id: Int
let name: String
private enum CodingKeys : String, CodingKey {
case id
case name = "band_name"
}
}

Results: Band(id: 23, name: "Nirvana")

But we need to write all the other properties in the enum, what we expected is adding others automatically in compiling time.
And also we expect a many-to-one ability, like "band_name" | "bandName" => name.
Hope Apple to update.

Update on 30th March, 2018 for Swift 4.1

1
2
3
4
5
6
7
8
struct Band: Decodable {
let id: Int
let bandName: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let band = try! decoder.decode(Band.self, from: data)

Results: Band(id: 23, name: "Nirvana")

Encode

Same like Decodable, We can use Encodable, or just use Codable

1
public typealias Codable = Decodable & Encodable

__Original articles, please indicate the source if you reproduced it.