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ę?

RamwiseMatt
źródło
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)
    }
}
Kod inny
źródło
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ść.

Hrishikesh Devhare
źródło
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

Aleksei Kiselev
źródło
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
  }
}
Beta-Logics
źródło
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.

Patru
źródło
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)

nieskończona pętla
źródło