Appending array of map from model in firestore document in Swift - arrays

I am trying to add to an array of maps in Firestore and getting the following error:
Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Unsupported type: __SwiftValue'
This is my Model Class:
struct StringModel: Identifiable, Codable, Equatable, Hashable {
var id = UUID()
var cross: Int
var mains: Int
var name: String
var date: Date
}
struct UserDataModel: Identifiable, Codable {
#DocumentID public var id: String?
var uid: String
var strings: [StringModel]?
}
Am using the following function to update the value in firestore, however, it doesnt seem to work and throws the error:
#Published var newString = StringModel(cross: 50, date: Timestamp(), mains: 50, name: "")
func addString() {
db.document(uidStr).updateData(["strings" : FieldValue.arrayUnion([newString])]) { err in
if let err = err{
print("\(err)")
}
}
}
Firestore Datatbase
Any ideas how I can go about this? Thanks!

You can't append a Swift object to a Firestore array. You must convert the Swift object into a [String: Any] object. You can add a computed property to the model that outputs this for you.
struct StringModel: Identifiable, Codable, Equatable, Hashable {
var id = UUID()
var cross: Int
var mains: Int
var name: String
var date: Date
var firestoreData: [String: Any] {
return [
"id": id,
"cross": cross,
"mains": mains,
"name": name,
"date": date
]
}
}
func addString() {
db.document(uidStr).updateData(["strings" : FieldValue.arrayUnion([newString.firestoreData])]) { err in
if let err = err{
print("\(err)")
}
}
}

Related

Upload array of Objects Firestore swift

ich work with swift and firestore and try to implemented a server similar to this:
chatId: String
eventCreatorId: String
matchedUserId: String
eventId: String
messages: [
{userId: String, timestamp:timestamp, messageText: String},
{userId: String, timestamp:timestamp, messageText: String},
],
in other Words with a MVVM design i want to upload a array of Models
but i getting a type error when i try with this
struct ChatModel: Codable {
var chatId: String
var eventCreatorId: String
var matchedUserId: String
var eventId: String
var messages: [MessageModel]
}
struct MessageModel: Codable {
var userId: String
var timeStamp: Timestamp
var messageText: String
}
the error happens if i try to upload
func uploadMessage(messageText: String, chatId: String) -> Promise<Void> {
return Promise { seal in
guard let currentUser = Auth.auth().currentUser else {
return
}
let timeStamp: Timestamp = Timestamp(date: Date())
let messageModel = MessageModel(userId: currentUser.uid,timestamp: timeStamp, messageText: messageText)
print(messageModel)
let _ = db.collection("chats")
.document(chatId)
.updateData(["messages" : FieldValue.arrayUnion([messageModel])]) { error in
if let error = error {
seal.reject(error)
} else {
seal.fulfill(())
}
}
}
}
}
i tried also without the timestamp but ran into the same error
can someone explain me what am i doing wrong?
On ArrayUnion, Firestore does not understand the type you're trying to pass.
Converting the struct to a dictionary with type [String: Any] will ommit this error -
struct MessageModel: Codable {
var userId: String
var timeStamp: Timestamp
var messageText: String
var dictionary: [String: Any] {
return["userId": userId,
"timeStamp": timeStamp,
"messageText": messageText]
}
}
Then when you're uploading to Firestore you use the Dict value:
.updateData(["messages" : FieldValue.arrayUnion([messageModel.dictionary])]) { error in

SwiftUI EXC_BAD_INSTRUCTION when filtering and mapping Array

I have an Entity called Lessons, which consists of:
#NSManaged public var timeMinutes: Double
#NSManaged public var id: UUID
#NSManaged public var lessonDescription: String
#NSManaged public var date: Dates
The 'date' attribute is linked with a one-to-many relationship to a separate 'Dates' entity, which consists of:
#NSManaged public var date: Date
I am now trying to display a graph that has the summed 'timeMinutes' by 'date'. This finalArray would have the following structure:
struct FinalArray {
let summedMinutes : Double
let date : Date
}
My code to generate (and display) this array looks as following:
import SwiftUI
import SwiftUICharts
struct Test: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Lessons.allLessonsFetchRequest()) var lessons: FetchedResults<Lessons>
var finalArray = [FinalArray]()
init(){
let allKeys = Set<Date>(lessons{$0.date.date})
for key in allKeys {
let sum = lessons.filter({$0.date.date == key}).map({$0.timeMinutes}).reduce(0, +)
finalArray.append(FinalArray(summedMinutes:sum, date:key))
}
finalArray = finalArray.sorted(by: {$0.date < $1.date})
}
var body: some View {
VStack{
LineView(data: finalArray.map {$0.summedMinutes}, title: "This graph returns empty").padding()
LineView(data: lessons.map {$0.timeMinutes}, title: "This graph works").padding()
}
}
}
I get the following error, on the first line of init:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Does anyone know how I can fix this? What is the reason for this?
If I do a ForEach on the Core Data within my view, it works OK:
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
ForEach(lessons.map {$0.date.date}, id: \.self) { item in
VStack{
Text("\(item, formatter: self.dateFormatter)")
Text(String(self.lessons.filter{$0.date.date == item}.map {$0.timeMinutes}.reduce(0, +)))
}
}
This code works, but it's not what I want, I want to display the summed 'timeMinutes' in an Array. Any ideas or workarounds would be super welcome!
The lessons is fetched on view initial update, but on init it is not defined yet, so trying to use it in there you've got a crash.
Here is possible approach:
#State private var finalArray = [FinalArray]()
var body: some View {
VStack{
LineView(data: finalArray.map {$0.summedMinutes}, title: "This graph returns empty").padding()
LineView(data: lessons.map {$0.timeMinutes}, title: "This graph works").padding()
}
.onAppear {
let allKeys = Set<Date>(lessons{$0.date.date})
for key in allKeys {
let sum = lessons.filter({$0.date.date == key}).map({$0.timeMinutes}).reduce(0, +)
finalArray.append(FinalArray(summedMinutes:sum, date:key))
}
finalArray = finalArray.sorted(by: {$0.date < $1.date})
}
}

