Reputation: 325
I am having the following two service calls, which both are using the method concertDates().
private callMyFirstService(params: MyParams): Observable<MyDto> {
return this.http.post<MyDto>(params).pipe(map(response => this.convertDates<MyDto>(response)));
}
private callMySecondService(params: MyOtherParams): Observable<MyAnotherDto> {
return this.http.post<MyAnotherDto>(params).pipe(map(response => this.convertDates<MyAnotherDto>(response)));
}
Since these methods are operating with another type of dtos, I try to implement some generic funtion which can be called in both cases. I have something like that:
private convertDates<type>(input: type): type {
for (const key in input) {
if (!input.hasOwnProperty(key)) {
continue;
}
if (typeof input[key] === 'object') {
this.convertDates(input[key]);
} else if (typeof input[key] === 'string' && this.MY_REGEXP.test(input[key])) {
input[key] = new Date(input[key]);
}
}
return input;
}
This does not seem to work, becase I always get:
Type 'Date' is not assignable to type 'type[Extract<keyof type, string>]'
and...
Argument of type 'type[Extract<keyof type, string>]' is not assignable to parameter of type 'string'.
Type 'type[string]' is not assignable to type 'string'.
Any ideas? Thanks a lot in advance!
Upvotes: 1
Views: 1731
Reputation: 186984
Whatever type that type
is has a specific shape. You seem to be expecting an object that has some properties that are strings, which you are changing to be a Date
instead. This means that you are in violation of that type.
Or more simply:
const obj = { maybeDate: 'foo' } // type is { maybeDate: string }
obj.maybeDate = new Date() // error
The type of maybeDate
is string
, so you can't just assign a Date
to that slot. That violates the type.
Typically when doing a conversion like that, you need to cast the object to an intermediary type, and then cast it to the final type.
const obj = { maybeDate: 'foo' } // type is { maybeDate: string }
const objIntermediate = obj as { maybeDate: string | Date }
objIntermediate.maybeDate = new Date()
const objFinal = objIntermediate as { maybeDate: Date }
At this point it's important to say that your logic is impossible to type. First, because typescript doesn't support regex transformations, but more importantly the type system can't know if a string
should be converted into a date, since it won't know the contents of that string ahead of time.
interface MyDto {
name: string
createdAt: string // How could typescript possible know this is a date string?
}
I've solved in my own codebase in a different way. I type the Dto
to have the fields as Date
, for example:
interface MyDto {
name: string
createdAt: Date
}
And then use some custom JSON decoder logic to change the fields:
/** Parse JSON content from a response. If any string matches the ISO date time format, convert it to a Date. */
async function decodeJson(res: Response): Promise<any> {
return JSON.parse(await res.text(), (key: string, value: unknown) => {
if (typeof value === 'string' && dateFormat.test(value)) {
return new Date(value)
}
return value
})
}
const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/
Which is called by:
function fetchStuff<T>(path: string) {
const res = await window.fetch(path)
const json = await decodeJson(res)
return json as T
}
// Then in some async function
const myStuff = await fetchStuff<MyDto>('/api/foo')
myStuff.createdAt.getHours() // works
This works because the response is any
until it's cast to the generic type, so typescript let's you do anything with it. (I'm not sure how to adapt this to rxjs powered code, to be honest)
The biggest downside of this whole approach of auto-converting dates is that if someone manually types into some text field a date like string, it will get converted into a Date, but your Dto will have that typed as a string, and that could crash your application. For example someone types in their name as 2015-01-02
. In my application, this is so exceedingly unlikely that it's worth the convenience. But that may not be tolerable for you.
As a final side note, the headaches that would have been saved if JSON had a native Date
type are no doubt innumerable...
Upvotes: 2