ども、@kimihom です。
Ruby とかの型の緩い言語だと、一つの配列に String や Int を詰め込んでも特に実装の手間にはならないのだけど、そのような環境で作った複数の型のあるJSON配列を、Swift 側で処理するのに少し手間取ったので記事しておこう。
実装の例
たとえば、とある API で以下のようなJSONが返ってきたとしよう。
{ "status": "ok", "data": [ [1, "taro"], [2, "jiro"] ] } { "status": "ng", "message": "何かに失敗" }
ここでの data
は、配列0番目がid で 1番目に名前の配列になっている。これを Swift 側で処理するのと、どうするとなる。。
struct APIResponse: Decodable { let status: String let message: String? let data: [[String]]? // NG let data: [[Int, String]]? // NG }
答え: 独自の型を定義する
enum StringOrIntType: Codable, Hashable { case string(String) case int(Int) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { self = try .string(container.decode(String.self)) } catch DecodingError.typeMismatch { do { self = try .int(container.decode(Int.self)) } catch DecodingError.typeMismatch { throw DecodingError.typeMismatch( StringOrIntType.self, DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "エラー: 期待した型でない" ) ) } } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .int(let int): try container.encode(int) case .string(let string): try container.encode(string) } } }
struct APIResponse: Decodable { let status: String let message: String? let data: [[StringOrIntType]]? }
この型定義をしたJSONを SwiftUI で View に反映するには、以下のようなアクセスが必要となる。
if let data = apiResponse.data { VStack { ForEach(data, id: \.self) {d in if case .int(let id) = d[0], case .string(let name) = d[1] { Text("\(id), \(name)") } } } }
終わりに
こういう型の厳しい言語だと、追加で手間のかかる実装が必要だ。それを型でコードをより固くさせることで安心する人と、そうでない人との違いか。
個人的にはやはり Ruby のような言語が合っている。
参考: Parse JSON Array that contains string and int in Swift 4 - Stack Overflow