I need to save a date has a firebase Timestamp Object to Cloud Firestore using Firebase functions.
The code in question:
import FirebaseFunctions
lazy var functions = Functions.functions(region: "europe-west3")
let stamp = Timestamp(date: Date()) —> Not working
self.functions.httpsCallable("userUpdate").call(["id": user.uid, “startDate”: stamp]) { (result, error) in
if let error = error {
print("##### update error \(error)”)
}
}
if let res = result {
if let fireData = (res.data as? [String: Any]) {
debugPrint("##### Success update Data -> \(fireData["data"])")
}
}
}
The problem is that Firebase does not recognise the Timestamp format throwing a error:
'Unsupported type: FIRTimestamp for value <FIRTimestamp: seconds=1633262425 nanoseconds=843380928>'
If I try to send the date on the format below it works BUT the date on the server has the wrong format, it shows has a string and not has a Timestamp.
let date = DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .none)
Any solutions for Firebase to recognise the date I'm sending has a Timestamp?
Callable Cloud Functions only know how to send/receive JSON type, and Timestamp is not a JSON type.
The easiest way to work around this is to send the seconds and nanoseconds as separate numbers (which are valid JSON types) and then reconstruct a Timestamp object from those numberzs in the Cloud Functions code.
Related
First off, what do we call a dictionary with a format like this in iOS?
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
And for my main question. I somehow need to format a string of JSON, like this:
[{"name":"Apple","value":"fruit-1"},{"name":"Banana","value":"fruit-2"}]
into whatever that format is called (of the string above).
For context, the existing approach of my project uses CoreData where the Server response (which uses the mystery format above) gets saved locally as a String, and I want to follow that format.
EDIT: for more context, I really need to just get the first format into the database because a module of a project was built to read the data with that format (e.g. make use of NSString.propertyList()).
Using a library called ios hierarchy viewer, I can see the saved object in the device.
Original format, server json to db (core data) in Objective-C:
What I've been trying to do in Swift, server json to local using JSONSerialization:
First off, what do we call a dictionary with a format like this in iOS?
According to the documentation of NSString.propertyList(), that's a "text representation of a property list".
It's a wonky, non-standard pretty-printing obtained by calling NSArray.description or NSDictionary.description.
Here's an example that shows a round-trip of data:
// The opening `{` indentation is fucky, but that's how it's generated.
let inputPropertyList = """
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
"""
// The result is an `Any` because we don't know if the root structure
// of the property list is an array or a dictionary
let deserialized: Any = inputPropertyList.propertyList()
// If you want the description in the same format, you need to cast to
// Foundation.NSArray or Foundation.NSDictionary.
// Swift.Array and Swift.Dictionary have a different description format.
let nsDict = deserialized as! NSArray
let roundTrippedPropertyList = nsDict.description
print(roundTrippedPropertyList)
assert(roundTrippedPropertyList == inputPropertyList)
The second format you show is what you get when you display an object in the debug console. That's the output of the object's description property. It isn't a "JSON string", exactly.
If you want to convert your objets to a true JSON string, see below.
As Alexander pointed out, the first string in your question is the output from NSString's propertyList() function. The format looks quite similar to "pretty-printed" JSON, but it's different enough that it it won't work that way.
The `propertyList() function is a debugging-only function, and I don't know of an existing way to parse that back into objects. If that is the string that's being sent by your server, your server is broken. If that's what you see in core data when you log the contents of a field, it's probably a misunderstanding on your part.
To convert an object to pretty JSON, see this answer, where I created an extension to the Encodable format that implements a property "prettyJSON":
extension Encodable {
var prettyJSON: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(self),
let output = String(data: data, encoding: .utf8)
else { return "Error converting \(self) to JSON string" }
return output
}
}
That should work for any object that supports the Encodable protocol. (And your object should.)
I have a custom class
public class Balance: NSObject {
var details: String
var date: Date
var amount: Double
}
I have tried as a struct and as a class both fail
I have an array of balances list: [Balance]
Now I'm need to convert this array into a JSON String
something like
[ {details = "text"; date = "2016-11-20"; amount = 0;} ,
{details = "text2"; date = "2016-11-25"; amount= 10;} ]
I also need to be able to convert the String back into the array.
But I can't even get the array to JSON string to work
var resStr = ""
var list: [Balance]
do {
let data = try JSONSerialization.data(withJSONObject: list, options: .prettyPrinted)
resStr = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ") }
Fails with
'Invalid type in JSON write (Balance)'
Please advise
Thanks in advance
What about defining your class like this:
public class Balance: NSObject {
var details: String
var date: Date
var amount: Double
func toJSONString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return "{details = \(self.details); date = \(dateFormatter.string(from: self.date)); amount = \(self.amount);}"
}
}
Then you can create the full JSON string like this:
var fullJSONArray = [String]()
for balance in list {
fullJSONArray.append(balance.toJSONString)
}
let fullJSONString = fullJSONArray.description
I hope this helps you out! Good luck and let me know if you have any further questions or issues!
You can only store a quite small list of data types to JSON.
#CoolPenguin offers one solution - a custom method that will convert your object to a JSON string.
I would advise against building JSON strings yourself.
Instead, I would suggest creating a toDictionary() method for your class, and an init(dictionary:) method to create a Balance object from a dictionary.
You can convert the dates to TimeIntervals since 1970 (the UNIX "epoch date".) That seems easier than converting them to date strings.
You can then map your array of Balance objects to an array of dictionaries, and convert that to JSON using normal JSONSerialization.
let mappedArray = balanceArray.map{$0.toDictionary()}
And then easily convert your array of dictionaries to JSON.
Since others have pointed out that you can achieve the desired effect by first converting your object to a Dictionary, I will provide a method to achieve what is required by using JSONEncoder instead.
If you maintain the values you need to represent in the JSON encoded string (for example the date as a formatted String), the only required bits are,
Make your type conform to Codable
Create a CodingKeys enum that represent the JSON keys for your type's properties.
Please see below for an example that applies to your object.
public class Balance: NSObject, Codable {
var details: String
// Used to represent formatted date.
var dateString: String
var date: Date = Date(){
didSet{
updateDateString()
}
}
var amount: Double
enum CodingKeys: String, CodingKey{
case details, dateString, amount
}
init(_ d: String, amt: Double){
details = d
dateString = ""
date = Date()
amount = amt
super.init()
updateDateString()
}
private func updateDateString(){
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd"
dateString = df.string(from: date)
}
}
var arr: [Balance] = [
Balance("Shared Balance", amt: 100),
Balance("Coupon Balance", amt: 120),
Balance("Account Balance", amt: 150)
]
do{
let data = try JSONEncoder().encode(arr)
// Convert to JSON Encoded String
let str = String(data: data, encoding: .utf8)!
print(str)
// Convert back from JSON Encoded String
let raw = try JSONDecoder().decode([Balance].self, from: str.data(using: .utf8)!)
print(raw)
}
catch(let err){
print(err)
}
The additional updateDateString() boiler plate code is to produce the String with the date format, "yyyy-MM-dd".
The above code can product a JSON encoded version of your Balance array as follows.
[{"details":"Shared Balance","amount":100,"dateString":"2021-01-14"},
{"details":"Coupon Balance","amount":120,"dateString":"2021-01-14"},
{"details":"Account Balance","amount":150,"dateString":"2021-01-14"}]
The key takeaway is to use JSONEncoder instead of JSONSerialization.
Hope I helped!
Well, I believe first you need to convert your object to some form of dictionary.
Let me show you an example:
class Balance: NSObject {
var details: String
var date: Date
var amount: Double
func asDictionary() -> [String: AnyObject] {
return ["details": details as AnyObject, "date": "\(date)" as AnyObject, "amount": amount as AnyObject]
}
}
You use the method asDictionary to convert your objects to a dictionary so that you can serialize it into JSON.
Suppose you have a list of Balance objects.
You need to first convert each of those objects to dictionary using the method above, and then try to serialize the objects to JSON. Note that the list is now a list of [String: AnyObject] dictionaries, and not a list of Balance objects.
var resStr = ""
var list: [[String: AnyObject]] = [balance1.asDictionary(), balance2.asDictionary()]
do {
let data = try JSONSerialization.data(withJSONObject: list, options: .prettyPrinted)
resStr = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ") }
For certain types, like the date field, you need to find some way to convert it to String, as the JSONSerializer is very picky. In this case I just used String interpolation, but the format may not be what you want it to be like.
If you want to convert back to a Balance object from JSON, first you need to get JSON into a dictionary, and then check for each field if it exists, and if so, construct your Balance object.
Supposing you have converted your JSON data into a dictionary in the variable named dict, you could do something like the following:
// supposing you have a single object in dict
var balance: Balance
balance.details = dict["details"]
balance.amount = dict["amount"]
balance.date = parseDate(dict["date"])
Supposing you have a function parseDate to parse the date from String into a Date object.
You can take a look here for converting String date into an object: Use SwiftyJSON to deserialize NSDate
My issue is trying to convert a Date Picker result into a unix timestamp. My client is adamant that it'd be saved as a unix timestamp into Firebase database. Im also quite new to the swift so...
let myTimeStamp = NSDate(timeIntervalSince1970: self.datePicker?.date)
this is the result of datePicker: 2016-12-03 00:56:00 +0000
This is the error: Cannot convert value of type 'Date?' to expected argument type 'TimeInterval' (aka 'Double')
Please help sirs & ma'ams!
You already have the Date. Since you want the timestamp, call:
let myTimeStamp = self.datePicker?.date.timeIntervalSince1970
myTimeStamp will be an NSTimeInterval with a value in Unix time.
Including swift 5+
**Pass date like yourDatePicker.date **
func getTimeStampFromDate(date: Date) -> String {
return String(format: "%.0f", (date.timeIntervalSince1970 * 1000))
}
I used childByAutoId() save the timestamp as an auto-id. I can successfully retrieve the timestamp key that looks like -KJrs03bWbSTXfomqzMW. When I check the key's type, it says AnyObject.
let timestamps = snap.value.allKeys
for timestamp in timestamps {
print(timestamp) // -KJrs03bWbSTXfomqzMW
if let t = timestamp as? NSTimeInterval {
print(NSDate(timeIntervalSince1970: t/1000))
print("true")
} else {
print("false")
}
}
However, it goes into false bit.
What am I doing wrong? What is the proper way of converting Firebase timestamps to NSDate?
PS:
I remember I read Firebase allows for a global timing for the items with this kind of timestamp. Is there a way that would enable me to get human-readable version of the time, such as 30mins ago, 5 hrs ago directly working with Firebase or should I use NSDateFormatter?
The node looks like:
- -KJrs03bWbSTXfomqzMW
- "key1": "val"
- "key2": "val"
I have a block of JSON returned from a REST API in the following format:
[
{
id: 1,
locations: [
{
arriveAt: "2015-03-14T16:05:16Z"
},
{
arriveAt: null
]
},
...
]
I then have code in my project like this:
let trips = json as [NSDictionary]
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
for trip in trips {
Trip.createOrUpdateInDefaultRealmWithObject(trip)
}
realm.commitWriteTransaction()
In my Location class, there is a var dynamic var arriveAt: NSDate?. I also extended NSDate with a fromISO8601String method which initializes NSDate converting the ISO 8601 string to a date.
Is there a way that when Realm tries to create the Location object, it would automatically run the string from the JSON through NSDate.fromISO8601String?
Hi Dave there isn't anything built into Realm yet that can do that for you. You will need to use a transformer like Realm-JSON, or Mantle, etc.