Working with Codable and JSON in Swift

Written by Reinder de Vries on January 20 2021 in アプリ開発, iOS, Swift

Working with Codable and JSON in Swift

JSON などのカスタム データ形式をネイティブ Swift オブジェクトにエンコードおよびデコードするには、Swift で Codable を使用することができます。 単に Codable プロトコルを採用することにより、Swift オブジェクトを JSON データに、またはその逆にマップすることは驚くほど簡単です。

実用的な iOS 開発者として、あなたは遅かれ早かれ JSON に出くわすでしょう。 Facebook から Foursquare まで、すべての Web サービスは JSON フォーマットを使用して、アプリケーションにデータを送受信します。

このチュートリアルでは、Codable プロトコルを使用して、Swift で JSON オブジェクトを操作する方法を学びます。 学習する内容は、他のデータ形式にも簡単に適用することができます。 JSONEncoderJSONDecoder によるエンコードとデコードに入り、JSON と Swift のデータを仲介する方法をお見せします。 行きましょう。

  1. Get Started: エンコードとデコード
  2. The Codable Protocol Explained
  3. Working with Codable
  4. Decoding JSON to Swift Objects with Codable
  5. Encoding Swift Objects as JSON with Codable

  6. Working with Nested Array and Codable
  7. Further Reading

Get Started: エンコードとデコード

SwiftのCodableプロトコルは、実際にどのような問題を解決するのでしょうか? 例から始めましょう。

レシピのアプリを構築していると想像してください。 アプリは、材料、手順、および食品に関する基本情報を含む、リスト内のさまざまなレシピを表示します。

あなたは、Web サービス、およびそのクラウドベースの API からアプリ用のデータを取得します。 この API は、JSON データ形式を使用します。 Web サービスからレシピを要求するたびに、JSON データが返されます。

以下は、レシピの JSON データの例です:

{ "name": "Spaghetti Bolognese", "author": "Reinder's Cooking Corner", "url": "https://cookingcorner.com/spaghetti-bolognese", "yield": 4, "ingredients": , "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"}

JSON データの構造を見てみましょう。 プロパティ名は文字列で、引用符 " で囲まれています。 JSONの値は、文字列、数値(引用符なし)、配列、その他のオブジェクトが使用できます。 508>

JSON はテキスト ベースのデータ形式で、Twitter、Facebook、Foursquare などの API を含む多くの Web サービスで使用されています。 Web ベースのリソースを使用するアプリケーションを構築する場合、JSON に遭遇することになります。

JSON形式は、効率的で簡単に解析でき、人間が読むことができるため、一般的な代替物であるXMLよりも優れています。 JSON は、Web サービス、API、およびアプリケーションのための合意されたフォーマットです。 JSON は、シンプルで柔軟なフォーマットであるため、Web、アプリケーション、オンライン サービス全体で使用されています。 このエンコードとデコードのプロセスが、JSON を非常に強力なものにしています。

あなたは Swift の StringIntDoubleURLDateDataArray および Dictionary 値を取って、それらを JSON としてエンコードすることができます。 次にそれらをウェブサービスに送ると、ウェブサービスはその値を理解できるネイティブな形式にデコードします。 同様に、Web サービスは JSON としてエンコードされたデータをアプリに送信し、あなたはデータを StringDoubleArray などのネイティブ型にデコードします。

あなたのレシピアプリが JSON を受け取るとき (上記参照)、それは次に、次のような Swift 構造体にデコードすることができます。 JSON データが Swift が理解する形式にデコードされるので、これはデコードと呼ばれます。 それは、JSON として Swift オブジェクトをエンコードすることです。

Swift の Codable プロトコルは、実際には DecodableEncodable プロトコルのエイリアスです。 エンコードとデコードを一緒に使うことが多いので、両方のプロトコルを一度に取得するために Codable プロトコルを使用します。

エンコード/デコードワークフローの中心は、Swift の Codable プロトコルです。 次はその仕組みを調べてみましょう!

「エンコード」と「デコード」の区別がつかない? こんな風に考えてみてください。 エニグマ機や秘密の暗号のように、データを「コード」から「コード」に変換しているのです。 エンコードとは、データをコードに変換することで、「エンコード」、つまり「コードの中/中で」変換することです。 508>

Get hired as an iOS developer

Learn to build iOS 14 apps with Swift 5

Sign up for my iOS development course, and learn how to start your career as a professional iOS developer.

The Codable Protocol Explained

