Codable với dynamic key và dynamic type of value

1. JSON với dynamic key và static type of value:

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.

2. JSON với dynamic key và dynamic type of value:

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.

3. Phần Custom Codable

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")
   }
  }
}


4. Phần init

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))
}

5. Sử dụng

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"}
Bình luận
* Các email sẽ không được công bố trên trang web.
I BUILT MY SITE FOR FREE USING