SwiftでJSONを扱おう!たったの10ステップで完全理解

SwiftとJSONを使ったコーディングの画像Swift
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swiftは現代のアプリケーション開発に欠かせない言語となっており、特にiOSやmacOSの開発には不可欠です。

一方、JSONはデータ交換のための軽量なデータフォーマットとして、多くのWebサービスやアプリケーションで使われています。

これら二つの技術を組み合わせることで、データを効率的に扱うことができます。

この記事では、SwiftでのJSONの操作方法を初心者でも理解できるように、手法ごとに分けて詳細に解説していきます。

●SwiftとJSONの基本的な関係

SwiftとJSONの関係を理解するには、それぞれの役割と特性を押さえることが重要です。

○SwiftでのJSONの役割

Swiftでは、APIからのデータ取得や設定ファイルの読み込みなど、様々な場面でJSON形式のデータを扱います。

特にWebサービスとの連携を想定したアプリケーション開発においては、JSONデータの送受信が日常的に行われます。

Swiftには、このJSONデータを直感的に扱うための機能が組み込まれています。

○JSONの基本的な構造

JSONは”Key-Value”のペアでデータを表現します。

基本的な形式は次のようになります。

let jsonSample = """
{
    "name": "田中",
    "age": 25,
    "hobbies": ["読書", "映画鑑賞"]
}
"""

このコードでは、文字列の形式でJSONを表現しています。

この例では、”name”、”age”、”hobbies”というキーにそれぞれの値が割り当てられています。

このように、Swiftで文字列として表現されたJSONデータは、辞書や配列としてアクセスできるようにデコードされることが多いです。

実際にこのコードを実行すると、Swiftの文字列としてjsonSampleが定義され、中にJSON形式のデータが格納されることになります。

このような形で、SwiftではJSONデータを簡単に扱うことができます。

●SwiftでのJSONの扱い方

SwiftでのJSONの扱い方を、具体的なサンプルコードを交えて解説していきます。

○サンプルコード1:SwiftでのJSONデコード

このコードでは、SwiftでJSON形式の文字列をSwiftのデータ型にデコードする方法を表しています。

この例では、JSONの文字列をSwiftの構造体にデコードしています。

import Foundation

// JSON形式の文字列
let jsonString = """
{
    "name": "山田太郎",
    "age": 25
}
"""

// Swiftの構造体
struct Person: Codable {
    var name: String
    var age: Int
}

let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
do {
    let person = try decoder.decode(Person.self, from: jsonData)
    print(person.name) // 山田太郎
    print(person.age)  // 25
} catch {
    print("デコードに失敗しました: \(error)")
}

このコードを実行すると、JSON形式の文字列がSwiftの構造体Personに変換され、”山田太郎”と25がそれぞれ出力されます。

○サンプルコード2:SwiftでのJSONエンコード

このコードでは、Swiftのデータ型をJSON形式の文字列にエンコードする方法を表しています。

この例では、Swiftの構造体をJSONの文字列にエンコードしています。

import Foundation

// Swiftの構造体
struct Person: Codable {
    var name: String
    var age: Int
}

let person = Person(name: "山田太郎", age: 25)
let encoder = JSONEncoder()
do {
    let jsonData = try encoder.encode(person)
    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print(jsonString) // {"name":"山田太郎","age":25}
    }
} catch {
    print("エンコードに失敗しました: \(error)")
}

このコードを実行すると、Swiftの構造体PersonがJSON形式の文字列{"name":"山田太郎","age":25}に変換されて出力されます。

●JSONのデータ形式とSwiftデータ型の対応

JSONとSwiftでのデータ型は、異なるシステムを持っているので、その違いや対応関係をしっかり理解することは非常に重要です。

正しくデータを扱うためには、これらの対応関係を知っておくことが必要となります。

○基本的なデータ型の対応

