Bagaimana cara mengecualikan properti dari Codable Swift 4

111

Encodable/ DecodableProtocols baru Swift 4 membuat serialisasi JSON (de) cukup menyenangkan. Namun, saya belum menemukan cara untuk memiliki kontrol yang sangat baik atas properti mana yang harus dikodekan dan mana yang harus didekodekan.

Saya telah memperhatikan bahwa mengecualikan properti dari CodingKeysenum yang menyertai mengecualikan properti dari proses sama sekali, tetapi adakah cara untuk memiliki kontrol yang lebih halus?

RamwiseMatt
sumber
Apakah Anda mengatakan Anda memiliki kasus di mana Anda memiliki beberapa properti yang ingin Anda encode, tetapi properti berbeda yang ingin Anda dekode? (mis. Anda ingin tipe Anda tidak round-trippable?) Karena jika Anda hanya peduli tentang mengecualikan properti, memberikannya nilai default dan membiarkannya keluar dari CodingKeysenum sudah cukup.
Itai Ferber
Terlepas dari itu, Anda selalu dapat menerapkan persyaratan Codableprotokol ( init(from:)dan encode(to:)) secara manual untuk kontrol penuh atas proses tersebut.
Itai Ferber
Kasus penggunaan khusus saya adalah untuk menghindari memberikan dekoder terlalu banyak kontrol, yang dapat menyebabkan JSON diperoleh dari jarak jauh karena menimpa nilai properti internal. Solusi di bawah ini cukup memadai!
RamwiseMatt
1
Saya ingin melihat jawaban / fitur Swift baru yang hanya memerlukan penanganan kasus khusus dan kunci yang dikecualikan, daripada menerapkan ulang semua properti yang biasanya Anda dapatkan secara gratis.
pkamb

Jawaban:

192

Daftar kunci untuk disandikan / didekode dikendalikan oleh jenis yang disebut CodingKeys(perhatikan sdi akhir). Kompilator dapat mensintesiskan ini untuk Anda tetapi selalu dapat menimpanya.

Misalkan Anda ingin mengecualikan properti nicknamedari encoding dan decoding:

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

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

Jika Anda ingin asimetris (yaitu, menyandikan tetapi tidak mendekode atau sebaliknya), Anda harus menyediakan implementasi Anda sendiri dari encode(with encoder: )dan 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)
    }
}
Kode Berbeda
sumber
19
Anda perlu memberikan nicknamenilai default agar ini berfungsi. Jika tidak, tidak ada nilai yang dapat ditetapkan ke properti di init(from:).
Itai Ferber
1
@ItaiFerber Saya mengubahnya menjadi opsional, yang awalnya ada di Xcode saya
Kode Berbeda
Apakah Anda yakin harus memberikan encodedalam contoh asimetris? Karena itu masih perilaku standar, saya rasa itu tidak diperlukan. Hanya decodekarena itu di mana asimetri itu berasal.
Mark A. Donohoe
1
@MarqueIV Ya, Anda harus. Karena fullNametidak dapat dipetakan ke properti tersimpan, Anda harus menyediakan pembuat enkode dan dekoder khusus.
Kode Berbeda
Baru saja mengujinya di Swift 5. Anda hanya perlu menentukan konstanta untuk properti yang tidak akan Anda dekode. Anda tidak perlu menambahkan kunci ke secara eksplisit CodingKeys. Jadi, var nickname: String { get { "name" } }seharusnya sudah cukup.
Leo
3

Jika kita perlu mengecualikan decoding beberapa properti dari sekumpulan besar properti dalam struktur, deklarasikan sebagai properti opsional. Kode untuk membuka bungkus opsional kurang dari menulis banyak kunci di bawah enum CodingKey.

Saya akan merekomendasikan menggunakan ekstensi untuk menambahkan properti contoh yang dihitung dan properti tipe yang dihitung. Ini memisahkan properti comforming yang dapat dikodekan dari logika lain sehingga memberikan keterbacaan yang lebih baik.

Hrishikesh Devhare
sumber
2

Cara lain untuk mengecualikan beberapa properti dari pembuat enkode, wadah pengkodean terpisah dapat digunakan

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

}

pendekatan yang sama dapat digunakan untuk decoder

Aleksei Kiselev
sumber
1

Anda dapat menggunakan properti yang dihitung:

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
  }
}
Beta-Logika
sumber
Ini adalah petunjuk bagi saya - menggunakan lazy varproperti runtime secara efektif untuk mengecualikannya dari Codable.
ChrisH
0

Meskipun hal ini dapat dilakukan, pada akhirnya akan menjadi sangat tidak sopan dan bahkan tidak PRIBADI . Saya rasa saya melihat dari mana Anda berasal, konsep #ids lazim di HTML, tetapi jarang dibawa ke dunia JSONyang saya anggap sebagai hal yang baik (TM).

Beberapa Codablestruct akan dapat mengurai JSONfile Anda dengan baik jika Anda merestrukturnya menggunakan hash rekursif, yaitu jika Anda recipehanya berisi sebuah array ingredientsyang pada gilirannya berisi (satu atau beberapa) ingredient_info. Dengan cara itu parser akan membantu Anda untuk menyatukan jaringan Anda dan Anda hanya perlu menyediakan beberapa tautan balik melalui struktur traversal sederhana jika Anda benar-benar membutuhkannya . Karena ini memerlukan pengerjaan ulang menyeluruh dari Anda JSONdan struktur data Anda, saya hanya membuat sketsa ide agar Anda memikirkannya. Jika Anda menganggapnya dapat diterima, tolong beri tahu saya di komentar maka saya dapat menjelaskannya lebih lanjut, tetapi tergantung pada keadaan Anda mungkin tidak memiliki kebebasan untuk mengubah salah satu dari mereka.

Patru
sumber
0

Saya telah menggunakan protokol dan ekstensinya bersama dengan AssociatedObject untuk mengatur dan mendapatkan properti gambar (atau properti apa pun yang perlu dikecualikan dari Codable).

Dengan ini kita tidak perlu mengimplementasikan Encoder dan Decoder kita sendiri

Ini kodenya, menyimpan kode yang relevan untuk kesederhanaan:

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
}

Sekarang, kapan pun kita ingin mengakses properti Image, kita dapat menggunakan objek yang mengonfirmasi protokol (SCAttachmentModelProtocol)

infiniteLoop
sumber