Как декодировать выбранные ключи вручную и использовать автоматическое декодирование с быстрым декодированием?

Вот код, который я использую,

struct CreatePostResponseModel : Codable{
    var transcodeId:String?
    var id:String = ""
    enum TopLevelCodingKeys: String, CodingKey {
        case _transcode = "_transcode"
        case _transcoder = "_transcoder"
    }
    enum CodingKeys:String, CodingKey{
        case id = "_id"
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
        if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcode) {
            self.transcodeId = transcodeId
        }else if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcoder) {
            self.transcodeId = transcodeId
        }

    }
}

Здесь transcodeId определяется либо _transcode, либо _transcoder. Но я хочу, чтобы id и остальные ключи (не включенные сюда) автоматически декодировались. Как мне это сделать ?


person infiniteLoop    schedule 26.07.2019    source источник
comment
Вы также должны вручную декодировать другие клавиши.   -  person Sweeper    schedule 26.07.2019


Ответы (3)


Вам нужно вручную проанализировать все ключи, как только вы реализуете init(from:) в типе Codable.

struct CreatePostResponseModel: Decodable {
    var transcodeId: String?
    var id: String

    enum CodingKeys:String, CodingKey{
        case id, transcode, transcoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
        if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcode) {
            self.transcodeId = transcodeId
        } else if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcoder) {
            self.transcodeId = transcodeId
        }
    }
}

В приведенном выше коде

  1. Если вы хотите декодировать только JSON, нет необходимости использовать Codable. Достаточно использовать Decodable.
  2. Использование нескольких enums для CodingKey здесь кажется излишним. Вы можете использовать один enum CodingKeys.
  3. Если имя свойства и имя ключа полностью совпадают, нет необходимости явно указывать rawValue этого case в enum CodingKeys. Таким образом, нет необходимости в "_transcode" и "_transcoder" rawValues в TopLevelCodingKeys.

Помимо всего этого, вы можете использовать keyDecodingStrategy как .convertFromSnakeCase для обработки нотации подчеркивания (нотация регистра змеи), т.е.

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase //here.....
    let model = try decoder.decode(CreatePostResponseModel.self, from: data)
    print(model)
} catch {
    print(error)
}

Таким образом, вам не нужно явно обрабатывать все ключи змеиного регистра. JSONDecoder справится с этим самостоятельно.

person PGDev    schedule 26.07.2019
comment
Спасибо за ответ. Для пункта 1: мне нужно кодировать, а наоборот. 2: Произошла ошибка компиляции, если я держу больше ключей в CodingKeys и не имею соответствующей переменной. 3: Имя свойства происходит от одного из двух значений (_transcoder и _transcode). - person infiniteLoop; 26.07.2019
comment
Не совсем так, мне все равно придется расшифровывать все ключи :( - person infiniteLoop; 26.07.2019
comment
Да, вы должны. Лучше изменить API, чтобы он давал последовательный ответ. - person PGDev; 26.07.2019
comment
Абсолютно согласен. - person infiniteLoop; 26.07.2019

Это может быть одним из хороших решений для вас, где бы вы ни захотели, вы можете добавить несколько ключей для одной переменной:

var transcodeId:String?

public init(from decoder: Decoder) throws {

    do {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        transcodeId =  container.getValueFromAvailableKey(codingKeys: [CodingKeys._transcoder,CodingKeys._transcode])
    } catch {
        print("Error reading config file: \(error.localizedDescription)")
    }
}

extension KeyedDecodingContainerProtocol{

    func getValueFromAvailableKey(codingKeys:[CodingKey])-> String?{
         for key in codingKeys{
             for keyPath in self.allKeys{
                 if key.stringValue == keyPath.stringValue{
                    do{ 
                        return try self.decodeIfPresent(String.self, forKey: keyPath)
                    } catch {
                        return nil
                    }
                }
            }
        }
        return nil
    }
}

Надеюсь, поможет.

person SaurabhRode007    schedule 14.11.2019

Генерируемый компилятором init(from:) - это все или ничего. Вы не можете заставить его декодировать одни клавиши и «вручную» декодировать другие.

Один из способов использования сгенерированного компилятором init(from:) – предоставить struct оба возможных закодированных свойства и сделать transcodeId вычисляемым свойством:

struct CreatePostResponseModel: Codable {
    var transcodeId: String? {
        get { _transcode ?? _transcoder }
        set { _transcode = newValue; _transcoder = nil }
    }

    var _transcode: String? = nil
    var _transcoder: String? = nil

    var id: String = “”
    // other properties
}
person rob mayoff    schedule 14.11.2019