JSONのデータ形式とSwiftのデータ型との対応は次のようになります。

  • JSONのstring → SwiftのString
  • JSONのnumber → SwiftのInt, Doubleなど
  • JSONのtrue or false → SwiftのBool
  • JSONのnull → Swiftのnil
  • JSONのobject → SwiftのDictionary<String, Any>
  • JSONのarray → SwiftのArray<Any>

このコードでは、JSONのデータ形式とSwiftのデータ型の対応を表しています。

この例では、各JSONデータ型がSwiftでどのように扱われるかを対応表として表しています。

○複雑なデータ構造の扱い

Swiftでは、複雑なデータ構造を表現するための構造体やクラスを使用することができます。

例えば、次のようなJSONを考えてみましょう。

{
    "name": "Taro",
    "age": 20,
    "address": {
        "country": "Japan",
        "city": "Tokyo"
    }
}

このJSONデータをSwiftで扱う場合、次のような構造体を定義することが考えられます。

struct Person {
    var name: String
    var age: Int
    var address: Address
}

struct Address {
    var country: String
    var city: String
}

このコードでは、JSONのデータ構造をSwiftの構造体で表現しています。

この例では、Personという構造体と、その中のaddressという属性をさらに別の構造体Addressで定義しています。

このようにSwiftでは、JSONの複雑なデータ構造も構造体やクラスを使用することで綺麗に扱うことができます。

もちろん、このデータをSwiftの型にデコードするには、Decodableプロトコルを採用する必要があります。

この構造体を利用して、JSONデータをSwiftのデータ型に変換すると、name属性には”Taro”、age属性には20、address属性のcountryには”Japan”、cityには”Tokyo”という値が格納されます。

●SwiftでJSON操作の応用例

SwiftとJSONの組み合わせは、データの取り扱いにおいて非常に強力です。

ここでは、SwiftでのJSON操作の応用例について、サンプルコードと詳細な説明を交えて解説していきます。

○サンプルコード3:SwiftでのJSONからの配列取得

まずは、JSON内に含まれる配列データをSwiftで取得する方法を見ていきましょう。

import Foundation

let jsonString = """
{
    "users": [
        { "id": 1, "name": "Taro" },
        { "id": 2, "name": "Hanako" }
    ]
}
"""

struct User: Decodable {
    let id: Int
    let name: String
}

struct UsersResponse: Decodable {
    let users: [User]
}

do {
    let data = jsonString.data(using: .utf8)!
    let decodedData = try JSONDecoder().decode(UsersResponse.self, from: data)
    for user in decodedData.users {
        print(user.name)
    }
} catch {
    print("デコードに失敗しました。: \(error)")
}

このコードでは、JSONDecoderを使ってJSON形式の文字列からUsersResponse型のオブジェクトをデコードしています。

この例では、”users”というキーに対応する配列データを取得しています。

このコードを実行すると、次のようにユーザーの名前が出力されます。

Taro
Hanako

○サンプルコード4:SwiftでのJSON内のネストされたデータへのアクセス

次に、JSON内のネストされたデータにアクセスする方法について見ていきましょう。

import Foundation

let jsonString = """
{
    "company": {
        "name": "Tech Corp",
        "employees": [
            { "id": 1, "name": "Yamada" },
            { "id": 2, "name": "Tanaka" }
        ]
    }
}
"""

struct Employee: Decodable {
    let id: Int
    let name: String
}

struct Company: Decodable {
    let name: String
    let employees: [Employee]
}

do {
    let data = jsonString.data(using: .utf8)!
    let decodedData = try JSONDecoder().decode(Company.self, from: data)
    print(decodedData.name) // 会社の名前を出力
    for employee in decodedData.employees {
        print(employee.name) // 従業員の名前を出力
    }
} catch {
    print("デコードに失敗しました。: \(error)")
}

このコードでは、Companyという型を用いて、JSON内の”company”というキーに対応するネストされたデータをデコードしています。

この例では、会社の名前と従業員の名前を取得しています。

このコードを実行すると、次のように会社の名前と従業員の名前が出力されます。

Tech Corp
Yamada
Tanaka

