修正(02/14)
2つ目の方法は、FirebaseFirestoreSwift
を使わないとできないというのは自分の勘違いでした。すみません。
現在は、修正しております。
はじめに
最近、Firestoreを使ったデモアプリを作って遊んでおります。
Firestoreでサポートされているデータ型は色々あるのですが、今回はその中でもmap型のデータを取得する方法について書きました。
Firestoreでサポートされるデータ型についてはこちらの公式リファレンスをご覧ください。
https://firebase.google.com/docs/firestore/manage-data/data-types
今回は、2つの方法を載せておきます。
動作環境
- Xcode12.4
- iOS14.4
今回取得するFirestoreのデータ&Swift側のオブジェクト
Firestoreのmap型を取得するということで、Firestore側のデータ構造があったほうがわかりやすいと思いましたので、載せておきます。
■ Swift側のオブジェクト(構造体)
struct Order: Codable {
let id: String
let orderNo: Int
let imageName: String
let name: String
let price: Int
let isWasabi: Bool
var isOrderComplete: Bool
}
[String : Any]から直接インスタンスを生成する方法
func fetchOrderMap_A() {
db.collection("ordering").document("01").getDocument { (_document, _error) in
guard _error == nil else {
print("Firestoreからエラーを受け取りました。")
return
}
if let document = _document, document.exists {
// document.data()は、[String : Any]になる
let mapData = document.data() ?? [:]
print(mapData) // ※補足参照
var orderList: [Order] = []
// データを1件ずつ抽出
for (_, data) in mapData {
// data(Any型)をdicData([String: Any]型)にキャストする <- Point
let dicData = data as! [String : Any]
// [String: Any]にキャストされていることを確認
print(dicData)
// key - value でプロパティ別に値を取り出す。(今回は強制アンラップ。Firestoreのデータ型に合わせてキャストすれば問題なし。)
let id = dicData["id"] as! String
let orderNo: Int = dicData["orderNo"] as! Int
let imageName: String = dicData["imageName"] as! String
let name: String = dicData["name"] as! String
let price: Int = dicData["price"] as! Int
let isWasabi: Bool = dicData["isWasabi"] as! Bool
let isOrderComplete: Bool = dicData["isOrderComplete"] as! Bool
// インスタンス生成して配列に追加
let order = Order(id: id, orderNo: orderNo, imageName: imageName, name: name, price: price, isWasabi: isWasabi, isOrderComplete: isOrderComplete)
orderList.append(order)
}
// 生成された配列を出力
print(orderList)
} else {
print("ドキュメントが存在しません。")
}
}
}
※補足
print(mapData)
実行時のmapData
の中身はこんな感じです。
["2": {
id = 002;
imageName = samon;
isOrderComplete = 1;
isWasabi = 1;
name = "\U30b5\U30fc\U30e2\U30f3";
orderNo = 2;
price = 150;
}, "1": {
id = 001;
imageName = "maguro_akami";
isOrderComplete = 0;
isWasabi = 0;
name = "\U8d64\U8eab\U30de\U30b0\U30ed";
orderNo = 1;
price = 100;
}]
上記のようにkey,valueの配列になっているので、for文
で1件ずつ処理していきます。
データを1件取得できたら、取得したいデータ部を[String : Any]
でキャストします。
現時点では、name
がUnicodeになっていますが問題ありません。
このようなステップを踏むことで、Swiftから容易に扱える形にしています。
ここまで実装できれば、後は通常のJSON解析などとやり方は同じだと思います。
JSONを噛ませてからCodableを使う方法
func fetchOrderMap_B() {
db.collection("ordering").document("01").getDocument { (_document, _error) in
guard _error == nil else {
print("Firestoreからエラーを受け取りました。")
return
}
if let document = _document, document.exists {
// document.data()は、[String : Any]になる
let mapData = document.data() ?? [:]
print(mapData) // ※補足参照
var orderList: [Order] = []
// データを1件ずつ抽出
for (_, data) in mapData {
// data(Any型)をdicData([String: Any]型)にキャストする <- Point
let dicData = data as! [String : Any]
// [String: Any]にキャストされていることを確認
print(dicData)
// ここからJSONを利用する場合の固有処理
// JSON へ一時的にキャスト
let jsonData = try? JSONSerialization.data(withJSONObject: dicData)
// JSON -> Order(Codable)
let order = try? JSONDecoder().decode(Order.self, from: jsonData!)
if let order = order {
orderList.append(order)
}
}
// 生成された配列を出力
print(orderList)
} else {
print("ドキュメントが存在しません。")
}
}
}
処理内容はコメントに書かせていただきましたが、ポイントは
[String:Any]
-> JSON
-> Order
というように一時的にJSON
を噛ませてから、目的のOrder
に辿り着いていることです。
自分はこの方法しか思いつかなかったのですが、もし他にいい方法がございましたらコメントにて教えていただけると嬉しいです。
まとめ
最初実装していたときは、Firestoreでmap型を取得したら、何も考えずにデコードできると思っていましたが、id = 002;
のような形式で値が帰ってきたので最初戸惑いました。
iOSアプリ開発時にFirestoreのmap型を使いたいという方は参考にしていただけると幸いです。