How can we send Date object to Rest Api in swiftui? - swift

Currently in my DTO class, I am declaring a type like this.
var transferDate : Date?
private enum CodingKeys: String, CodingKey {
case transferDate = "TransferDate"
}
override func encode(to encoder: Encoder) throws{
try super.encode(to:encode)
try container.encode(transferDate, forKey: .transferDate)
}
In my view, I am assigning Date like this
Dto.transferDate = self.Date
while debugging I see this in debug window
(Date) <unavailable; try printing with "vo" or "po"
Any help would be appreciated.
Thanks

You shouldn't have to do anything custom for Dates. As I recall, by default the system encodes dates as a Double like the one returned by Date.timeIntervalSinceReferenceDate().
If you're writing the RESTFUL API, you should be able to write it to handle that date format.
If you need "internet dates" (date strings in the standard format that includes an offset from UTC) you would need to specify that when setting up your encoder.

Related

Swift Tests: Spying on `Encoder`?

I have some Swift models that encode data to json. For example:
struct ExampleModel: Encodable {
var myComputedProperty: Bool { dependentModel.first(where: { $0.hasTrueProperty}) }
enum CodingKeys: String, CodingKey {
case firstKey = "first_key"
case secondKey = "second_key"
case myComputedProperty = "my_computed_property"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstKey, forKey: .firstKey)
try container.encode(myComputedProperty, forKey: .myComputedProperty)
}
}
The encoded data is sent to an API, and cross-platform system tests in this case are logistically tricky, so it's important I write tests that ensure the encoded data is as expected. All I'm looking to do is ensure container.encode receives expected keys and values.
I'm somewhat new to Swift, and trying to override the container, its dependencies & generics, and its .encode method is taking me down a rabbit-hole of rewriting half Swift's encoding foundation. In short: the spy I'm writing is too complex to be useful.
Despite lack of Google/StackOverflow results, I'm guessing spying on encoders is common (?), and that there's an easier way to confirm container.encode receives expected values. But the way swift's Encoder functionality is written is making it hard for me to do so without rewriting half the encoder. Anyone have boilerplate code or an example of effectively spying on container.encode?
I have never done tests spying on encoder, although I have plenty of tests checking correction of encoding/decoding.
There are multiple ways to do it:
You can get something like SwiftyJSON and inspect encoded JSON
You can use OHHTTPStubs to mock sending a request to API, and examine the request the way it's sent (this allows to examine not only JSON, but headers as well)
But the most basic way to test is encoding and then decoding your data structure, and comparing the structures. They should be identical.
How it's done:
Create a Decodable extension for your struct (or if your struct is Codable, then you already ave this). It can be added directly in the test class / test target if you don't need it in the production code:
extension ExampleModel: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstKey = try container.decode(<...>.self, forKey: .firstKey)
// etc
}
}
Add Equatable extension for the struct:
extension ExampleModel: Equatable {
// Rely on synthesized comparison, or create your own
}
Add a test:
let given = ExampleModel(firstKey: ..., ...)
let whenEncoded = try JSONEncoder().encode(given)
let whenDecoded = try JSONDecoder().decode(ExampleModel.self, from: whenEncoded)
// Then
XCTAssertEqual(given, whenDecoded)
Couple of notes:
It's very unusual to encode computed property. It's not prohibited, but it breaks the immutability of the property you are about to send to API: what if it changes after you called encode, but before it was sent to API? Better solution is to make a let property in the struct, but have an option to create a struct with this value given with appropriate calculation (e.g. init for ExampleModel could be passing the dependentModel, and the property would be calculated in the init once)
If you still choose to have a calculated property, you obviously cannot decode into it. So in that case you will need to preserve the decoded property in some class variable to compare it separately.

SwiftUI return type Error: Cannot convert return expression of type 'Foundation.Date' to return type 'BarCharts.Date'

I am doing a bar chart in my SwiftUI app, however I encountered a problem...
struct ViewMonth: Identifiable {
let id = UUID()
let date: Date
let viewCount: Int
}
I defined several variable types in the above code.
struct Date {
static func from(year: Int, month: Int, day: Int) -> Date {
let components = DateComponents(year: year, month: month, day: day)
return Calendar.current.date(from: components)!
}
}
It seems like I can't convert the expression type 'Foundation.Date' to 'BarCharts.Date'. I don't understand the error message. Please help!
I expect the code will yield no errors.
The error complains about a terminology conflict of your type Date with existing Date in Foundation.
Basically there are three options:
Replace struct Date with extension Date.
Rename your struct Date as something which does not interfere with any type in the Foundation framework.
Declare all instances of your custom type as BarCharts.Date.
I recommend the first option.
There are two types: Foundation.Date is the date type that everyone uses. And I suppose you defined your struct Date inside a class Barcharts, so it is a Barcharts.Date.
The “from” function is declared to return Date - which is a shortcut for Barcharts.Date. Now the error message should make sense. It’s up to you to decide what you wanted it to return.

How to validate an input string as a valid date in Kotlin?

So my Kotlin app is accepting an input String that should be a date in a certain format:
fun haveFun(dateStr: String){
var formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy")
var formattedDate = dateStr.format(formatter)
println(formattedDate)
}
The issue is that no matter which input string I send, it seems that everything is valid and no error is thrown.
The program is cool with whatever text I sent it: 61-Boby-2019 or even I'm not a date
From Java I'm used to some exception to be thrown or an isValid method, but I didn't find such yet in Kotlin
This should work. instead of using the format() use parse() it will throw exception if it fails and handle that at calling side.
#Throws(ParseException::class)
fun haveFun(dateStr: String) {
var formatter = SimpleDateFormat("dd-MMM-yyyy", Locale.getDefault())
val date = formatter.parse(dateStr)
println(date)
}
it will throw error like below:
java.text.ParseException: Unparseable date: "im not date"
You are using the wrong methods to format your date.
The method format which you are using (dateStr.format(formatter)) is for formatting the input which takes the current string as a format string as stated here:
fun String.format(vararg args: Any?): String Uses this string as a
format string and returns a string obtained by substituting the
specified arguments, using the default locale.
You need to do something else in order to achieve what you are looking for.
var formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy")
formatter.format(LocalDate.now()) //12-Dec-2019

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.