Cách loại trừ các thuộc tính khỏi Swift 4's Codable


104

Các giao thức Encodable/ mới của Swift 4 Decodablelàm cho việc tuần tự hóa JSON (de) trở nên khá dễ chịu. Tuy nhiên, tôi vẫn chưa tìm ra cách kiểm soát chi tiết đối với thuộc tính nào nên được mã hóa và thuộc tính nào sẽ được giải mã.

Tôi nhận thấy rằng việc loại trừ thuộc tính khỏi CodingKeysenum đi kèm sẽ loại trừ hoàn toàn thuộc tính khỏi quy trình, nhưng có cách nào để kiểm soát chi tiết hơn không?


Bạn đang nói rằng bạn có một trường hợp mà bạn có một số thuộc tính mà bạn muốn mã hóa, nhưng các thuộc tính khác mà bạn muốn giải mã? (tức là Bạn muốn kiểu của mình không thể xoay vòng được?) Bởi vì nếu bạn chỉ quan tâm đến việc loại trừ thuộc tính, thì việc đặt cho nó một giá trị mặc định và bỏ nó ra khỏi CodingKeysenum là đủ.
Itai Ferber

Bất kể, bạn luôn có thể triển khai các yêu cầu của Codablegiao thức ( init(from:)encode(to:)) theo cách thủ công để toàn quyền kiểm soát quá trình.
Itai Ferber

Trường hợp sử dụng cụ thể của tôi là tránh cấp cho bộ giải mã quá nhiều quyền kiểm soát, điều này có thể dẫn đến việc JSON thu được từ xa do ghi đè các giá trị thuộc tính nội bộ. Các giải pháp dưới đây là đầy đủ!
RamwiseMatt

1
Tôi muốn thấy một câu trả lời / tính năng Swift mới chỉ yêu cầu xử lý các trường hợp đặc biệt và các khóa bị loại trừ, thay vì triển khai lại tất cả các thuộc tính mà bạn thường nhận được miễn phí.
pkamb

Câu trả lời:


182

Danh sách các khóa để mã hóa / giải mã được điều khiển bởi một loại được gọi là CodingKeys(ghi chú sở cuối). Trình biên dịch có thể tổng hợp điều này cho bạn nhưng luôn có thể ghi đè điều đó.

Giả sử bạn muốn loại trừ thuộc tính nicknamekhỏi cả mã hóa giải mã:

struct Person: Codable {
    var firstName: String
    var lastName: String
    var nickname: String?

    private enum CodingKeys: String, CodingKey {
        case firstName, lastName
    }
}

Nếu bạn muốn nó không đối xứng (tức là mã hóa nhưng không giải mã hoặc ngược lại), bạn phải cung cấp các triển khai của riêng mình encode(with encoder: )init(from decoder: ):

struct Person: Codable {
    var firstName: String
    var lastName: String

    // Since fullName is a computed property, it's excluded by default
    var fullName: String {
        return firstName + " " + lastName
    }

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
        case fullName
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(fullName, forKey: .fullName)
    }
}

17
Bạn cần cung cấp nicknamemột giá trị mặc định để giá trị này hoạt động. Nếu không, không có giá trị nào có thể được gán cho thuộc tính trên init(from:).
Itai Ferber

1
@ItaiFerber tôi chuyển nó đến một tùy chọn, đó là trong Xcode của tôi ban đầu
Mã khác nhau

Bạn có chắc chắn phải cung cấp encodetrong ví dụ bất đối xứng không? Vì đó vẫn là hành vi tiêu chuẩn, tôi không nghĩ rằng nó là cần thiết. Chỉ decodevì đó là nơi bắt nguồn của sự bất đối xứng.
Mark A. Donohoe

1
@MarqueIV Có, bạn phải làm vậy. Vì fullNamekhông thể được ánh xạ tới thuộc tính đã lưu trữ, bạn phải cung cấp bộ mã hóa và giải mã tùy chỉnh.
Mã khác

2

Nếu chúng ta cần loại trừ việc giải mã một vài thuộc tính khỏi một tập hợp lớn các thuộc tính trong cấu trúc, hãy khai báo chúng dưới dạng các thuộc tính tùy chọn. Mã để mở gói tùy chọn ít hơn so với việc viết nhiều khóa trong enum CodingKey.

