Swift Decodable - как избежать универсального типа?

Aug 19 2020

Я получаю сложный вложенный объект из своего JSON REST API.

DocumentDraft
 - uuid: String
 - schema: Schema // Very complicated object with many variations
 - url: String
 - values: [Value]
 - successors: [String]
 - predecessors: [String]

Value
 - key: String
 - val: String? OR [String]?   // <-- This is the problem

Я полагаю, что правильный способ справиться с этим - ввести общий тип.

struct Value<V: Decodable>: Decodable {
  let key: String
  let val: V?
}

... но даже в этом случае это valuesможет быть смешанный массив, поэтому я не понимаю, как объявление того, что это Vтакое, может помочь.

Но затем, конечно же, общий тип распространяется вверх по иерархии, к DocumentDraftобъекту, к издателю, к моим вызовам API и т. Д., Загрязняя всю цепочку очень чистых и удобочитаемых вызовов и объектов. Я хотел бы разобраться с этим только на уровне Value, и пусть JSONDecoder просто каким-то образом вернет один из двух.

Есть еще один способ борьбы с этими двумя возможностями Факультативного valлибо как Stringили [String]без изменения всей родительского объекта?

Ответы

4 gcharita Aug 19 2020 at 12:31

Вы можете добиться этого, используя только [String]тип и вручную реализовав init(from:)функцию Decodableпротокола следующим образом:

struct Value: Decodable {
    let key: String
    let val: [String]?
    
    enum CodingKeys: String, CodingKey {
        case key, val
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        key = try container.decode(String.self, forKey: .key)
        do {
            if let string = try container.decodeIfPresent(String.self, forKey: .val) {
                val = [string]
            } else {
                val = nil
            }
        } catch DecodingError.typeMismatch {
            val = try container.decodeIfPresent([String].self, forKey: .val)
        }
    }
}

При успешном декодировании в Stringзначение создайте массив строк только с одним элементом. Когда декодирование в Stringзначение не удается, попробуйте декодировать как[String]