Swift Vapor wrong decoding of array of objects

I'm trying to pass an array of objects inside another object
struct CreationData: Encodable {
enum CodingKeys: String, CodingKey {
case property1
case amount
case participants
case code
}
var property1: String?
var amount: Double?
var tipAmount: Double?
var participants: [Participant]
var currency: Currency
init() {
name = nil
property1 = nil
participants = []
tipAmount = nil
currency = .hryvna
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let name = name {
try container.encode(name, forKey: .name)
}
if let property1 = property1 {
try container.encode(property1, forKey: .property1)
}
if let tipAmount = tipAmount {
try container.encode(tipAmount, forKey: .tipAmount)
}
if !participants.isEmpty {
try container.encode(participants, forKey: .participants)
}
try container.encode(currency, forKey: .currencyCode)
}
}
where object in array is
struct Participant: Hashable, Encodable {
enum CodingKeys: String, CodingKey {
case amount
case id
}
var image: Data?
var displayName: String
var amount: Double? = 100
var id: Int?
var name: String?
var isSelected: Bool = false
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(amount, forKey: .amount)
try container.encode(id, forKey: .id)
}
}
so my JSON looks something like this
{
"property1" : 123,
"code" : "zen",
"name" : "No name",
"participants" : [
{
"amount" : 100,
"id" : 1
}
]
}
On the backend I use this handler
func createHandler(_ req: Request, splitCreate: Create) throws -> Future<SplitResponse> {
let logger = try req.make(Logger.self)
let user = try req.requireAuthenticated(User.self)
guard let id = user.id else {
throw Abort(.badRequest, reason: "Couldn't get an id for current user")
}
...
with this request content object
struct Create: Content {
struct Participant: Content {
var id: Int?
var amount: Double?
}
var amount: Double?
var tipAmount: Double?
var name: String?
var participants: [Participant]
var currencyCode: String
}
I create pivots by the participants array, and the problem is that instead of getting a single participant from an array like so
[App.Create.Participant(id: Optional(1), amount: Optional(100.0))]
on the backend from the specified JSON I get these participants
[App.Create.Participant(id: Optional(1), amount: nil), App.Create.Participant(id: Optional(1), amount: Optional(100.0))]
Seems like my array json is being decoded incorrectly, but I'm unable to find a way to fix it
For some reason adding Content-Type header to URLSessionConfiguration.additionalHeaders didn't change the encoding to JSON.
So I had to set Alamofire's request encoding to JSONEncoding.default explicitly.

Accessing embedded JSON using decodable in Swift 4

I am trying to access a particular an embedded array of dictionaries to create my swift objects. I am unsure about how to access that array in the JSON dictionary.
Here is the definition of my Swift object = StarWarsPeople
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var weight: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Here is my APIClient class:
class StarWarsPeopleAPIClient
{
class func getStarWarsPeopleInformation (page: Int, completion:#escaping ([StarWarsPeople])-> ()) throws {
let starWarsPeopleURL = "https://swapi.co/api/people/?page=\(page)"
let convertedStarWarsPeopleURL = URL(string: starWarsPeopleURL)
guard let unwrappedConvertedStarWarsPeopleURL = convertedStarWarsPeopleURL else { print("unwrappedConvertedStarWarsPeopleURL did not unwrap"); return}
let request = URLRequest(url: unwrappedConvertedStarWarsPeopleURL)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode([StarWarsPeople].self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
task.resume()
}
}
Here is my Json, it is the results array that I would like to access, it is an array of dictionaries, which I need to iterate over to create my StarWarsPeople Object.
{
"count": 87,
"next": "url",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "url",
"films": [
"url",
"url",
"url",
"url",
"url"
],
"species": [
"url"
],
"vehicles": [
"url",
"url"
Please read the JSON. You are ignoring the enclosing object
struct Root: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results : [StarWarsPeople]
}
struct StarWarsPeople: Decodable {
private enum CodingKeys: String, CodingKey {
case name, height, mass
case hairColor = "hair_color", skinColor = "skin_color"
case eyeColor = "eye_color", birthYear = "birth_year", gender
}
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
}
...
let root = try JSONDecoder().decode(Root.self, from: unwrappedData)
let starWarsPeopleDataArray = root.results
...
Notes:
A struct is sufficient.
Map the snake_cased keys to camelCased properties.
In almost all cases the properties can be declared as constants (let).
Don't declare all properties schematically as optional. Declare only those as optional which corresponding key can be missing or the value can be null.
Simply define a wrapper struct that holds the results property from the JSON response.
struct ApiResponse: Decodable {
results: [StarWarsPeople]
}
and later use
let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: unwrappedData)
let starWarsPeopleDataArray = apiResponse.results
to parse it.
Results you are trying to fetch is actually present in results keys. Also we need to use properties same as parameter name( we can use CodingKeys enum too for overriding this).
So, first parse outer JSON , In new struct say StarWarsPeopleParent
class StarWarsPeopleParent: Decodable {
var count: Int?
var results: [StarWarsPeople]?
}
Update your StarWarsPeople struct's properties as:
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var mass: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Then parse it like:
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode(StarWarsPeopleParent.self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
Hope this is fine for you to understand.

How to Make JSON from Array of Struct in Swift 3?

I have a problem to make a JSON from an array of struct in Swift3. I searched in Stack Overflow, nothing help me (here the screenshot). I have a struct like this:
public struct ProductObject {
var prodID: String
var prodName: String
var prodPrice: String
var imageURL: String
var qty: Int
var stock: String
var weight: String
init(prodID: String, prodName: String, prodPrice: String, imageURL: String, qty: Int, stock: String, weight: String){
self.prodID = prodID
self.prodName = prodName
self.prodPrice = prodPrice
self.imageURL = imageURL
self.qty = qty
self.stock = stock
self.weight = weight
}
}
and the array of that struct:
private var productsArray = [ProductObject]()
When the array is not empty, and then I tried to print it in another class, it shows this in debugger:
[app.cartclass.ProductObject(prodID: "2", prodName: "produk 2", prodPrice: "IDR 1000000", imageURL: "someURL", qty: 1, stock: "11", weight: "200")]
The array is not a valid JSON object. How to make it a valid JSON object? And I wonder whether this part "app.cartclass.ProductObject" is a problem or not to make it a valid JSON object?
edit:
Here's how I serialize into a JSON:
var products = [String:Any]()
for j in 0 ..< cart.numberOfItemsInCart() {
products=["\(j)":cart.getAllProduct(atIndex: j)]
}
if let valid = JSONSerialization.isValidJSONObject(products) {
do {
let jsonproducts = try JSONSerialization.data(withJSONObject: products, options: .prettyPrinted) as! [String:Any]
//print(jsonproducts)
} catch let error as NSError {
print(error)
}
} else {
print("it is not a valid JSON object");
}
If you want to make JSON from custom object then first you need to convert your custom object to Dictionary, so make one function like below in your ProductObject struct.
func convertToDictionary() -> [String : Any] {
let dic: [String: Any] = ["prodID":self.prodID, "prodName":self.prodName, "prodPrice":self.prodPrice, "imageURL":self.imageURL, "qty":qty, "stock":stock, "weight":weight]
return dic
}
Now use this function to generate Array of dictionary from Array of custom object ProductObject.
private var productsArray = [ProductObject]()
let dicArray = productsArray.map { $0.convertToDictionary() }
Here dicArray is made of type [[String:Any]], now you can use JSONSerialization to generate JSON string from this dicArray.
if let data = try? JSONSerialization.data(withJSONObject: dicArray, options: .prettyPrinted) {
let str = String(bytes: data, encoding: .utf8)
print(str)
}

Resources