Общая стратегия декодирования ключей несоответствия типов в JSON в ноль, если это необязательно в Swift

Вот моя проблема, когда я получаю какой-то JSON, бывает, что некоторые значения не соответствуют требуемому типу. Я действительно не против, меня интересует только значение, когда его тип правильный.

Например, следующая структура:

struct Foo : Decodable {
    var bar : Int?
}

Я бы хотел, чтобы он соответствовал этим JSON:

{ "bar" : 42 }    => foo.bar == 42
{ "bar" : null }  => foo.bar == nil
{ "bar" : "baz" } => foo.bar == nil

Действительно, я ищу необязательный Int, поэтому всякий раз, когда это целое число, я хочу его, но когда это null или что-то еще, я хочу nil.

К сожалению, наш старый добрый JSONDecoder в последнем случае выдает ошибку несоответствия типов.

Я знаю ручной способ сделать это:

struct Foo : Decodable {
    var bar : Int?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.bar = try? container.decode(Int.self, forKey: .bar)
    }
    
    enum CodingKeys : CodingKey {
        case bar
    }
}

Но у меня много структур и много полей для проверки.

Поэтому я хотел бы знать, есть ли общий способ сделать это примерно так:

decoder.typeMismatchStrategy = .nilInsteadOfError // <= Don't try it at home, I know it does not exist...

Или, может быть, переопределить JSONDecoder, все равно что-то писать один раз, а не на каждую структуру.

Заранее спасибо.


person Zaphod    schedule 17.09.2020    source источник
comment
Вы пытались объявить var bar : Any? Потому что он может быть любым, как вы его описываете.   -  person Roman Ryzhiy    schedule 17.09.2020
comment
Это не то, чего я хочу, к сожалению. Я хочу, чтобы поле заполнялось только тогда, когда оно Int. Более того, очень не хочется уходить от сильного шрифта, ставя везде Any.   -  person Zaphod    schedule 17.09.2020


Ответы (1)


Один из подходов — создать оболочку свойства, которая Decodable будет использоваться для таких свойств:

@propertyWrapper
struct NilOnTypeMismatch<Value> {
    var wrappedValue: Value?
}

extension NilOnTypeMismatch: Decodable where Value: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = try? container.decode(Value.self)
    }
}

Затем вы можете выборочно обернуть свойства, которые вы хотите специально обрабатывать:

struct Foo : Decodable {
    @NilOnTypeMismatch
    var bar : Int?
}

Более целостным подходом было бы расширение KeyedDecodingContainer для Ints, но это будет применяться ко всему приложению:

extension KeyedDecodingContainer {
    func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? {
        try? decode(Int.self, forKey: key)
    }
}

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

person New Dev    schedule 17.09.2020
comment
Это действительно умная идея. Но я искал что-то более общее со стороны декодирования, не касаясь структуры модели. Но мне все равно нравится. - person Zaphod; 17.09.2020
comment
Спасибо за ваше время ... Второй тоже умный, даже если вам нужно написать его для каждого типа, и, как вы говорите, для всего приложения. - person Zaphod; 17.09.2020