Tôi khuyên bạn nên sử dụng tiện ích mở rộng để thêm thuộc tính phiên bản được tính toán và thuộc tính loại được tính toán. Nó tách biệt các thuộc tính hợp lệ có thể mã hóa khỏi các logic khác do đó cung cấp khả năng đọc tốt hơn.


2

Một cách khác để loại trừ một số thuộc tính khỏi bộ mã hóa, có thể sử dụng vùng chứa mã hóa riêng biệt

struct Person: Codable {
    let firstName: String
    let lastName: String
    let excludedFromEncoder: String

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
    }
    private enum AdditionalCodingKeys: String, CodingKey {
        case excludedFromEncoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)

        excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
    }

    // it is not necessary to implement custom encoding
    // func encode(to encoder: Encoder) throws

    // let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
    // let jsonData = try JSONEncoder().encode(person)
    // let jsonString = String(data: jsonData, encoding: .utf8)
    // jsonString --> {"firstName": "fname", "lastName": "lname"}

}

phương pháp tương tự có thể được sử dụng cho bộ giải mã


1

Bạn có thể sử dụng các thuộc tính được tính toán:

struct Person: Codable {
  var firstName: String
  var lastName: String
  var nickname: String?

  var nick: String {
    get {
      nickname ?? ""
    }
  }

  private enum CodingKeys: String, CodingKey {
    case firstName, lastName
  }
}

Đây là manh mối đối với tôi - sử dụng một lazy varcách hiệu quả để biến nó thành thuộc tính thời gian chạy đã loại trừ nó khỏi Codable.
ChrisH

0

Mặc dù điều này có thể được thực hiện nhưng cuối cùng lại trở nên rất kém thoải mái và thậm chí là không thoải mái . Tôi nghĩ tôi biết bạn đến từ đâu, khái niệm #ids rất phổ biến trong HTML, nhưng nó hiếm khi được chuyển đến thế giới JSONmà tôi cho là một điều tốt (TM).

Một số Codablecấu trúc sẽ có thể phân tích cú pháp JSONtệp của bạn tốt nếu bạn cấu trúc lại nó bằng cách sử dụng hàm băm đệ quy, tức là nếu bạn recipechỉ chứa một mảng trong ingredientsđó lần lượt chứa (một hoặc một số) ingredient_info. Bằng cách đó, trình phân tích cú pháp sẽ giúp bạn kết hợp mạng của mình với nhau ngay từ đầu và bạn chỉ phải cung cấp một số liên kết ngược thông qua cấu trúc duyệt đơn giản nếu bạn thực sự cần chúng . Vì điều này đòi hỏi phải làm lại toàn bộ cấu trúc dữ liệu của bạn JSON của bạn, tôi chỉ phác thảo ý tưởng để bạn suy nghĩ về nó. Nếu bạn cho là có thể chấp nhận được, vui lòng cho tôi biết trong phần nhận xét sau đó tôi có thể giải thích thêm, nhưng tùy thuộc vào trường hợp bạn có thể không được tự do thay đổi một trong hai.


0

Tôi đã sử dụng giao thức và phần mở rộng của nó cùng với AssociatedObject để đặt và lấy thuộc tính hình ảnh (hoặc bất kỳ thuộc tính nào cần được loại trừ khỏi Codable).

Với điều này, chúng tôi không phải triển khai Bộ mã hóa và Bộ giải mã của riêng mình

Đây là mã, giữ mã có liên quan cho đơn giản:

protocol SCAttachmentModelProtocol{
    var image:UIImage? {get set}
    var anotherProperty:Int {get set}
}
extension SCAttachmentModelProtocol where Self: SCAttachmentUploadRequestModel{
    var image:UIImage? {
        set{
            //Use associated object property to set it
        }
        get{
            //Use associated object property to get it
        }
    }
}
class SCAttachmentUploadRequestModel : SCAttachmentModelProtocol, Codable{
    var anotherProperty:Int
}

Bây giờ, bất cứ khi nào chúng ta muốn truy cập thuộc tính Image, chúng ta có thể sử dụng trên đối tượng xác nhận giao thức (SCAttachmentModelProtocol)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.