SwiftUIでRealmのデータをJSONファイルで入出力する方法に困ったことはありませんか?
ネットで調べても情報が少なく、私も苦戦しました。
そこで、コーディング方法についてまとめましたので、ぜひ参考にしてください。
目次
RealmデータをJSONファイルで出力する方法
JSONオブジェクトへエンコード
JSONEncoderクラスを使うことでデータ型のインスタンスをJSONオブジェクトへ変換することができます。
Realmのデータを使ってデータ型のインスタンス(Codableプロトコルに準拠している構造体)を生成します。
// データ型のインスタンスをJSONオブジェクトとしてエンコードするオブジェクトを設定します。
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
// 各モデルのバックアップするデータを取得します。
let items = Array(Item.all())
let users = Array(User.all())
let companies = Array(Company.all())
// バックアップするデータからデータ型のインスタンスを生成します。
let jsonData = JsonData(items: items, users: users, companies: companies)
// データ型のインスタンスをJSONオブジェクトとしてエンコードします。
if let data = try? encoder.encode(jsonData) {
return data
}
Codableプロトコルに準拠している構造体
// バックアップデータをJSONオブジェクトへエンコードする際に使用するインスタンスの型です。
// バックアップするモデルを指定してください。
struct JsonData: Codable {
var items: [Item]
var users: [User]
var companies: [Company]
}
JSONオブジェクトを生成
エンコードで変換したJSONオブジェクトからFileDocumentプロトコルに準拠するクラスのインスタンスを生成します。
// JSONFileを作成します。
self.jsonFile = TextFile(initialText: String(bytes: jsonData, encoding: .utf8)!)
// FileDocumentプロトコルに準拠するクラス
struct TextFile: FileDocument {
var text = ""
// ファイルを新規作成する場合に使用します。
init(initialText: String = "") {
text = initialText
}
}
JSONファイルの出力画面を表示
fileExporterでJSONファイル出力用の画面を表示します。
ユーザは画面でファイル名と出力箇所を選択して、JSONファイルを出力することができるようになります。
.fileExporter(
isPresented: self.$isExporting, // trueにしたときにファイル出力用の画面が表示されます。
document: self.jsonFile, // 出力するファイルを指定します。
contentType: .plainText, // 出力するファイルの種類を指定します。
defaultFilename: self.jsonFileName // デフォルトのファイル名を指定します。ファイル名は画面で変更できます。
) { result in
if case .success = result {
// ファイルの出力に成功した時の処理
} else {
// ファイルの出力に失敗した時の処理
}
}
RealmデータをJSONファイルで入力する方法
JSONファイルの入力画面を表示
fileImporterでJSONファイル入力用の画面を表示します。
ユーザは画面で入力するJSONファイルを選択することができるようになります。
.fileImporter(
isPresented: $isImporting, // trueにしたときにファイル取込用の画面が表示されます。
allowedContentTypes: [.plainText], // 取込可能なファイルの種類を指定します。
allowsMultipleSelection: false // ユーザーが複数のファイルを同時に選択できるか指定します。
) { result in
do {
// ファイルを読み込み、Realmに書き込みます。
guard let selectedFile: URL = try result.get().first else { return }
RealmUtils.restore(jsonValue: try Data(contentsOf: selectedFile))
} catch {
// ファイルの取込に失敗した時の処理
}
}
JSONオブジェクトをデコード
JSONDecoderクラスを使うことでJSONオブジェクトからデータ型のインスタンスへ変換することができます。
JSONオブジェクトは画面から選択したJSONファイルです。
データ型のインスタンスの構造体はエンコードと同じものを使います。
// JSONオブジェクトからデータ型のインスタンスをデコードするオブジェクトを設定します。
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// JSONオブジェクトからデータ型のインスタンスをデコードします。
guard let jsonData: JsonData = try? decoder.decode(JsonData.self, from: jsonValue) else {
fatalError("Failed to decode from JSON.")
}
JSONオブジェクトを読み込む
デコードで変換したデータ型のインスタンスをRealmへ書き込みます。
別オブジェクトを参照しているモデルの場合は、データ1件ずつ参照先のリストを作成する必要があるので要注意です。
let realm = try! Realm()
// データ型のインスタンスをRealmへ書き込みます。
try! realm.write {
// 重複エラーを防ぐために既存のデータを削除します。
realm.deleteAll()
// JSONデータを読み込み、モデルごとにRealmへ書き込みます。
realm.add(jsonData.items)
realm.add(jsonData.companies)
// 1対多、多対多などのリレーションを持ち、別オブジェクトを参照している場合は、
// 参照先のリストを作成し1件ずつ書き込む必要があります。
for user in jsonData.users {
let userTemp = User()
userTemp.id = user.id
for company in user.companies {
userTemp.companies.append(Company.get(id: company.id).first!)
}
realm.add(userTemp)
}
}
サンプル
説明で用いたサンプルの全てのプログラムを載せておきます。
参考にしてください。
import SwiftUI
import RealmSwift
struct ContentView: View {
@State private var jsonFile: TextFile? = nil
@State private var jsonFileName: String = ""
@State private var isImporting: Bool = false
@State private var isExporting: Bool = false
@State private var alertItem: AlertItem? = nil
struct AlertItem: Identifiable {
var id = UUID()
var alert: Alert
}
var body: some View {
VStack {
Button(action: {
// バックアップデータを取得します。
if let jsonData: Data = RealmUtils.backup() {
// JSONFileを作成します。
self.jsonFile = TextFile(initialText: String(bytes: jsonData, encoding: .utf8)!)
// File名を指定します。ここではファイル名の後ろにシステム日付をつけます。
let dateformater = DateFormatter()
dateformater.dateFormat = "yyyyMMddhhmmss"
dateformater.locale = Locale(identifier: "ja_JP")
self.jsonFileName = "Backup_" + dateformater.string(from: Date())
// ファイル出力用の画面を表示するためのフラグをtrueにします。
self.isExporting = true
}
}){
Text("バックアップを作成")
}
Button(action: {
isImporting = true
}){
Text("バックアップから復元")
}
}.fileExporter(
isPresented: self.$isExporting, // trueにしたときにファイル出力用の画面が表示されます。
document: self.jsonFile, // 出力するファイルを指定します。
contentType: .plainText, // 出力するファイルの種類を指定します。
defaultFilename: self.jsonFileName // デフォルトのファイル名を指定します。ファイル名は画面で変更できます。
) { result in
if case .success = result {
// ファイルの出力に成功した時のメッセージを指定します。
alertItem = AlertItem(alert: Alert(title: Text("成功"),
message: Text("作成できました。"),
dismissButton: .default(Text("OK"))))
} else {
// ファイルの出力に失敗した時のメッセージを指定します。
alertItem = AlertItem(alert: Alert(title: Text("失敗"),
message: Text("作成できませんでした。"),
dismissButton: .default(Text("OK"))))
}
}.fileImporter(
isPresented: $isImporting, // trueにしたときにファイル取込用の画面が表示されます。
allowedContentTypes: [.plainText], // 取込可能なファイルの種類を指定します。
allowsMultipleSelection: false // ユーザーが複数のファイルを同時に選択できるか指定します。
) { result in
do {
// ファイルを読み込み、Realmに書き込みます。
guard let selectedFile: URL = try result.get().first else { return }
RealmUtils.restore(jsonValue: try Data(contentsOf: selectedFile))
// ファイルの取込に成功した時のメッセージを指定します。
alertItem = AlertItem(alert: Alert(title: Text("成功"),
message: Text("復元できました。"),
dismissButton: .default(Text("OK"))))
} catch {
// ファイルの取込に失敗した時のメッセージを指定します。
alertItem = AlertItem(alert: Alert(title: Text("失敗"),
message: Text("復元できませんでした。"),
dismissButton: .default(Text("OK"))))
}
}.alert(item: $alertItem) { item in
item.alert
}
}
}
import SwiftUI
import UniformTypeIdentifiers
struct TextFile: FileDocument {
// UTType.plainTextのみ読み込み可能であることを明示します。
static var readableContentTypes = [UTType.plainText]
var text = ""
// ファイルを新規作成する場合に使用します。
init(initialText: String = "") {
text = initialText
}
// すでに存在するファイルを読み込む場合に使用します。
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
text = String(decoding: data, as: UTF8.self)
}
}
// ファイルを書き込む場合に使用します。
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(text.utf8)
return FileWrapper(regularFileWithContents: data)
}
}
import RealmSwift
// バックアップデータをJSONオブジェクトへエンコードする際に使用するインスタンスの型です。
// バックアップするモデルを指定してください。
struct JsonData: Codable {
var items: [Item]
var users: [User]
var companies: [Company]
}
class RealmUtils {
// バックアップデータの取得
public static func backup() -> Data? {
// データ型のインスタンスをJSONオブジェクトとしてエンコードするオブジェクトを設定します。
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
// 各モデルのバックアップするデータを取得します。
let items = Array(Item.all())
let users = Array(User.all())
let companies = Array(Company.all())
// バックアップするデータからデータ型のインスタンスを生成します。
let jsonData = JsonData(items: items, users: users, companies: companies)
// データ型のインスタンスをJSONオブジェクトとしてエンコードします。
if let data = try? encoder.encode(jsonData) {
return data
}
return nil
}
// JSONファイルの読み込み
public static func restore(jsonValue: Data) {
let realm = try! Realm()
// JSONオブジェクトからデータ型のインスタンスをデコードするオブジェクトを設定します。
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// JSONオブジェクトからデータ型のインスタンスをデコードします。
guard let jsonData: JsonData = try? decoder.decode(JsonData.self, from: jsonValue) else {
fatalError("Failed to decode from JSON.")
}
// データ型のインスタンスをRealmへ書き込みます。
try! realm.write {
// 重複エラーを防ぐために既存のデータを削除します。
realm.deleteAll()
// JSONデータを読み込み、モデルごとにRealmへ書き込みます。
realm.add(jsonData.items)
realm.add(jsonData.companies)
// 1対多、多対多などのリレーションを持ち、別オブジェクトを参照している場合は、
// 参照先のリストを作成し1件ずつ書き込む必要があります。
for user in jsonData.users {
let userTemp = User()
userTemp.id = user.id
for company in user.companies {
userTemp.companies.append(Company.get(id: company.id).first!)
}
realm.add(userTemp)
}
}
}
}
import RealmSwift
final class Item: Object, ObjectKeyIdentifiable, Identifiable, Codable {
@objc dynamic var id: Int = 0
@objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "id"
}
// 全データを取得する。
static func all() -> Results<Item> {
let realm: Realm = try! Realm()
return realm.objects(Item.self)
}
}
import RealmSwift
final class User: Object, ObjectKeyIdentifiable, Identifiable, Codable {
@objc dynamic var id: Int = 0
@objc dynamic var name: String = ""
var companies: List<Company> = List<Company>()
override static func primaryKey() -> String? {
return "id"
}
// 全データを取得する。
static func all() -> Results<User> {
let realm: Realm = try! Realm()
return realm.objects(User.self)
}
}
import RealmSwift
final class Company: Object, ObjectKeyIdentifiable, Identifiable, Codable {
@objc dynamic var id: Int = 0
@objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "id"
}
// 全データを取得する。
static func all() -> Results<Company> {
let realm: Realm = try! Realm()
return realm.objects(Company.self)
}
// idを指定してデータを取得する。
static func get(id: Int) -> Results<Company> {
let realm: Realm = try! Realm()
return realm.objects(Company.self).filter("id == %@", id)
}
}