Swift 4 より前の JSON を使用するのは少し大変でした。 あなたは、JSONSerialization で JSON を自分でシリアライズし、正しい Swift の型に JSON のすべてのプロパティを型キャストする必要がありました。 これは非常に冗長であり、潜在的なエラーや型の不一致に適切に対応することは困難です。 SwiftyJSON のようなライブラリは、JSON での作業を非常に簡単にしますが、まだ、JSON データをそれぞれの Swift オブジェクトとプロパティにマップする必要があります。 Swift の構造体やクラスは、単に Codable プロトコルを採用する必要があり、無料で、箱から出して JSON エンコードとデコードを取得します。 どちらのプロトコルもかなりミニマルなもので、それぞれinit(from: Decoder)encode(to: Encoder)という関数を定義しているだけです。 言い換えれば、これらの関数は、型が「デコード可能」または「エンコード可能」であるために、「何かからデコード」し、「何かにエンコード」する必要があることを意味する。

Codableの本当の魔法は、DecoderEncoderプロトコルで発生する。 これらのプロトコルは、JSON のようなさまざまなデータ形式をエンコード/デコードするコンポーネントによって採用されています。 Swift では、いくつかの -coders:

  • PropertyListEncoderPropertyListDecoder は .plist のプロパティリストのため
  • JSONEncoderJSONDecoder は JSON のため – これが私たちです! -coders:
    • PropertyListEncoder は JSON のため、そして JSONDecoder は JSON のため – これが私たちです。
    • NSKeyedArchiver は Codable で動作し、PropertyListEncoderData および NSCoding を経由します。

    JSONDecoder および JSONEncoder クラスは、これらの Decoder および Encoder プロトコルを使用して JSON をデコード/エンコードする機能を提供します。 これは、Decoder および Encoder プロトコルを採用すれば、Codable 用の独自のエンコーダー/デコーダーを記述できることも意味します!

    Swift のプロトコルについて再確認が必要ですか? 508>

    Working with Codable

    Let’s take a look at an example. いくつかの JSON データを Swift の構造体にマッピングするつもりです。

    最初に、User と呼ばれる構造体を作成します。

    struct User: Codable { var first_name: String var last_name: String var country: String}

    この User 構造体は、String 型の 3 つの単純なプロパティを持っており、Codable プロトコルに準拠しています。

    次に、JSON を少し書きましょう。 通常、JSON データは Web サービス リクエストの応答として、または .json ファイルを介してアプリケーションに入りますが、この例では JSON を単純に String に記述しています。 次のようにします。

    let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""

    注: 上記のコードでは、トリプル クォート """ を使用して複数行の文字列を作成しています。

    次に行うことは、JSON をデコードして User オブジェクトに変換することです。

    let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe

    ここで何が起こるかというと、

    • 最初に、文字列に対して data(using:) 関数を呼び出して、jsonStringData オブジェクトに変換します。
    • 次に、JSONDecoderオブジェクトを作成し、それに対してすぐにdecode(_:from:)関数を呼び出す。 これは、JSONをデコードすることで、jsonDataUser型のオブジェクトに変える。 User.self型はパラメータとして提供されます。
    • 最後に、print(user.last_name)でユーザーのラストネームをプリントアウトしています。 その user 値は、その型として User を持っているので、実際の Swift オブジェクトです!

    簡単でしょう? あなたは本質的に Swift の構造体に JSON オブジェクトを「マッピング」し、Swift が動作できるネイティブオブジェクトに JSON フォーマットをデコードしました。

    上記のコードでは、decode(_:from:)try! によって投げられたどんなエラーも無視しています。 自分のコードでは、do-try-catch ブロックでエラーを処理する必要があります。 投げられる可能性のあるエラーは、たとえば、無効な JSON を提供した場合に発生します。

    この User.self はメタタイプで、タイプ User 自体への参照です。 508>

    User 構造体のような Codable 型を定義する際によくあるエラーは、キーとプロパティの不一致です。 JSON に存在しない、または異なる型を持つプロパティを構造体に追加した場合、JSON をデコードするときにエラーが発生します。 その逆、つまり、構造体には存在しないが、JSONには存在するプロパティは問題ではありません。 つまり、JSONがエラーなしでデコードできなくなるまで、プロパティを追加し続けることが最も簡単なデバッグ方法です。 どのプロパティ/キーを含めるか、または無視するかは、CodingKeys 列挙型 (下記参照) で決定できます。

    Codable で JSON を Swift オブジェクトにデコードする

    前のセクションの例をもう一度見て、展開しましょう。 次に、それを Data オブジェクトに変換します。 このように:

    let jsonData = jsonString.data(using: .utf8)!

    これは必要な中間ステップです。 JSON を文字列として表現するのではなく、ネイティブの Data オブジェクトとして保存します。 508>

    よく見ると、上記のコードは data(using:) からのオプションの戻り値で動作するように強制アンラッピングを使用していることがわかります。 そのオプショナルをもっとエレガントにアンラップしてみましょう!

    if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }

    上記のコードで、data(using:)nil を返すときにエラーに対応することができます。 たとえば、エラーメッセージを表示したり、タスクを失敗させてログに診断情報を保存したりできます。

    次に、新しい JSONDecoder オブジェクトを作成します。 このように:

    let decoder = JSONDecoder()

    次に、そのデコーダーを使用して JSON データをデコードしています。 このように:

    let user = try! decoder.decode(User.self, from: jsonData)

    しかし、decode(_:from:)関数はエラーを投げることがあります。 それが発生するたびに、上記のコードはクラッシュします。 ここでも、起こりうるすべてのエラーに対応したいと思います。 このように:

    do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}

    そして、コードブロック全体は次のようになります。 どう違うかわかりますか?

    if let jsonData = jsonString.data(using: .utf8){ let decoder = JSONDecoder() do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name) } catch { print(error.localizedDescription) }}

    決してエラーを黙殺しないでください。 エラーをキャッチし、UI/UX、タスクの再試行、またはログ、クラッシュ、および修正によって、それに対応します。

    Working with CodingKeys

    first_namefirstName など、JSON のプロパティが Swift 構造体のプロパティと異なる場合はどうしたらよいでしょうか。

    Codable に準拠するすべてのクラスまたは構造体は、CodingKeys と呼ばれる特別なネストされた列挙を宣言することができます。 これを使用して、エンコードおよびデコードされるべきプロパティを、その名前も含めて定義します。

    例を見てみましょう。 下のUser構造体では、プロパティ名をsnake_caseからcamelCaseに変更しました。 たとえば、キー first_namefirstName.

    struct User:Codable { var firstName: String var lastName: String var country: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case country }}

    上の構造体を前のセクションの例と一緒に使用すると、新しいプロパティ名を持つ User オブジェクトを使用できることがわかります。 次のようになります。

    print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom

    この CodingKeys 列挙体は、そのケースをプロパティにマッピングし、JSON データで正しいプロパティ名を選択するために文字列値を使用します。 列挙型のケースは構造体で使用したいプロパティの名前であり、その値は JSON データ内のキーの名前です。

    Codable で Swift オブジェクトを JSON としてエンコードする

    これまで、JSON データを Swift オブジェクトにデコードすることに焦点を当てました。 他の方法はどうでしょうか? オブジェクトを JSON としてエンコードすることもできるのでしょうか? はい、そうです!

    var user = User()user.firstName = "Bob"user.lastName = "and Alice"user.country = "Cryptoland"let jsonData = try! JSONEncoder().encode(user)let jsonString = String(data: jsonData, encoding: .utf8)!print(jsonString)

    そして、出力は次のとおりです:

    {"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}

    ここで何が起こるのでしょうか?

    • まず、User オブジェクトを作成し、そのプロパティにいくつかの値を割り当てます
    • 次に、encode(_:) を使用して user オブジェクトを JSON Data オブジェクトにエンコードします
    • 最後に、 オブジェクトを JSON にエンコードします。 Data オブジェクトを String に変換して出力する

    よく見ると、エンコードはデコードと同じ手順を踏んでいますが、逆になっていることがわかりますね。 Swift オブジェクトからデコーダーを通過して、JSON 文字列になります。

    以前と同様に、エラーを処理するために例を拡張することができますか? はい、できます。 次のようにします。

    let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinteddo { let jsonData = try encoder.encode(user) if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }} catch { print(error.localizedDescription)}

    上記の例では、JSON データを「きれいに印刷」するために、エンコーダーの outputFormatting プロパティを使用しています。 これは、JSON文字列を読みやすくするために、スペース、タブ、および改行を追加するものです。 このように:

    { "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}

    Awesome!

    Swift で、そして JSON で、辞書は固定のソート順を持っていないことに注目する価値があります。 上記のコードを数回実行すると、JSON の countryfirst_name などのキーのソート順が変化するのはそのためです。 JSON を出力するほとんどの Web サービスは、スキーマによって固定のソート順を強制するため、JSON を読みやすく、ナビゲーションしやすくなります。 しかし、扱うデータがより複雑な場合はどうでしょうか。

    たとえば、Twitter や Facebook の Web サービスから戻ってくる JSON データは、しばしば入れ子構造になっています。 つまり、取得したいデータは、2~3 レベルの JSON 配列や辞書に埋もれています。 今度は何でしょう!

    まず、簡単な例を見てみましょう。 次に、デコードしたい JSON データがあります。

    let jsonString = """"""

    よく見ると、必要なデータが配列として格納されていることがわかります。 JSONは1つの単純なオブジェクトではなく、3つのUserオブジェクトを含む配列になっています。 角括弧の(配列)と角括弧の{ }(オブジェクト)があるので、オブジェクトの配列であることがわかる。 508>

    これらのUserオブジェクトをどのようにデコードすればよいのだろうか。 508>

    let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}

    上記のコード スニペットは、以前に見たものと非常によく似ていますが、重要な違いが 1 つあります。 decode(_:from:) 関数に提供している型がわかりますか? この型は (.self付き)、つまり「ユーザー オブジェクトの配列」です。 1 つの User オブジェクトを扱うのではなく、それらの束をデコードしたいので、 配列型を指定します。

    次は、より複雑な入れ子型を扱う方法について説明します。 たとえば、User オブジェクトの配列が JSON 辞書の内部にネストされていると考えてください。 次のようになります。

    let jsonString = """{ "users": }"""

    上記の JSON スニペットでは、最上位の要素は辞書 (または「オブジェクト」) です。 これは、1つのキーと値のペアを持つだけです。 をusers配列に変換します。 欲しいデータはその配列の中にある。 508>

    あまり複雑なアプローチにしないことが重要です。 高度な JSON 解析が必要だと思いがちですが、結局のところ、実際には Swift の構造体をネストすることもできます。 私たちは、全体の JSON を struct として記述し、その中に User struct を記述します。

    ここで、これをチェックしてみましょう:

    struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }

    ここで、トップレベルの JSON 辞書は、Response 型に対応します。 以前と同様に、この型は JSON のエンコードとデコードをサポートするために Codable に準拠しています。

  • この構造体の内部では、User という別の構造体を定義しています。 これは、以前使用したのとまったく同じ型です。
  • Response 構造体は、 型の users という 1 つのプロパティ、またはユーザー オブジェクトの配列を持っています。
  • 素晴らしいことは、意味的には、JSON と Swift データ構造は両方ともまったく同じである、ということです。 彼らは異なる構文を使用するだけです。 Response 構造体がネストされた User 構造体と users プロパティを内部に持つように、私たちはトップレベルの辞書の内部にネストされた配列を定義しています。

    let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}

    Response 型を使用して JSON をデコードし、response.users プロパティをループ処理しているのがわかるでしょうか。 Response 構造体とJSONで確認してみてください。 トップレベル辞書のusersキーと値のペアをピックアップして、中のオブジェクトをUserオブジェクトにマッピングしているんだ。 すっきりした!

    入れ子型を使用することは、複雑な JSON データ構造に対する素晴らしい汎用的なアプローチです。 UserTweet のような具体的な型で簡単に表現できない JSON のビットに遭遇したら、ResponseUserCollection のような型に拡大してみてください。 より深くするのではなく、より広く、完全なJSONを取り込むのです。 入れ子になった型を使って、欲しいデータにたどり着く。 また、ここで注目すべきは、User 構造体を Response 構造体の中に追加する必要はなく、その外側に追加することも可能だということです。

    Get hired as a iOS developer

    Learn to build iOS 14 apps with Swift 5

    Sign up for my iOS development course, and learn how to start your career as a professional iOS developer.Of a iOS開発コースに登録してください。

    Further Reading

    そして、これですべてです! あなたは今知っています。

    • エンコードおよびデコード可能なオブジェクトを作成するための Codable の使用方法
    • JSONオブジェクトとそのSwift対応物をエンコードおよびデコードするための JSONDecoder および JSONEncoder の使用方法
    • エンコーディングとデコーディングが何のためなのか。 そしてなぜそれが日常の iOS 開発で重要なのか。
    • より複雑な JSON データを扱う方法、および入れ子型の使用方法

    もっと学びたいですか? 508>

    • Array, Dictionaries and Structs
    • Working With JSON In Swift With SwiftyJSON
    • Introduction Of Object-Oriented Programming In Swift
    • How To: Swift で配列のアイテムを見つける
    • How To Use Apple’s Developer Documentation For Fun and Profit
    • How To Find Strings With Regular Expressions In Swift
    • Why Why? App Architecture Matters
    • Strings in Swift Explained
    • Working with Files on iOS with Swift
    • Storing Data with NSCoding and NSKeyedArchiver

コメントする