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

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

ただいまの
回答率

87.49%

Json 取得する型が異なる場合について

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 658

score 12

前提・実現したいこと

SwiftでぐるなびAPIを利用しています。
https://api.gnavi.co.jp/api/manual/restsearch/
ぐるなびAPIからJSONを利用してデータを取得していますが、下記エラーが出ます。

The data couldn’t be read because it isn’t in the correct format.

Jsonで取得したデータが空の場合の挙動についてまだ理解していないため定かではありませんが、ぐるなびAPIのbudgetの値がある場合はINTとして取得しており、値が空の場合" "になっていることから、Stringで取得してしまっているように見えます。

 {
    "@attributes": {
        "api_version": "v3"
    },
    "total_hit_count": 20832,
    "hit_per_page": 10,
    "page_offset": 1,
    "rest": [
        {
            "@attributes": {
                "order": 0
            },
            "id": "",
            "update_date": "",
            "name": "",
            "name_kana": "",
            "latitude": "",
            "longitude": "",
            "category": "",
            "url": "",
            "url_mobile": "",
            "coupon_url": {
                "pc": "",
                "mobile": ""
            },
            "image_url": {
                "shop_image1": "",
                "shop_image2": "",
                "qrcode": ""
            },
            "address": "",
            "tel": "",
            "tel_sub": "",
            "fax": "",
            "opentime": "",
            "holiday": "",
            "access": {
                "line": "",
                "station": "",
                "station_exit": "",
                "walk": "",
                "note": ""
            },
            "parking_lots": "",
            "pr": {
                "pr_short": "",
                "pr_long": ""
            },
            "code": {
                "areacode": "",
                "areaname": "",
                "prefcode": "",
                "prefname": "",
                "areacode_s": "",
                "areaname_s": "",
                "category_code_l": [
                    "",
                    ""
                ],
                "category_name_l": [
                    "",
                    ""
                ],
                "category_code_s": [
                    "",
                    ""
                ],
                "category_name_s": [
                    "",
                    ""
                ]
            },
            "budget": 3000,  //値がない場合は、"budget": "",
            "party": 4000,
            "lunch": 980,
            "credit_card": "",
            "e_money": "",
            "flags": {
                "mobile_site": 1,
                "mobile_coupon": 1,
                "pc_coupon": 1
            }
        },
        // MARK: - REST
    struct REST: Codable {
        let id: String
        let budget:Int

        enum CodingKeys: String, CodingKey {
            case id = "id"
            case budget = "budget"
        }
    }


Stringで定義してしまうと、値があるときにINTとなってしまうためエラーで出てしまい、
Intで定義してしまうと、値がない時にStringとなってしまいエラーが出てしまうため、どうすれば良いかわからない状況です。

またコード自体の書き方に問題がある可能性もあるため、解決方法をご教授いただけば幸いです。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • hoshi-takanori

    2020/02/24 19:49

    情報が少なくて分かりません。「Stringで取得してしまっているように見えます」って、あなたがそういうコードを書いてるのでは?

    キャンセル

  • ko-ru

    2020/02/24 20:11

    情報が少ないということで失礼しました。
    コード等を追加いたしましたのでミス等があれば教えていただきたいです。

    キャンセル

回答 1

checkベストアンサー

+1

とりあえずこれでどうでしょう?

enum StrOr<T: Codable>: Codable {
    case str(String)
    case val(T)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(String.self) {
            self = .str(value)
        } else {
            let value = try container.decode(T.self)
            self = .val(value)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch (self) {
        case .str(let value):
            try container.encode(value)
        case .val(let value):
            try container.encode(value)
        }
    }
}

struct REST: Codable {
    let id: String
    let budget: StrOr<Int>
}

テストコード

func testCodable() {
    testDecode("{ \"id\": \"abc\", \"budget\": 123 }")
    testDecode("{ \"id\": \"abc\", \"budget\": \"123\" }")

    testEncode(REST(id: "abc", budget: .val(123)))
    testEncode(REST(id: "abc", budget: .str("123")))
}

func testDecode(_ json: String) {
    do {
        let decoder = JSONDecoder()
        let rest = try decoder.decode(REST.self, from: json.data(using: .utf8)!)
        print("REST: id = \(rest.id), budget = \(rest.budget)")
    } catch let error {
        print(error)
    }
}

func testEncode(_ rest: REST) {
    do {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        let data = try encoder.encode(rest)
        print(String(data: data, encoding: .utf8)!)
    } catch let error {
        print(error)
    }
}

実行結果

REST: id = abc, budget = val(123)
REST: id = abc, budget = str("123")
{
  "id" : "abc",
  "budget" : 123
}
{
  "id" : "abc",
  "budget" : "123"
}

追記。StrOr<Int> の値を取り出すには switch を使って以下のように書きます。

var rest: REST = ... // 何らかの値を取得したと仮定します。

switch rest.budget {
case .str(let strValue): // strValue という変数名は自由に変えて構いません。
    // 文字列の場合。strValue を使って処理を行う。
case .val(let value):
    // Int の場合。value を使って処理を行う。
}

参考: Swiftのenumについてまとめてみる - Qiita

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/02/27 23:16

    ありがとうございます。
    回答していただいた方法で取得することができました!
    そこで1点質問なのですが、独自で作成した型(StrOr<Int>)をStringに変換することは可能でしょうか?
    教えていただけると幸いです。

    キャンセル

  • 2020/02/28 21:38

    取得から値の取り出しまでできました!
    どうしてもエラーが出てしまって困っていたので助かりました。
    独自に型を宣言して、処理を書いて値を取り出すときはSwitchを使用してStringかIntかに分ける。勉強なりました。ありがとうございました。

    キャンセル

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

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

関連した質問

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