12/08/2018, 15:45

Ultimate Guide to JSON Parsing With Swift 4 (Phần 2)

Mở đầu Trong bài viết trước, tôi đã giới thiệu cơ bản về cách Parsing Data JSON trên swift 4, phương pháp xử lý một số kiểu dữ liệu thông dụng sử dụng công cụ có sẵn mà Apple cung cấp. Trong phần này tôi sẽ tiếp tục giới thiệu tới các bạn các thuộc tính nâng cao của Codable để Decode và encode ...

Mở đầu

Trong bài viết trước, tôi đã giới thiệu cơ bản về cách Parsing Data JSON trên swift 4, phương pháp xử lý một số kiểu dữ liệu thông dụng sử dụng công cụ có sẵn mà Apple cung cấp. Trong phần này tôi sẽ tiếp tục giới thiệu tới các bạn các thuộc tính nâng cao của Codable để Decode và encode JSON format.

Customizing Key Names

Thông thường trường hợp sử dụng của API cho việc đặt tên các khóa và phong cách này không khớp với các nguyên tắc đặt tên cho thuộc tính Swift.

Để tùy chỉnh điều này, chúng ta cần phải implement Codable default.

Các key được handled tự động bởi "CodingKeys" enumeration do trình biên dịch tạo ra. Enum này phù hợp với CodingKey, định nghĩa cách chúng ta có thể kết nối một thuộc tính với một giá trị kiểu encoded fomat.

Để tùy chỉnh các key, chúng ta sẽ phải viết việc thực hiện điều này

struct Beer : Codable {
      // ...
      enum CodingKeys : String, CodingKey {
          case name
          case abv = "alcohol_by_volume"
          case brewery = "brewery_name"
          case style
    }
}

Ví dụ về một instanse Beer và endcode nó dưới dạng JSON:

let encoder = JSONEncoder()
let data = try! encoder.encode(beer)
print(String(data: data, encoding: .utf8)!)

Output:

{"style":"ipa","name":"Endeavor","alcohol_by_volume":8.8999996185302734,"brewery_name":"Saint Arnold"}

Chúng ta có thể tùy chỉnh định dạng ouput của JSONEncoder để làm cho nó trở nên đẹp hơn với thuộc tính outputFormatting.

encoder.outputFormatting = .prettyPrinted
{
  "style" : "ipa",
  "name" : "Endeavor",
  "alcohol_by_volume" : 8.8999996185302734,
  "brewery_name" : "Saint Arnold"
}

Wrapper Keys

Thông thường API sẽ bao gồm các tên khóa wrapper để đối tượng JSON root luôn là một đối tượng.

Ví dụ như sau:

{
  "beers": [ {...} ]
}

Để thể hiện điều này trong Swift, chúng ta có thể tạo một kiểu mới cho response:

struct BeerList : Codable {
    let beers: [Beer]
}

Lưu ý rằng chúng ta đang sử dụng kiểu Array ở đây.

Dealing with Object Wrapping Keys

Một ví dụ khác, response array mà mỗi đối tượng trong mảng được wrapped bởi 1 key:

[
  {
    "beer" : {
      "id": "uuid12459078214",
      "name": "Endeavor",
      "abv": 8.9,
      "brewery": "Saint Arnold",
      "style": "ipa"
    }
  }
]

Chúng ta có thể thực hiện như sau:

[[String:Beer]]

hoặc

Array<Dictionary<String, Beer>>

Decoded:

let decoder = JSONDecoder()
let beers = try decoder.decode([[String:Beer]].self, from: data)
dump(beers)
▿ 1 element
  ▿ 1 key/value pair
    ▿ (2 elements)
      - key: "beer"
      ▿ value: __lldb_expr_37.Beer
        - name: "Endeavor"
        - brewery: "Saint Arnold"
        - abv: 8.89999962
        - style: __lldb_expr_37.BeerStyle.ipa

More Complex Nested Response

Đôi khi dữ liệu respone từ API trả về rất phức tạp, ví dụ như sau:

{
    "meta": {
        "page": 1,
        "total_pages": 4,
        "per_page": 10,
        "total_records": 38
    },
    "breweries": [
        {
            "id": 1234,
            "name": "Saint Arnold"
        },
        {
            "id": 52892,
            "name": "Buffalo Bayou"
        }
    ]
}

Chúng ta có thể thực hiện việc decode/encode JSON trong swift như sau:

struct PagedBreweries : Codable {
    struct Meta : Codable {
        let page: Int
        let totalPages: Int
        let perPage: Int
        let totalRecords: Int
        enum CodingKeys : String, CodingKey {
            case page
            case totalPages = "total_pages"
            case perPage = "per_page"
            case totalRecords = "total_records"
        }
    }

    struct Brewery : Codable {
        let id: Int
        let name: String
    }

    let meta: Meta
    let breweries: [Brewery]
}

Custom Encoding

Để bắt đầu, chúng ta bắt đầu với việc encoding:

extension Beer {
    func encode(to encoder: Encoder) throws {

    }
}

Thêm một vài thuộc tính của ** Beer**:

struct Beer : Coding {
    // ...
    let createdAt: Date
    let bottleSizes: [Float]
    let comments: String?

    enum CodingKeys: String, CodingKey {
        // ...
        case createdAt = "created_at",
        case bottleSizes = "bottle_sizes"
        case comments
    }
}

Trong method này chúng ta cần lấy bộ encoder, lấy một "container" và encode các giá trị vào nó.

What is a container?

Các kiểu container: Keyed Container – cung cấp các giá trị bởi các key. đây có nghĩa là một dictionary. Unkeyed Container – cung cấp các giá trị không bởi các key. Trong JSONEncoder, đây có nghĩa là một array. Single Value Container – Output là một kiểu giá trị nguyên và không bởi bao hàm phần tử nào. Để bắt đầu encode bắt đầu chúng ta phải khai báo một container:

var container = encoder.container(keyedBy: CodingKeys.self)

Có 2 điều cần chú ý

  • Container phải là một thuộc tính có thể thay đổi, vì chúng ta sẽ ghi vào nó, do đó, biến phải được khai báo với var
  • Chúng ta phải xác định các key để nó biết chúng ta có thể endcode những gì chúng ta vào trong container này Tiếp theo chúng ta cần phải encode các giá trị vào container. Bất kỳ cuộc gọi nào trong số này có thể gây ra lỗi, vì vậy chúng tôi sẽ bắt đầu bằng try :
try container.encode(name, forKey: .name)
try container.encode(abv, forKey: .abv)
try container.encode(brewery, forKey: .brewery)
try container.encode(style, forKey: .style)
try container.encode(createdAt, forKey: .createdAt)
try container.encode(comments, forKey: .comments)
try container.encode(bottleSizes, forKey: .bottleSizes)

Đối với trường comments, mặc Encodable sử dụng encodeIfPresent trên optional values. Có nghĩa là các key sẽ bị miss nếu chúng nil. Điều này thực sự không phải là tốt cho APIs, vậy tốt nhất là bao gồm các key mặc dù chúng có giá trị là nil. Ở đây, chúng ta buộc output các key này bằng cách sử dụng encode (: forKey             </div>
            
            <div class=

0