Как исключить свойства из Codable Swift 4

104

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

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

RamwiseMatt
источник
Вы говорите, что у вас есть случай, когда у вас есть некоторые свойства, которые вы хотите кодировать, но другие свойства, которые вы хотите декодировать? (т.е. вы хотите, чтобы ваш тип не допускал циклического отключения?) Потому что, если вы просто заботитесь об исключении свойства, достаточно присвоить ему значение по умолчанию и оставить его вне CodingKeysперечисления.
Итаи Фербер,
Тем не менее, вы всегда можете реализовать требования Codableпротокола ( init(from:)и encode(to:)) вручную для полного контроля над процессом.
Итаи Фербер,
Мой конкретный вариант использования - не давать декодеру слишком много контроля, что может привести к удаленному получению JSON из-за перезаписи значений внутренних свойств. Приведенные ниже решения подходят!
RamwiseMatt
1
Я хотел бы увидеть ответ / новую функцию Swift, которая требует только обработки особых случаев и исключенных ключей, а не повторной реализации всех свойств, которые вы обычно должны получать бесплатно.
pkamb

Ответы:

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:).
Итаи Фербер,
1
@ItaiFerber Я переключил его на необязательный, который изначально был в моем Xcode
Code Different
Вы уверены, что должны указать encodeв асимметричном примере? Поскольку это все еще стандартное поведение, я не думал, что это нужно. Просто decodeпотому, что отсюда и происходит асимметрия.
Марк А. Донохо
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

Хотя это можно сделать, в конечном итоге это оказывается очень непостоянным и даже не JSONy . Думаю, я понимаю, откуда вы пришли, концепция #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)

бесконечная петля
источник