🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1144閲覧

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

sharman

総合スコア6

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2019/10/10 08:24

前提・実現したいこと

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

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

OKの時↓

Json

1{"result": { 2 "message": "", 3      "response": { 4 "data":[{ 5 "xxx": "xxx", 6 "yyy": "yyy" 7 }] 8 }, 9 "status": "OK" 10 } 11}

NGの時↓

Json

1{"result": { 2 "message": "エラーメッセージ", 3       "response": "", 4 "status": "NG" 5 } 6}

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

受け取り側では、

Swift

1struct Response: Codable { 2 let result: Result 3 4 struct Result: Codable { 5 let message: String 6 let response: Response 7 let status: String 8 9 struct Response: Codable { 10 let xxx: String 11 let yyy: String 12 } 13 } 14}

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

Swift

1let 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を使った方法など調べたのですが、私の場合に当てはまらずうまくいきませんでした。

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

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

swift

1 2struct XY: Codable { 3 let xxx: String 4 let yyy: String 5} 6 7struct XYs: Codable { 8 let data: [XY] 9} 10 11enum ResponseData: Decodable { 12 case error(String) 13 case data(XYs) 14 15 init(from decoder: Decoder) throws { 16 17 self = try Result<ResponseData, Error> { 18 try .data(decoder.singleValueContainer().decode(XYs.self)) 19 } 20 .flatMapError { _ in 21 Result { try .error(decoder.singleValueContainer().decode(String.self)) } 22 } 23 .get() 24 } 25} 26 27struct ResultData: Decodable { 28 let message: String 29 let response: ResponseData 30 let status: String 31} 32 33struct Response: Decodable { 34 let result: ResultData 35} 36

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


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


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


追記(10/17)

swift

1let response: Response = /// ここにデコードした値が入っているとします 2 3switch response.result.response { 4case let .result(responseData): 5 // データが取れたときの処理。 responseDataにXYsの値が入っている 6case let .error(error): 7 // エラーの時の処理。 errorにStringの値が入っている 8}

投稿2019/10/10 09:31

編集2019/10/17 03:00
MasakiHori

総合スコア3391

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sharman

2019/10/16 10:20 編集

ご連絡遅くなり申し訳ありません。 回答いただいたお手本を元に書き直してみました。 ①case data(Success)の部分で 「Use of undeclared type 'Success'」 ②try .data(decoder.singleValueContainer().decode(XYs.self))の部分で 「Ambiguous reference to member 'decode'」 がそれぞれ出ました。 ①Successはどこから出てきたクラスですか? ②多分Encoderのメソッドと区別がつかないのでしょうか?  decoderはDecoderクラスとはっきりしていると思うのですが、なぜこのエラーが出るかわかりません。 どちらも解決策が浮かばないのですが、原因わかりますでしょうか?
MasakiHori

2019/10/16 12:46

もうしわけない。回答欄上で型名を変更したのが失敗でした。 修正しています。
sharman

2019/10/17 02:13

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

2019/10/17 03:03

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

2019/10/17 06:28

追記ありがとうございます。参考にさせていただきました。 成功時はXYs型で受け取っていること、失敗時はString型で受け取っていること、どちらも確認できました。 「Accociated Value」というものを初めて知りました。大変勉強になりました。 ご丁寧にお答え頂き、本当にありがとうございました。 同じことで悩んでここに来る人がいるかもしれないので念の為。 追記の let response: Response = /// ここにデコードした値が入っているとします switch response.result.response { は、変数responseをそのまま使用して switch response { ということでいいですか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問