Reputation: 101
I noticed, when receive data from my Server/API (in this case asp.net core 3.1) Angular assigns a string to the date target field instead of actually "converting" it into a Date.
As far as I can tell for now, it's not a problem with asp.net core or the "server side".
I did the following Test and either I understand something completly wrong here or the JSON parser is broken...(i hope not)?
//The target interface for testing
export interface DateTesting
{
aText: string;
aDate: Date;
}
//For testing
let input: DateTesting = { aText: "Hello World!", aDate: new Date() };
console.log(JSON.stringify(input)); //-> outputs the "input" object as JSON
let json = JSON.stringify(input); //-> {"aText":"Hello World!","aDate":"2022-07-12T12:01:46.498Z"}
let output: DateTesting = JSON.parse(json);
console.log(output); //-> outputs the object "output"
console.log(typeof output.aDate); //-> is a string! Looks like the parser is broken??
console.log(output.aDate.getDay()); //-> Results in an Error: "Uncaught (in promise): Invalid time"
So, what am I missing here? Can i tell the parser anyhow to assign a date and not a string when the target type is a Date?
I also don't think that the correct solution is to do "Date.parse(whatsoever)" for each date field, also because I want to keep the whole thing as "generic" as possible.
I'm using Angular 12.1.2
Upvotes: 0
Views: 1146
Reputation: 3593
Another solution would be to write an interceptor, which will automatically convert all date strings into Date objects.
@Injectable({
providedIn: 'root'
})
export class DateInterceptor implements HttpInterceptor {
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
this.postProcessDates(event.body);
return event;
}
})
);
}
/**
* PostProcessing Dates
* Converting UTC to Local Date
* Date must be in ISO 8601 format
*/
private postProcessDates(obj: any): any {
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj !== 'object') {
return obj;
}
for (const key of Object.keys(obj)) {
const value = obj[key];
const iso8601 = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/;
if (iso8601.test(value) === true) {
obj[key] = new Date(value);
} else if (typeof value === 'object') {
this.postProcessDates(value);
}
}
}
}
Upvotes: 1
Reputation: 492
For a generic approach that covers any Date property in your interfaces you can use this Decorator
import {map} from "rxjs/operators";
let dateRegex = new RegExp(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); // yyyy-MM-ddTHH:mm:ss
/**
* Automatically transforms all dates in json in defined regex format to Date entity
*/
export function TransformDate(target, key: string, descriptor: any) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
return originalMethod.apply(this, args).pipe(
map((obj) => convertDatesInObject(obj))
);
}
return descriptor;
}
const isDate = (s) => dateRegex.test(s);
function convertDatesInObject(obj): any {
if (obj === null || obj === undefined) {
return obj;
}
if (isDate(obj)) {
return new Date(obj) as any;
}
if (obj instanceof Array) {
return obj.map((n: any) => convertDatesInObject(n)) as any;
}
if (typeof obj === 'object' && obj !== {}) {
Object.keys(obj).forEach(k => {
obj[k] = convertDatesInObject(obj[k]);
});
return obj;
}
return obj;
}
And then you can just put this decorator to any function calling your API and all string that match the defined regexp will be then parsed to Date object automatically.
@TransformDate
getData$(): Observable<Data[]> {
return this.httpService.getRequest<Data[]>('yourURL');
}
Upvotes: 1
Reputation: 4582
Can i tell the json parser anyhow to assign a date and not a string when the target type is a Date?
Yes, you can if you use the JSON.parse
method with the optional reviver
argument. From the documentation if a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. So in your specific case you can specify your aDate
property and return your date string as a new Date
object instead of a string like below:
let output: DateTesting = JSON.parse(json, (key, value) => {
//instead of a string it returns a Date object
if (key === 'aDate') return new Date(value);
return value; // return the unchanged property value for other keys.
});
console.log(typeof output.aDate); //-> is a Date object
console.log(output.aDate.getDay()); //ok it works and returns a number
Upvotes: 1