xmlやplistを使うことも考えたが、codableを使うことで独自のモデルにも容易に対応させることができた。
環境
- Xcode 11.4.1
- Swift 5
- iOS 13.4.1
StringやIntなどは自動Codableに準拠しているので、encodeやdecodeメソッドを使う必要はないが、
今回はその他のデータ型にも対応できるようにencode、decodeメソッドを実装した。
モデル[Recipe]
import Foundation import RealmSwift class Recipe: Object,Decodable { @objc dynamic var name = "" @objc dynamic var category = "" @objc dynamic var country = "" @objc dynamic var season = "" var ingredients = List<Ingredient>() //RecipeとIngredientは1対多 override static func primaryKey() -> String? { return "name" } private enum CodingKeys:String,CodingKey{ case name case category case country case season case ingredients } required convenience public init(from decoder:Decoder) throws{ self.init() let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) category = try container.decode(String.self, forKey: .category) country = try container.decode(String.self, forKey: .country) season = try container.decode(String.self, forKey: .season) //一度配列に格納した後、Listに変換する let ingredientsArray = try container.decode([Ingredient].self, forKey: .ingredients) ingredients = ingredientsArray.reduce(List<Ingredient>()) {$0.append($1); return $0} } }
モデル[ingredient]
import Foundation import RealmSwift class Ingredient: Object,Decodable { @objc dynamic var name = "" let recipes = LinkingObjects(fromType: Recipe.self, property: "ingredients") private enum CodingKeys: String, CodingKey { case name } required convenience public init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) } }
JSONファイル
[ { "name": "トマトパスタ", "category": "パスタ", "country": "イタリアン", "season": "Any", "ingredients":[ { "name": "トマト" }, { "name": "にんにく" } ] }, { "name": "カツ丼", "category": "丼ぶり", "country": "和食", "season": "Any", "ingredients":[ { "name": "たまねぎ" }, { "name": "卵" }, { "name": "豚肉" } ] }, { "name": "ペペロンチーノ", "category": "パスタ", "country": "イタリアン", "season": "Any", "ingredients":[ { "name": "にんにく" }, { "name": "鷹の爪" } ] }, { "name": "カレー", "category": "カレー", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "じゃがいも" }, { "name": "牛肉" } ] }, { "name": "シチュー", "category": "シチュー", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "じゃがいも" }, { "name": "牛肉" } ] }, { "name": "炒飯", "category": "ご飯", "country": "中華", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "卵" }, { "name": "にんにく" } ] }, { "name": "炊き込みご飯", "category": "ご飯", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "椎茸" }, { "name": "鶏肉" }, { "name": "大葉" } ] } ]
JSONデータの取り込み
//JSONファイルのパスを取得 guard let path = Bundle.main.path(forResource: "RecipesList", ofType: "json") else { return } let url = URL(fileURLWithPath: path) do { let realm = try! Realm() let data = try Data(contentsOf: url) let recipe_obj = try! JSONDecoder().decode([Recipe].self, from: data) try! realm.write { realm.deleteAll() realm.add(recipe_obj, update: true) //人参を使うレシピを取得・表示 var objects: Results<Recipe> objects = realm.objects(Recipe.self).filter("SUBQUERY(ingredients, $ingredient, $ingredient.name = %@).@count >= 1","人参") print("objectsの中",objects) } } catch { print("エラー") }