Convert date format from json data in the model and then pass to controller - swift

Here in my model I have a few struct to handle the data from server
struct Articles: Decodable {
var content : [ArticleData]
}
struct ArticleData: Decodable {
var title: String
var date_time: Date
var image: String
}
From the jason, I get a date, it's like that:
"date_time": 1524842056.035,
First question:
I set the type of this type in model Date as you see in ArticleData, it that correct?
Then I read this json in the model something like that:
guard let json = try? JSONDecoder().decode(Articles.self, from: data!) else {
completion(.failure(loginError.networkError))
return
}
completion(.success(json.content))
As you can see, I get an array from server json.content that contain title, date_time and image. I want to convert it here in the model and pass everything in completion to the controller.
could you help me on that?
Thanks :)

you can use type 'String' for your date and then convert it using dateFormatter or, use 'dateDecodingStrategy' function in your decoder.
for passing the data you are already doing the correct thing. where ever you call the method you can get the model.

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Decode Arbitrary JSON in Swift

I am trying decode a JSON with below structure and convert to a Swift struct object.
"{\"createdAt\":\"2021-07-20T06:53:05.755Z\",\"extraPayload\":\"32bd7d6691964519\",\"messageID\":\"a5f77d0a27da2e14a78ce4d736590bb3c369d5e5bebd36339553e3b699b82d13\",\"publishedAt\":\"2021-07-20T06:53:05.836655Z\"}"
Both the publishedAt and messageID fields are fixed and any other fields in the JSON can be arbitrary based on a particular use-case.
In this case both fields createdAt and extraPayload are completely optional and another case it could be something like appConfig:true etc apart from the 2 fixed fields.
How can I map into a struct with below format which has 2 mandatory fixed fields and arbitrary part captured using a Dictionary of type [String:Any]?
public struct MessagePayload {
/**
* Message identifier — opaque non-empty string.
*/
let messageID: String
/**
* ISO 8601 date indicating a moment when the message was published to the ACN back-end service.
*/
let publishedAt: String
/**
* Application-specific fields.
*/
let messageObject: [String:Any]?
}
I have tried using Swift Codable but it does not support [String:Any].
It seems like JSONSerialization from Foundation is probably a better fit for your use case. This will decode the JSON data directly into an Any type, which you can then cast to [String: Any] to access the underlying properties.
let decoded = try? JSONSerialization.jsonObject(with: myJsonData, options: []) as? [String: Any]
decoded?["messageID"]
decoded?["publishedAt"]
// ...access other properties as needed

How do I convert Postgres dates to ISO8601 in JSON responses with Vapor 3 running on Heroku?

