Jak wykluczyć właściwości z Codable Swift 4


111

Nowe protokoły Encodable/ w Swift 4 Decodablesprawiają, że serializacja JSON (de) jest całkiem przyjemna. Jednak nie znalazłem jeszcze sposobu na precyzyjną kontrolę nad tym, które właściwości powinny być zakodowane, a które zdekodowane.

Zauważyłem, że wykluczenie właściwości z towarzyszącego CodingKeyswyliczenia całkowicie wyklucza właściwość z procesu, ale czy istnieje sposób na bardziej szczegółową kontrolę?


Czy mówisz, że masz przypadek, w którym masz pewne właściwości, które chcesz zakodować, ale inne właściwości, które chcesz zdekodować? (tj. chcesz, aby Twój typ nie był możliwy do zrealizowania w obie strony?) Ponieważ jeśli zależy Ci tylko na wykluczeniu właściwości, wystarczy nadanie jej wartości domyślnej i pozostawienie jej poza CodingKeyswyliczeniem.
— Itai Ferber

Niezależnie od tego zawsze możesz zaimplementować wymagania Codableprotokołu ( init(from:)i encode(to:)) ręcznie, aby uzyskać pełną kontrolę nad procesem.
— Itai Ferber

Mój konkretny przypadek użycia polega na tym, aby nie dawać dekoderowi zbyt dużej kontroli, co mogłoby prowadzić do zdalnie uzyskanego kodu JSON w wyniku nadpisania wewnętrznych wartości właściwości. Poniższe rozwiązania są wystarczające!
— RamwiseMatt

1
Chciałbym zobaczyć odpowiedź / nową funkcję Swift, która wymaga tylko obsługi specjalnych przypadków i wykluczonych kluczy, zamiast ponownego wdrażania wszystkich właściwości, które normalnie powinieneś otrzymać za darmo.
— pkamb

Odpowiedzi:


192

Lista kluczy do kodowania / dekodowania jest kontrolowana przez typ o nazwie CodingKeys(zwróć uwagę sna koniec). Kompilator może zsyntetyzować to za Ciebie, ale zawsze może to zastąpić.

Załóżmy, że chcesz wykluczyć właściwość nicknamezarówno z kodowania, jak i dekodowania:

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

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

Jeśli chcesz, aby był asymetryczny (tj. Koduje, ale nie dekoduje lub odwrotnie), musisz dostarczyć własne implementacje encode(with encoder: )i 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)
    }
}

19
Aby nicknameto zadziałało, musisz podać wartość domyślną. W przeciwnym razie nie ma wartości, którą można przypisać do właściwości init(from:).
— Itai Ferber

1
@ItaiFerber Zmieniłem to na opcjonalny, który pierwotnie był w moim Xcode
— Code Different

Czy na pewno musisz podać encodew asymetrycznym przykładzie? Ponieważ jest to nadal standardowe zachowanie, nie sądziłem, że jest potrzebne. Tylko decodedlatego, że stąd bierze się asymetria.
— Mark A. Donohoe

1
@MarqueIV Tak, musisz. Ponieważ fullNamenie można zamapować na przechowywaną właściwość, należy podać niestandardowy koder i dekoder.
— Code Different

Właśnie przetestowałem to w Swift 5. Powinieneś tylko zdefiniować stałą dla właściwości, której nie chcesz dekodować. Nie musisz jawnie dodawać kluczy do CodingKeys. Więc var nickname: String { get { "name" } }powinno wystarczyć.
— Leo

3

Jeśli musimy wykluczyć dekodowanie kilku właściwości z dużego zestawu właściwości w strukturze, zadeklaruj je jako właściwości opcjonalne. Kod do rozpakowywania opcji jest mniejszy niż pisanie wielu kluczy w wyliczeniu CodingKey.

Zalecałbym używanie rozszerzeń do dodawania obliczonych właściwości instancji i obliczonych właściwości typu. Oddziela kodowalne, zgodne właściwości od innej logiki, dzięki czemu zapewnia lepszą czytelność.


2

Innym sposobem na wykluczenie niektórych właściwości z kodera może być użycie oddzielnego kontenera kodowania

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

}

to samo podejście można zastosować do dekodera


1

Możesz użyć obliczonych właściwości:

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

To była dla mnie wskazówka - lazy varefektywne użycie właściwości środowiska wykonawczego wykluczyło ją z Codable.
— ChrisH,

0

Chociaż można to zrobić, ostatecznie okazuje się, że jest to bardzo nierozsądne, a nawet nieskomplikowane . Myślę, że widzę, skąd pochodzisz, pojęcie #ids jest powszechne w HTML, ale rzadko przenoszone jest do świata, JSONktóry uważam za dobrą rzecz (TM).

Niektóre Codablestruktury będą w stanie JSONpoprawnie przeanalizować plik, jeśli zmienisz jego strukturę za pomocą rekurencyjnych skrótów, tj. Jeśli Twoja recipepo prostu zawiera tablicę, ingredientsktóra z kolei zawiera (jedną lub kilka) ingredient_info. W ten sposób parser pomoże ci najpierw zszyć twoją sieć, a jeśli naprawdę ich potrzebujesz, wystarczy zapewnić kilka linków zwrotnych poprzez proste przejście przez strukturę . Ponieważ wymaga to gruntownej zmiany struktury twoich JSONi twoich danych, szkicuję tylko pomysł, abyś o tym pomyślał. Jeśli uznasz to za dopuszczalne, poinformuj mnie o tym w komentarzach, wtedy mógłbym to bardziej rozwinąć, ale w zależności od okoliczności możesz nie mieć możliwości zmiany któregokolwiek z nich.


0

Użyłem protokołu i jego rozszerzenia wraz z AssociatedObject, aby ustawić i pobrać właściwość obrazu (lub dowolnej właściwości, która musi zostać wykluczona z Codable).

Dzięki temu nie musimy wdrażać własnego kodera i dekodera

Oto kod, zachowując odpowiedni kod dla uproszczenia:

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
}

Teraz, ilekroć chcemy uzyskać dostęp do właściwości Image, możemy użyć na obiekcie potwierdzającym protokół (SCAttachmentModelProtocol)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.