○サンプルコード5:SwiftでのJSONの値の変更

JSONデータの中の特定の値を変更する場合、まずはそのJSONデータをSwiftのデータ型にデコードして、変更を加えた後、再びエンコードしてJSON形式に戻します。

下記のサンプルコードは、その流れを表しています。

import Foundation

// JSONのサンプルデータ
let jsonData = """
{
    "name": "山田太郎",
    "age": 25
}
""".data(using: .utf8)!

struct Person: Codable {
    var name: String
    var age: Int
}

// JSONをSwiftのデータ型にデコード
var person = try! JSONDecoder().decode(Person.self, from: jsonData)

// 値の変更
person.age = 26

// Swiftのデータ型をJSONにエンコード
let updatedData = try! JSONEncoder().encode(person)
if let updatedJSONString = String(data: updatedData, encoding: .utf8) {
    print(updatedJSONString)
}

このコードでは、Personという構造体を使って、JSONデータのnameageをSwiftのデータ型にデコードしています。

この例では、ageの値を26に変更しています。

このコードを実行すると、変更されたageの値を持つ新しいJSON文字列が出力されます。

具体的には、{"name":"山田太郎","age":26}という結果が得られるでしょう。

○サンプルコード6:SwiftでのJSONへの新しいデータの追加

JSONデータに新しいキーと値を追加する際も、先ほどの変更の流れと同様に、デコード→変更→エンコードの手順を踏みます。

下記のサンプルコードでは、新たに住所を追加しています。

import Foundation

// JSONのサンプルデータ
let jsonData = """
{
    "name": "山田太郎",
    "age": 26
}
""".data(using: .utf8)!

struct Person: Codable {
    var name: String
    var age: Int
    var address: String?
}

// JSONをSwiftのデータ型にデコード
var person = try! JSONDecoder().decode(Person.self, from: jsonData)

// 新しいデータの追加
person.address = "東京都渋谷区"

// Swiftのデータ型をJSONにエンコード
let updatedData = try! JSONEncoder().encode(person)
if let updatedJSONString = String(data: updatedData, encoding: .utf8) {
    print(updatedJSONString)
}

このコードでは、Person構造体にaddressという新しいオプショナルの文字列型のプロパティを追加しています。

この例では、addressに”東京都渋谷区”を設定しています。

このコードを実行すると、addressの値が追加された新しいJSON文字列が出力されます。

具体的には、{"name":"山田太郎","age":26,"address":"東京都渋谷区"}という結果が得られるでしょう。

○サンプルコード7:SwiftでのJSONからのデータの削除

JSONを操作する際、特定のデータを削除する必要がしばしばあります。

SwiftでJSONデータから要素を削除する方法を詳しく学びましょう。

このコードでは、SwiftのDictionary型を使ってJSONのキーと値のペアを管理し、特定のキーを指定してそれに関連付けられた値を削除するコードを表しています。

この例では、”age”というキーのデータを削除しています。

import Foundation

// サンプルのJSONデータ
var jsonData: [String: Any] = [
    "name": "Taro",
    "age": 25,
    "city": "Tokyo"
]

// "age"というキーのデータを削除
jsonData.removeValue(forKey: "age")

print(jsonData)

上記のコードを実行すると、”age”というキーとそれに関連する値がjsonDataから削除されるため、結果として次のデータが出力されます。

["name": "Taro", "city": "Tokyo"]

○サンプルコード8:SwiftでのJSONファイルの読み込みと保存

Swiftでアプリケーションを開発する際、JSONファイルの読み込みや保存は一般的な操作となっています。

ここでは、SwiftでJSONファイルを読み込み、変更を加えて再び保存する方法を解説します。

このコードでは、FileManagerを使ってアプリのドキュメントディレクトリからJSONファイルを読み込み、その後JSONデータを変更して同じ場所に保存するコードを表しています。

この例では、読み込んだJSONデータに新しいデータを追加して保存しています。

import Foundation

