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

#json #swift #type-mismatch #jsondecoder

#json #swift #несоответствие типов #jsondecoder

Вопрос:

Вот моя проблема, когда я получаю некоторый 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 , в любом случае что-то написать один раз, а не в каждой структуре.

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

Комментарии:

1. Вы пытались объявить var bar : Any? , потому что это может быть любой, как вы его описываете.

2. К сожалению, это не то, чего я хочу. Я хочу, чтобы поле заполнялось только тогда, когда оно является Int . Более того, я действительно не хочу оставлять строгий тип, устанавливая Any везде.

Ответ №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 для Int s, но это будет применяться ко всему приложению:

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

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

Комментарии:

1. Это действительно умная идея. Но я искал что-то более общее на стороне декодирования, без необходимости касаться структур модели. Но мне все равно это нравится.

2. Спасибо за ваше время… Второй вариант также умный, даже если вам нужно написать его для каждого типа, и, как вы говорите, для всего приложения.