ボクココ

個人開発に関するテックブログ

Swift で複数の型のJSON配列をデコードする方法

ども、@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