// ファイルのパスを取得
let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let jsonFilePath = documentDir.appendingPathComponent("sample.json")

do {
    // JSONファイルの読み込み
    let data = try Data(contentsOf: jsonFilePath)
    var jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]

    // JSONデータの変更
    jsonData["newKey"] = "newValue"

    // 変更を加えたJSONデータを保存
    let newData = try JSONSerialization.data(withJSONObject: jsonData, options: .prettyPrinted)
    try newData.write(to: jsonFilePath)

    print("JSONファイルを更新しました。")
} catch {
    print("エラー: \(error.localizedDescription)")
}

上記のコードを実行すると、指定されたパスに存在する”sample.json”という名前のJSONファイルが読み込まれ、新しいデータが追加された後、再び同じ場所に保存されます。

エラーが発生した場合、エラーメッセージが出力されます。

●JSON操作時の一般的なエラーと対処法

SwiftでJSONを操作する際、さまざまなエラーが生じることがあります。

エラーの原因やその対処法を知ることで、スムーズにJSON操作を行うことができるようになります。

ここでは、一般的なエラーとその対処法について説明します。

○型の不一致やキーの不在によるエラー

SwiftでJSONをデコードする際、最もよく遭遇するエラーの一つが、型の不一致やキーの不在です。

JSONのデータとSwiftのデータ型が一致しない場合や、期待しているキーがJSONに存在しない場合にこのエラーが発生します。

このコードでは、Swiftの構造体を使用してJSONデータをデコードするコードを表しています。

この例では、Userという構造体を定義し、JSONデータをデコードしています。

import Foundation

struct User: Decodable {
    let id: Int
    let name: String
    let age: Int
}

let jsonData = """
{
    "id": 1,
    "name": "Taro",
    "age": "25"
}
""".data(using: .utf8)!

do {
    let user = try JSONDecoder().decode(User.self, from: jsonData)
    print(user)
} catch {
    print("エラー: \(error)")
}

上記のコードを実行すると、ageの型がStringとして指定されているため、デコード時に型の不一致のエラーが発生します。

このような場合、エラーメッセージをよく読み、型の不一致やキーの不在を確認し、適切に修正する必要があります。

○デコード時のエラーハンドリング

デコード時には、前述の型の不一致やキーの不在だけでなく、さまざまなエラーが考えられます。

エラーハンドリングを行うことで、具体的なエラーの内容を知ることができ、問題の解決が容易になります。

このコードでは、Swiftのdo-catch構文を使用してエラーハンドリングを行っています。

この例では、意図的にJSONデータのキーを間違えて指定し、エラーを引き起こしています。

import Foundation

struct Profile: Decodable {
    let userId: Int
    let username: String
}

let jsonData = """
{
    "id": 1,
    "name": "Taro"
}
""".data(using: .utf8)!

do {
    let profile = try JSONDecoder().decode(Profile.self, from: jsonData)
    print(profile)
} catch let DecodingError.keyNotFound(key, context) {
    print("キー'\(key.stringValue)'が見つかりません: \(context.debugDescription)")
} catch let DecodingError.typeMismatch(type, context) {
    print("型'\(type)'が一致しません: \(context.debugDescription)")
} catch {
    print("その他のエラー: \(error)")
}

上記のコードを実行すると、"キー'userId'が見つかりません"というエラーメッセージが表示されます。

これにより、JSONデータ内のキーとSwiftの構造体のプロパティ名が一致していないことがわかります。

○エンコード時のエラーハンドリング

エンコード時にも、さまざまなエラーが発生する可能性があります。

エンコード時のエラーハンドリングは、デコード時と同様にdo-catch構文を使用して行います。

このコードでは、Swiftの構造体を使用してJSONデータをエンコードするコードを表しています。

この例では、UserDetailという構造体を定義し、エンコードしています。

ただし、UserDetail構造体はEncodableプロトコルを採用していないため、エンコード時にエラーが発生します。

import Foundation

