Як виключити властивості з кодування Swift 4's


104

Нові Encodable/ Decodableпротоколи Swift 4 роблять серіалізацію JSON досить приємною. Однак я ще не знайшов способу чітко контролювати, які властивості слід кодувати, а які декодувати.

Я помітив, що виключення майна із супровідного CodingKeysпереліку виключає майно із процесу взагалі, але чи є спосіб отримати більш чіткий контроль?


Ви хочете сказати, що у вас є випадок, коли у вас є деякі властивості, які ви хочете кодувати, але різні властивості, які ви хочете декодувати? (тобто Ви хочете, щоб ваш тип не мав можливості скористатися оборотом?) Тому що, якщо ви просто дбаєте про виключення властивості, достатньо вказати йому значення за замовчуванням і залишити його поза CodingKeysпереліком.
Ітай Фербер

Незважаючи на це, ви завжди можете реалізувати вимоги Codableпротоколу ( init(from:)і encode(to:)) вручну для повного контролю над процесом.
Ітай Фербер

Моїм конкретним випадком використання є уникнення надмірного керування декодером, що може призвести до віддаленого отримання JSON із перезапису внутрішніх значень властивостей. Рішення нижче є адекватними!
RamwiseMatt

1
Я хотів би побачити відповідь / нову функцію Swift, яка вимагає обробки лише особливих випадків та виключених ключів, а не повторної реалізації всіх властивостей, які ви зазвичай повинні отримувати безкоштовно.
пкамб

Відповіді:


182

Список ключів для кодування / декодування контролюється типом, який називається CodingKeys(зверніть увагу sна кінець). Компілятор може синтезувати це для вас, але завжди може замінити це.

Скажімо, ви хочете виключити властивість nicknameяк з кодування, так і з декодування:

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

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

Якщо ви хочете, щоб воно було асиметричним (тобто кодувати, але не декодувати або навпаки), вам слід надати власні реалізації 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
Вам потрібно вказати nicknameзначення за замовчуванням, щоб це працювало. В іншому випадку значення не може бути присвоєне властивості init(from:).
Itai Ferber

1
@ItaiFerber Я переключив його на необов’язковий, який був у моєму Xcode спочатку
інший код

Ви впевнені, що повинні надати encodeв асиметричному прикладі? Оскільки це все ще стандартна поведінка, я не вважав, що це потрібно. Саме з decodeтих пір, звідки походить асиметрія.
Mark A. Donohoe

1
@MarqueIV Так, ти повинен. Оскільки fullNameнеможливо зіставити збережену властивість, ви повинні надати власний кодер та декодер.
Code Different

2

Якщо нам потрібно виключити декодування пари властивостей із великого набору властивостей у структурі, оголосіть їх як необов’язкові властивості. Код для розгортання додаткових опцій менше, ніж написання великої кількості ключів під переліком CodingKey.

Я б рекомендував використовувати розширення для додавання обчислюваних властивостей примірника та обчислюваних властивостей типу. Він відокремлює кодуючі властивості, що формують, від іншої логіки, отже, забезпечує кращу читабельність.


2

Ще одним способом виключення деяких властивостей з кодера може бути використаний окремий контейнер кодування

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

}

той же підхід може бути використаний для декодера


1

Ви можете використовувати обчислювані властивості:

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

Це був підказкою для мене - використання того, що lazy varефективно робить його властивістю виконання, виключало його з Codable.
ChrisH

0

Хоча це можна зробити, врешті-решт це виявляється дуже нехитрим і навіть unJSONy . Думаю, я бачу, звідки ви родом, концепція #ids поширена в HTML, але вона рідко переноситься у світJSON який я вважаю хорошим (TM).

Деякі Codableструктури зможуть JSONчудово проаналізувати ваш файл, якщо ви реструктуризуєте його за допомогою рекурсивних хешів, тобто якщо ваш recipeпросто містить масив, ingredientsякий, у свою чергу, містить (один або кілька) ingredient_info. Таким чином, парсер допоможе вам спочатку з’єднати вашу мережу, і вам доведеться надати деякі зворотні посилання за допомогою простого обходу структури, якщо вони вам дійсно потрібні . Оскільки для цього потрібна ретельна переробка вашої JSONта вашої структури даних, я лише намічаю ідею, щоб ви над цим задумалися. Якщо ви вважаєте це прийнятним, скажіть мені в коментарях, тоді я міг би це докласти далі, але залежно від обставин ви можете не мати свободи змінити жоден із них.


0

Я використовував протокол та його розширення разом із AssociatedObject для встановлення та отримання властивості зображення (або будь-якої властивості, яку потрібно виключити з Codable).

З цим нам не потрібно реалізовувати власний кодер та декодер

Ось код, зберігаючи відповідний код для простоти:

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
}

Тепер, коли ми хочемо отримати доступ до властивості Image, яку ми можемо використовувати на об'єкті, що підтверджує протокол (SCAttachmentModelProtocol)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.