Swift Decodable-ジェネリック型を回避する方法は?

Aug 19 2020

JSON RESTAPIから複雑なネストされたオブジェクトを取得しています。

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が2つのうちの1つを何らかの方法で返すようにします。

親オブジェクト全体を変更するvalString[String]変更せずに、オプションの2つの可能性を処理する別の方法はありますか?

回答

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値へのデコードが成功したら、要素が1つだけの文字列の配列を作成します。String値へのデコードが失敗した場合は、次のようにデコードしてみてください[String]