struct UserDetail {
    let id: Int
    let email: String
}

let userDetail = UserDetail(id: 1, email: "taro@example.com")

do {
    let encodedData = try JSONEncoder().encode(userDetail)
    let jsonString = String(data: encodedData, encoding: .utf8)!
    print(jsonString)
} catch {
    print("エンコード時のエラー: \(error)")
}

上記のコードを実行すると、UserDetailがEncodableプロトコルを採用していないため、エンコードできないというエラーが発生します。

このような場合、構造体にEncodableプロトコルを採用することでエラーを解消できます。

●SwiftとJSONの高度なテクニック

Swiftを使用してJSONを操作する際、基本的なデコードやエンコードだけではなく、より高度な操作が求められることがあります。

高度なテクニックを理解し、適切に使用することで、より複雑なJSONデータの取り扱いや、特定のデータ変換が必要な場合でも、SwiftでのJSON操作が容易となります。

ここでは、カスタムデコーダーとカスタムエンコーダーの使用方法を詳細に解説します。

○サンプルコード9:Swiftでのカスタムデコーダーの利用

SwiftでのJSONデコード時に、特定のデータ変換や処理を行いたい場合、カスタムデコーダーを利用することが考えられます。

下記のコードは、カスタムデコーダーを利用して、JSONからのデータ変換を行う例を表しています。

import Foundation

struct Person: Decodable {
    var name: String
    var birthDate: Date
}

let json = """
{
    "name": "山田太郎",
    "birthDate": "1990-01-01"
}
""".data(using: .utf8)!

let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter
}()

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)

do {
    let person = try decoder.decode(Person.self, from: json)
    print(person.name)  // 山田太郎
    print(dateFormatter.string(from: person.birthDate))  // 1990-01-01
} catch {
    print("デコードエラー: \(error)")
}

このコードでは、Personという構造体を定義しており、その中にnamebirthDateというプロパティが存在しています。

birthDateはDate型であり、通常のデコード処理ではString型としての日付を直接Date型に変換することはできません。

そのため、カスタムデコーダーであるdateDecodingStrategyを用いて、日付のフォーマットを指定し、StringからDateへの変換を行っています。

このように、カスタムデコーダーを活用することで、JSONデータの特定の形式や内容に応じたデコード処理を簡単に実現することができます。

○サンプルコード10:Swiftでのカスタムエンコーダーの利用

一方、SwiftでのJSONエンコード時に特定のデータ変換や処理を行いたい場合は、カスタムエンコーダーを利用します。

下記のコードは、カスタムエンコーダーを利用して、JSONへのデータ変換を行う例を表しています。

import Foundation

struct Person: Encodable {
    var name: String
    var birthDate: Date
}

let person = Person(name: "山田太郎", birthDate: Date())

let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter
}()

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .formatted(dateFormatter)

do {
    let jsonData = try encoder.encode(person)
    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print(jsonString)  // {"name":"山田太郎","birthDate":"2023-10-27"}
    }
} catch {
    print("エンコードエラー: \(error)")
}

このコードでは、前述のPerson構造体をエンコードする際に、カスタムエンコーダーであるdateEncodingStrategyを用いて、Date型からString型への変換を行っています。

この方法を採用することで、Date型のデータを特定のフォーマットのString型としてJSONにエンコードすることが可能となります。

まとめ

Swiftを使用してJSONを操作する過程で、基本的なデコードやエンコードの方法だけでなく、より高度なテクニックを取り入れることが重要であることがわかりました。

特に、カスタムデコーダーやカスタムエンコーダーを利用することで、JSONデータの特定の形式や内容に応じた効率的なデコード・エンコードが可能となり、より複雑なデータの取り扱いをスムーズに行うことができます。

この記事を通して、SwiftでのJSONの扱い方に関する基本から高度なテクニックまでの知識を深めることができたと思います。

今後Swiftでアプリ開発やデータ処理を行う際、この知識を活用して、より効率的かつ正確にデータの取り扱いを行ってください。