Giả sử bây giờ mình có một JSON như sau:
{ "id": "4yq6txdpfadhbaqnwp3", "email": "duy.bui297dn@gmail.com", "name":"Duy Bui", "metadata": { "link_id": "linked-id", "buy_count": "4" } }
Trong đó, các parameter bên trong metadata có key thay đổi nhưng may mắn là TYPE của value là cố định. Trong trường hợp này là String type. Khi đó, ta Decode & Encode rất nhẹ nhàng.
struct User: Codable { var id: String var email: String var name: String var metadata: [String: String] }
enum CodingKey thì ta sẽ làm như bình thường.
Giả sử bây giờ mình có một JSON như sau:
{ "id": "4yq6txdpfadhbaqnwp3", "email": "john.doe@example.com", "name":"John Doe", "metadata": { "link_id": "linked-id", "buy_count": 4.0 } }
Trong đó, các parameter bên trong metadata có key thay đổi VÀ TYPE của value cũng không xác định được luôn.
struct User: Codable { var id: String var email: String var name: String var metadata: [String: Any] }
Một khi có một kiểu Any xuất hiện, Codable sẽ báo lỗi vì nó không thể implement mặc định giúp bạn khi không biết đó chính xác là kiểu gì.
Bạn phải tự mình thực hiện hầu hết công việc vì compiler không thể tạo ra các triển khai cho bạn.
a. Coding Keys
Đầu tiên, tìm hiểu một chút về các codingKey, ta có hai loại codingKey:
- StaticCodingKeys, phần này không có gì đặc biệt. Ta list ra những key mà chắc chắn sẽ không thay đổi
private enum StaticCodingKeys: String, CodingKey { case id, email, name, metadata }
- DynamicCodingKeys, phần này ta sẽ lưu trữ dưới dạng struct và implement các requirement của protocol đó như sau:
private struct DynamicCodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { self.init(stringValue: "") self.intValue = intValue } } }
b. Decode và Encode metadata:
Tiếp theo ta sẽ tạo các function decode và encode cái thằng dynamic kia. Lưu ý, ta cần sử dụng DynamicCodingKeys để reference đến những key dynamic.
- Decode
static func decodeMetadata(from decoder: Decoder) throws -> [String: Any] { let container = try decoder.container(keyedBy: DynamicCodingKeys.self) var result: [String: Any] = [:] for key in container.allKeys { if let double = try? container.decode(Double.self, forKey: key) { result[key.stringValue] = double } else if let string = try? container.decode(String.self, forKey: key) { result[key.stringValue] = string } } return result }
- Encode: Encode và decode khác nhau đôi chút nên ta không có sự khác nhau chút chút. Ở decode ta cần static function và ở encode thì không.
func encodeMetadata(to encoder: Encoder) throws { var container = encoder.container(keyedBy: DynamicCodingKeys.self) for (key, value) in metadata { switch value { case let double as Double: try container.encode(double, forKey: DynamicCodingKeys(stringValue: key)!) case let string as String: try container.encode(string, forKey: DynamicCodingKeys(stringValue: key)!) default: fatalError("unexpected type") } } }
Cuối cùng là phần tạo init cho nó như sau:
Phần decode
init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StaticCodingKeys.self) self.id = try container.decode(String.self, forKey: .id) self.email = try container.decode(String.self, forKey: .email) self.name = try container.decode(String.self, forKey: .name) self.metadata = try User.decodeMetadata(from: container.superDecoder(forKey: .metadata)) }
Phần encode
func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StaticCodingKeys.self) try container.encode(self.id, forKey: .id) try container.encode(self.email, forKey: .email) try container.encode(self.name, forKey: .name) try encodeMetadata(to: container.superEncoder(forKey: .metadata)) }
let decoder = JSONDecoder() let user = try! decoder.decode(User.self, from: userJson) print(user) // Prints: User(id: "4yq6txdpfadhbaqnwp3", email: "john.doe@example.com", name: "John Doe", metadata: ["buy_count": 4.0, "link_id": "linked-id"])
let encoder = JSONEncoder() let data = try! encoder.encode(user) print(String(data: data, encoding: .utf8)!) // Prints: {"email":"john.doe@example.com","id":"4yq6txdpfadhbaqnwp3","metadata":{"link_id":"linked-id","buy_count":4},"name":"John Doe"}