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.
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’m coding Swift for iOS 11, and in some context (described in part 2) I end up with a variable of type String. However, when examining the variable in Xcode’s debugger the type is shown as _PFEncodedString (which I have read elsewhere is an internal subclass of String). For some reason (described below, but not directly related to the first part of my question) I want to transform the _PFEncodedString variable to a plain String variable. The only way I’ve found to do that is as follows:
var attributeName : String
// ... attributeName is assigned a value and ends up being _PFEncodedString
let data = attributeName.data(using: String.Encoding.utf8)!
let plainName = String(data: data, encoding: String.Encoding.utf8)! // this is type String not _PFEncodedString
The first part of my question is: Is this conversion safe or is there is simpler or better way to do it?
Part 2. For the second part of my question I will first describe the context where the above becomes an issue. I’m using Core Data and want to export attribute names (and values) to JSON. Using reflection I can find Core Data entity names and find the attribute names that should be exported to JSON. The problem is that when I use JSONEncoder with this data, the generated JSON contains Chinese characters whereas the Core Data model attribute names do not. I have found that JSONEncoder produces partly Chinese output when Strings in the exported object is of type _PFEncodedString, but works fine when handling "plain" String types. Therefore the first part of my questions.
Here is code that illustrates the problem, boiled down to a near minimum. QPTVideo is a subclass of NSManagedObject with two attributes ‘qaUUID’ and ‘thumbnailData’.
// First define a struct to be used with JSONEncoder
struct AttributeForJSON : Codable {
var name : String
}
let entityDescription = QPTVideo.entity()
let attributes = entityDescription.attributesByName // this is an array of (key: String, value: NSAttributeDescription).
var allAttributes : [AttributeForJSON] = [] // this will be an array of all the attribute names for export to JSON.
for attribute in attributes {
let attributeName = attribute.key // NOTE: debugger shows this as: attributeName String class name = _PFEncodedString
let aj = AttributeForJSON(name: attributeName)
print(aj.name) // this works just fine, printing “qaUUID” or “thumbnailData”
allAttributes.append(aj) // when exported, the attribute names look nothing like they should, containing Chinese characters.
// now use the work-around from first part of my question:
let data = attributeName.data(using: String.Encoding.utf8)!
let plainName = String(data: data, encoding: String.Encoding.utf8)! // NOTE: debugger shows this as: plainName String "thumbnailData"
let ajplain = AttributeForJSON(name: plainName)
allAttributes.append(ajplain) // when exported, the attribute names are correct.
}
// the rest is just use of JSONEncoder to generate the JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(allAttributes)
try data.write(to: URL(fileURLWithPath: “/Users/someone/so.json"), options: Data.WritingOptions.atomic)
} catch {
print(error.localizedDescription)
}
The exported JSON looks like this (where I would have expected both of the first two name values to be "qaUUID", and the next two to be "thumbnailData"):
[
{
"name" : "慱啕䑉\u0000戨্"
},
{
"name" : "qaUUID"
},
{
"name" : "桴浵湢楡䑬瑡a\u0000戨্\u0001\u0000\u0000"
},
{
"name" : "thumbnailData"
}
]
So, my here question is: Why do I get Chinese characters? What am I doing wrong in how I use the Core Data reflection API and/or JSONEncoder? How do I do this without resorting to the work-around from the first part of my question?
Ok so I'm able to parse my JSON to my Model struct which looks like this:
JSON:
{
"base":"CHF",
"date":"2017-02-09",
"rates":{
"AUD":1.3086,
"BGN":1.8326,
"BRL":3.123,
"CAD":1.3133,
"CNY":6.879,
"CZK":25.32,
"DKK":6.9665,
"GBP":0.79732,
"HKD":7.7729,
"HRK":6.9992,
"HUF":289.31,
"IDR":13280.0,
"ILS":3.7553,
"INR":66.867,
"JPY":112.48,
"KRW":1146.2,
"MXN":20.482,
"MYR":4.4473,
"NOK":8.3265,
"NZD":1.3871,
"PHP":50.008,
"PLN":4.0382,
"RON":4.2115,
"RUB":58.914,
"SEK":8.8863,
"SGD":1.4173,
"THB":35.076,
"TRY":3.7,
"USD":1.0019,
"ZAR":13.435,
"EUR":0.93703
}
}
MODEL:
struct TestStruct {
var base: String
var date: String
var rates: [String: Double]
init(base: String, date: String, rates: [String:Double]) {
self.base = base
self.date = date
self.rates = rates
}
}
But now I have no Idea how I'm going to store my Model in CoreData I know how store the base and date because those are just strings but how can I store a Dictionary or maybe convert it to something because I will need the "rates" Dictionary back from CoreData since I need to know which currency has which exchange rate...
Core Data is so complex that I suggest you search on Google for tutorials. However, I built the proper data model. May it help you.
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
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"