I have a Vapor 3 API up on Heroku. Unfortunately, it's not handling dates correctly. Originally, I thought I could just treat dates like strings for simplicity in Vapor, like so:
struct MyModel {
var time: String?
}
But whenever I fetch MyModels from the db and return it, the time key doesn't appear at all (while other keys and values have no problems). I thought I might be able to just change time's type to Date, but that resulted in the same thing, and I've already used ContentConfig to set the JsonEncoder.dateEncodingStrategy to .iso8601 (again, no luck – perhaps because dateEncodingStrategy only supports millis on Linux, which is what Heroku uses?).
How do I convert Postgres dates to ISO8601 in json with Vapor 3 running on Heroku?
Got it working! Just changed the properties to Dates, and manually converted request query parameters to Dates as well (for use in filter calls). So, a little more by hand than most things in Vapor 3, but not terrible.
Eg my model looks like this now:
struct MyModel {
var time: Date?
}
And then when I try to filter by date I do something like this:
var builder = MyModel.query(on: req)
if let afterString: String = try? self.query.get(String.self, at: "after") {
let afterDate: Date? = DateFormatter.iso8601Full.date(from: afterString)
builder = builder.filter(\.time > afterDate)
}
where after is a url parameter, and DateFormatter.iso8601Full is my iso8601 date formatter. Then when I'm returning an array of MyModels in a response, I map the array to an array of MyModelResponseObjects which look like this:
struct MyModelResponseObject {
var time: String?
}
by doing something like this:
myModelsFuture.all().map(to: [MyModelResponseObject].self, { (myModels) -> [MyModelResponseObject] in
return myModels.map { it in
return MyModelResponseObject(time: DateFormatter.iso8601Full.string(from: it.time ?? Date.init(timeIntervalSince1970: 0)))
}
}
So basically I'm manually converting the dates into the format that I want when returning them in JSON.

NSNull into a Struct with a property of type NSDate

I have an object from the server that is recognized by Swift 2.1 as either NSDate or NSNull. I want to put it into a struct with a property of type NSDate.
Is that possible? If not, how should I handle this to be type safe later when I use it?
struct Data {
var completedAt: [NSDate]
var name: [String]
var gender: [Bool]
}
but sometimes completedAt comes from the server as NSNull:
completedAt = "<null>";
Any help is very much appreciated, thank you.
Based on my interpretation of the text in the question you didn't mean to declare the variables as arrays.
This is how I handle my parson and I think it works pretty neatly.
The date formatter should probable not be initiated in every iteration of the constructor. If you won't use the date regularly you might want to keep the detesting until you need to parse the date or you can have a static date formatter utility that you only instantiate once.
struct Data {
var completedAt: NSDate?
var name: String
var gender: Bool
init?(dictionary: [String:AnyObject]) {
//Guessing that you want some of the values non optional...
guard let name = dictionary["name"] as? String,
let gender = dictionary["gender"] as? String
else {
return nil
}
self.name = name
self.gender = gender
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//safe handle of optional values
if let completedAtString = dictionary["completedAt"] as? String, completedAt = dateFormater.dateFromString(completedAtString) {
self.completedAt = completedAt
}
}
}
Take a step back. For each item that the server might provide, there is no guarantee whatsoever that you receive what you expect, since you cannot control the server. So you need to decide how to react to which input.
In the case of expecting a date for example (if your data comes in JSON, that means you likely expect a string formatted in a certain way), the actual data that you receive might be an array, dictionary, string, number, bool, null, or nothing. You might then for example decide that you want to interpret nothing or null or an empty string as nil, that you want to interpret a string containing a well-formatted date as an NSDate, and anything else a fatal error in a debug version, and as either nothing or a fatal error in a release version. On the other hand, if an NSDate is absolutely required then you might interpret anything that doesn't give an NSDate as an error.
Then you write a function that delivers exactly what you want and use it. That way you can parse complex data, with your code warning you when something isn't as it should be, and with your code either surviving any possible input, or deliberately crashing on wrong input, as you want it.

Convert JSON AnyObject to Int64

this is somewhat related to this question: How to properly store timestamp (ms since 1970)
Is there a way to typeCast a AnyObject to Int64? I am receiving a huge number via JSON this number arrives at my class as "AnyObject" - how can I cast it to Int64 - xcode just says its not possible.
JSON numbers are NSNumber, so you'll want to go through there.
import Foundation
var json:AnyObject = NSNumber(longLong: 1234567890123456789)
var num = json as? NSNumber
var result = num?.longLongValue
Note that result is Int64?, since you don't know that this conversion will be successful.
You can cast from a AnyObject to an Int with the as type cast operator, but to downcast into different numeric types you need to use the target type's initializer.
var o:AnyObject = 1
var n:Int = o as Int
var u:Int64 = Int64(n)
Try SwiftJSON which is a better way to deal with JSON data in Swift
let json = SwiftJSON.JSON(data: dataFromServer)
if let number = json["number"].longLong {
//do what you want
} else {
//print the error message if you like
println(json["number"])
}
As #Rob Napier's answer says, you are dealing with NSNumber. If you're sure you have a valid one, you can do this to get an Int64
(json["key"] as! NSNumber).longLongValue