Инициализация Swift Codable

Я хотел бы выполнить некоторую логику инициализации после того, как функция быстрого кодирования/кодирования завершила декодирование JSON.

struct MyStruct: Codable {
    let id: Int 
    var name: String

    init() {
       name = "\(id) \(name)" 
    }
}

Но я получаю ошибку компилятора:

Return from initializer without initializing all stored properties

Что мне ясно, потому что init() хочет, чтобы я инициализировал все свойства. Но добавление init() со всеми необходимыми свойствами также не решает эту проблему, потому что этот инициализатор не вызывается (!) при запуске Codable:

init(id: Int, name: String) {
    // This initializer is not called if Decoded from JSON!
    self.id = id 
    self.name = "\(id) \(name)" 
}

Тем не менее, есть ли способ выполнить некоторую логику инициализации после завершения декодирования, но без выполнения всего декодирования вручную для каждого свойства? Таким образом, без реализации каждый раз init(from decoder: Decoder). В этом коротком примере у меня есть только два простых свойства, но производственный код состоит из тысяч из них.

Спасибо.


person Darko    schedule 16.01.2018    source источник
comment
Почему бы вам не сделать name вычисляемым свойством?   -  person Inder Kumar Rathore    schedule 16.01.2018
comment
Содержит ли ваш json ключ имени?   -  person Inder Kumar Rathore    schedule 16.01.2018
comment
Спасибо, но это просто пример. Мне нужен способ запустить логику инициализации.   -  person Darko    schedule 16.01.2018
comment
Я не думаю, что это возможно. Я либо сдался и реализовал init(from:), либо прибегнул к методам finializeInit(), которые я вызываю вручную после декодирования JSON.   -  person Gereon    schedule 16.01.2018
comment
@Gereon Не могли бы вы объяснить свой подход finalizeInit? Мне непонятно. Спасибо.   -  person Darko    schedule 16.01.2018
comment
self.name = "\(id) \(name)" — что он делает? Добавляет префикс id к имени?   -  person user28434'mstep    schedule 16.01.2018
comment
Как я уже сказал: это просто пример. Мне нужен способ запустить логику инициализации. (что будет намного позже)   -  person Darko    schedule 16.01.2018


Ответы (2)


Либо вы получаете все бесплатно, но стандартизировано, либо вам нужно написать собственный инициализатор, например

struct MyStruct: Codable  {

    let id: Int 
    var name: String

    private enum CodingKeys : String, CodingKey { case id, name }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        let decodedName = try container.decode(String.self, forKey: .name)
        name = "\(id) \(decodedName)" 
    }
}

Вы можете реализовать init(), но это работает независимо от функции декодирования, и вы должны присвоить значение по умолчанию всем необязательным свойствам, о чем говорит ошибка.

person vadian    schedule 16.01.2018
comment
получая ошибку для CodingKeys говоря 'CodingKeys' is inaccessible due to 'private' protection level. ты имеешь в виду CodingKey там? - person Keval Langalia; 26.02.2021
comment
Вы должны указать CodingKeys, мой плохой, см. Правку. - person vadian; 26.02.2021
comment
о да, я понял это после небольшого исследования и уже реализовал перечисление CodingKeys. Тем не менее, спасибо за решение! - person Keval Langalia; 02.03.2021

Используйте фабричный метод, который сначала использует init(from:), а затем вызывает ваш собственный код инициализации.

struct Foo: Decodable {
    let name: String
    let id: Int

    var x: String!

    private mutating func finalizeInit() {
        self.x = "\(name) \(id)"
    }

    static func createFromJSON(_ data: Data) -> Foo? {
        guard var f = try? JSONDecoder().decode(Foo.self, from: data) else { 
            return nil 
        }
        f.finalizeInit()
        return f
    }
}

let sampleData = """
    { "name": "foo", "id": 42 }
    """.data(using: .utf8)!
let f = Foo.createFromJSON(sampleData)
person Gereon    schedule 16.01.2018