質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.63%

SwiftのCodableでJSONデコード時、特定の項目が場合によって型が異なるため、どちらにも対応させたい。

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 955

sharman

score 6

前提・実現したいこと

今週からSwiftでアプリ開発をはじめたsharmanと言います。
APIからJson形式でデータを取得するのですが、そのAPIの処理結果がOKの時と、NGの時とで特定の項目のみ、オブジェクトか文字列かで型が異なります。

OK:オブジェクトが返ってくるため、それに合わせたクラスの型
NG:空文字

OKの時↓

{"result": {
          "message": "",
      "response": {
                        "data":[{
                                  "xxx": "xxx",
                                  "yyy": "yyy"
                                 }]
                      },
           "status": "OK"
           }
}

NGの時↓

{"result": {
           "message": "エラーメッセージ",
       "response": "",
           "status": "NG"
           }
}

responseが、APIの結果がOKの時は処理結果の情報をオブジェクトで返し、NGの時は空文字を入れてきます。

受け取り側では、

struct Response: Codable {
        let result: Result

        struct Result: Codable {
             let message: String
             let response: Response
             let status: String

             struct Response: Codable {
                let xxx: String
                let yyy: String
             }
       }
}

のようなクラスを用意し、

let json = try decoder.decode(Response.self, from: data)


でデコードしてます。

NGの時にデコードしようとすると以下のエラーが出ます。

エラーメッセージ
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "response", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))


OKの時もNGの時もResponseクラスを使って対応はできないでしょうか?
デコード時に、responseが空文字か否かで処理を分けられないかと、
enumを使った方法など調べたのですが、私の場合に当てはまらずうまくいきませんでした。

初歩的なことかもしれませんが、ご教示いただけますでしょうか。
よろしくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

まあまあ面倒くさいことをしないとダメです。

デコードだけが必要のようなので一部Decoadableにしています。

struct XY: Codable {
    let xxx: String
    let yyy: String
}

struct XYs: Codable {
    let data: [XY]
}

enum ResponseData: Decodable {
    case error(String)
    case data(XYs)

    init(from decoder: Decoder) throws {

        self = try Result<ResponseData, Error> {
            try .data(decoder.singleValueContainer().decode(XYs.self))
        }
        .flatMapError { _ in
            Result { try .error(decoder.singleValueContainer().decode(String.self)) }
        }
        .get()
    }
}

struct ResultData: Decodable {
    let message: String
    let response: ResponseData
    let status: String
}

struct Response: Decodable {
    let result: ResultData
}

コンパイルもしてないけど多分これで動きます。
responseのところがenumになっているので、使うにはenumから取り出す必要があります。


追記:
内側にenumが入るのが嫌であればこんな感じにして一番外側をenumにする。
それでもダメならdo-catchの入れ子を作って対処する。
という感じになります。


型名を変更/修正しました。


追記(10/17)

let response: Response = /// ここにデコードした値が入っているとします

switch response.result.response {
case let .result(responseData):
    // データが取れたときの処理。 responseDataにXYsの値が入っている
case let .error(error):
    // エラーの時の処理。 errorにStringの値が入っている
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/17 11:13

    早急な修正ありがとうございます。コンパイルが通りました。
    これを実際に使うとき、Response.error.rawValueで中身を取ろうとしたのですが、それはできませんでした。
    連続での質問で申し訳ないですが、enumから取り出すというのはどういうやり方があるのでしょうか?

    キャンセル

  • 2019/10/17 12:03

    コメント欄では書けないので回答に追記の形で書いています。
    Accociated Valueを持ったenumからその値を取り出すのは一般的にはswitch-case-letが使われます。
    if-case-let などで取り出す方法もありますし、それを利用してenumに取り出すメソッドを追加するなどもできます。

    キャンセル

  • 2019/10/17 15:28

    追記ありがとうございます。参考にさせていただきました。
    成功時はXYs型で受け取っていること、失敗時はString型で受け取っていること、どちらも確認できました。

    「Accociated Value」というものを初めて知りました。大変勉強になりました。
    ご丁寧にお答え頂き、本当にありがとうございました。

    同じことで悩んでここに来る人がいるかもしれないので念の為。
    追記の
    let response: Response = /// ここにデコードした値が入っているとします
    switch response.result.response {
    は、変数responseをそのまま使用して
    switch response {
    ということでいいですか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • JSONに関する質問
  • SwiftのCodableでJSONデコード時、特定の項目が場合によって型が異なるため、どちらにも対応させたい。