Written by Reinder de Vries on January 20 2021 in アプリ開発, iOS, Swift
JSON などのカスタム データ形式をネイティブ Swift オブジェクトにエンコードおよびデコードするには、Swift で Codable
を使用することができます。 単に Codable
プロトコルを採用することにより、Swift オブジェクトを JSON データに、またはその逆にマップすることは驚くほど簡単です。
実用的な iOS 開発者として、あなたは遅かれ早かれ JSON に出くわすでしょう。 Facebook から Foursquare まで、すべての Web サービスは JSON フォーマットを使用して、アプリケーションにデータを送受信します。
このチュートリアルでは、Codable
プロトコルを使用して、Swift で JSON オブジェクトを操作する方法を学びます。 学習する内容は、他のデータ形式にも簡単に適用することができます。 JSONEncoder
と JSONDecoder
によるエンコードとデコードに入り、JSON と Swift のデータを仲介する方法をお見せします。 行きましょう。
- Get Started: エンコードとデコード
- The Codable Protocol Explained
- Working with Codable
- Decoding JSON to Swift Objects with Codable
- Working with Nested Array and Codable
- Further Reading
Encoding Swift Objects as JSON with Codable
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 の String
、Int
、Double
、URL
、Date
、Data
、Array
および Dictionary
値を取って、それらを JSON としてエンコードすることができます。 次にそれらをウェブサービスに送ると、ウェブサービスはその値を理解できるネイティブな形式にデコードします。 同様に、Web サービスは JSON としてエンコードされたデータをアプリに送信し、あなたはデータを String
、Double
、Array
などのネイティブ型にデコードします。
あなたのレシピアプリが JSON を受け取るとき (上記参照)、それは次に、次のような Swift 構造体にデコードすることができます。 JSON データが Swift が理解する形式にデコードされるので、これはデコードと呼ばれます。 それは、JSON として Swift オブジェクトをエンコードすることです。
Swift の Codable
プロトコルは、実際には Decodable
と Encodable
プロトコルのエイリアスです。 エンコードとデコードを一緒に使うことが多いので、両方のプロトコルを一度に取得するために 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
の本当の魔法は、Decoder
とEncoder
プロトコルで発生する。 これらのプロトコルは、JSON のようなさまざまなデータ形式をエンコード/デコードするコンポーネントによって採用されています。 Swift では、いくつかの -coders:
-
PropertyListEncoder
とPropertyListDecoder
は .plist のプロパティリストのため -
JSONEncoder
とJSONDecoder
は JSON のため – これが私たちです! -coders:-
PropertyListEncoder
は JSON のため、そしてJSONDecoder
は JSON のため – これが私たちです。 - NSKeyedArchiver は
Codable
で動作し、PropertyListEncoder
、Data
および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:)
関数を呼び出して、jsonString
をData
オブジェクトに変換します。 - 次に、
JSONDecoder
オブジェクトを作成し、それに対してすぐにdecode(_:from:)
関数を呼び出す。 これは、JSONをデコードすることで、jsonData
をUser
型のオブジェクトに変える。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_name
やfirstName
など、JSON のプロパティが Swift 構造体のプロパティと異なる場合はどうしたらよいでしょうか。Codable
に準拠するすべてのクラスまたは構造体は、CodingKeys
と呼ばれる特別なネストされた列挙を宣言することができます。 これを使用して、エンコードおよびデコードされるべきプロパティを、その名前も含めて定義します。例を見てみましょう。 下の
User
構造体では、プロパティ名をsnake_caseからcamelCaseに変更しました。 たとえば、キーfirst_name
はfirstName
.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
オブジェクトを JSONData
オブジェクトにエンコードします - 最後に、 オブジェクトを 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 の
country
、first_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 データ構造に対する素晴らしい汎用的なアプローチです。
User
やTweet
のような具体的な型で簡単に表現できない JSON のビットに遭遇したら、Response
やUserCollection
のような型に拡大してみてください。 より深くするのではなく、より広く、完全な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
-