The API returns the result as an object with more than thousand values in the following format:
result = {
"items": [
{
"Name": "abc",
"CreatedOn": "2017-08-29T15:21:20.000Z",
"Description": "xyz"
},
{
"Name": "def",
"CreatedOn": "2021-05-21T15:20:20.000Z",
"Description": "cvb"
}
]
}
I would like to filter items in the object which are more than 90 days old without having to iterate all the items using for loop. In other words I would like to do something like this below but this doesn't work.
var currentDate = new Date();
var newResult = result.items?.filter(function(i) {
return ((currentDate - Date.parse(i.CreatedOn)) > 90);
}
According to the IDE property CreatedOn
is of type string | undefined
so it throws the error: Argument of type 'string | undefined'
is not assignable to parameter of type 'string'
. Type 'undefined'
is not assignable to type 'string'
.
CodePudding user response:
Somewhere in your project you'll have something like this:
interface Result {
readonly items: readonly ResultItem[] | null;
}
interface ResultItem {
readonly Name : string;
readonly CreatedOn : string | undefined;
readonly Description: string;
}
or this (or variations thereof):
type Result = {
items?: ResultItem[];
}
interface ResultItem {
Name : string;
CreatedOn? : string;
Description: string;
}
Or it may be a type
instead of an interface
(just make sure you never use class
to describe JSON data, as JSON object
data cannot be a class
instance because the constructor never runs).
Also, you should be using camelCase
, not PascalCase
, for member properties. So use names like createdOn
instead of CreatedOn
in your generated JSON.
Fortunately You don't need to change the types/interfaces, just change your TypeScript to safely check .CreatedOn
and that Date.parse
did not return NaN
. Like so:
- The
result.items ?? []
part is because your post impliesresult.items
is nullable or maybe-undefined
. - Note when using
map
with an=>
-style function that you may need to wrap object-literals in()
so the JS engine doesn't interpret the{
and}
as block delimiters.
const result: Result = ...
const currentDate = new Date();
const newResult = (result.items ?? []).filter( e => {
if( typeof e.CreatedOn === 'string' ) {
const parsed = Date.parse( e.CreatedOn );
if( !isNaN( parsed ) ) {
return ( currentDate - parsed ) > 90;
}
}
return false;
} );
Though personally I'd do it with an initial filter
and map
steps:
const items = result.items ?? [];
const currentDate = new Date();
const newResult = items
.filter( e => typeof e.CreatedOn === 'string' )
.map( e => ( { ...e, CreatedOn2: Date.parse( e.CreatedOn ) } ) )
.filter( e => !isNaN( e.CreatedOn2 ) )
.filter( e => ( currentDate - e.CreatedOn2 ) > 90 ) );
or simplified further:
const items = result.items ?? [];
const currentDate = new Date();
const newResult = items
.filter( e => typeof e.CreatedOn === 'string' )
.map( e => Object.assign( e, { createdOn2: Date.parse( e.CreatedOn ) )
.filter( e => !isNaN( e.CreatedOn2 ) && ( currentDate - e.CreatedOn2 ) > 90 );
An even better solution:
If you're in control of how the JSON is generated then you can ensure that certain (or all) item properties will always be set (and so never
undefined
ornull
), so if you can guarantee that all 3 properties are always set (nevernull
orundefined
) then update your types/interfaces to this:interface ResultItem { readonly name : string; readonly createdOn : string; readonly description: string; }
- Note the
camelCase
properties. - Immutability of data is a huge benefit, so make sure your interface properties are all
readonly
, all arrays arereadonly T[]
, and that properties are only annotated with?
or| null
or| undefined
as-appropriate instead of simply assuming one way or another.
- Note the
So make sure you use
strictNullChecks
in yourtsconfig.json
ortsc
options! - actually, just usestrict
always!Also consider changing your JSON DTO from using a
string
representation of a Date (are there any gurantees about timezone?) to being a natively readable Unix timestamp (in milliseconds), that way you can avoid problems withDate.parse
entirely:
e.g.:
Result.cs:
public class ResultItem
{
[JsonProperty( "createdOn" )]
public DateTimeOffset CreatedOn { get; }
[JsonProperty( "createdOnUnix" )]
public Int64 CreatedOnUnix => this.CreatedOn.ToUnixTimeMilliseconds();
}
Result.ts:
interface ResultItem {
readonly createdOn : string;
readonly createdOnUnix: number;
}
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate( ninetyDaysAgo.getDate() - 90 );
const newResult = items.filter( e => new Date( e.createdOnUnix ) < ninetyDaysAgo );
...that way it's a single-line job.
The above can be made even simpler as Unix timestamps are just integers that are directly comparable, so new Date()
can be avoided inside the filter
, like so:
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate( ninetyDaysAgo.getDate() - 90 );
const ninetyDaysAgoUnix = ninetyDaysAgo.getTime();
const newResult = items.filter( e => e.createdOnUnix < ninetyDaysAgoUnix );
CodePudding user response:
Assuming you have interfaces defined like this...
interface Item {
Name: string,
Description: string,
CreatedOn?: string // note the optional "?"
}
interface Result {
items?: Item[] // also optional according to your code
}
and you want to filter for items that are older than 90 days (excluding those with no CreatedOn
), then try this
interface ItemWithDate extends Omit<Item, "CreatedOn"> {
CreatedOn?: Date // real dates, so much better than strings
}
const result: Result = { /* whatever */ }
const items: ItemWithDate[] = result.items?.map(({ CreatedOn, ...item }) => ({
...item,
CreatedOn: CreatedOn ? new Date(CreatedOn) : undefined
})) ?? []
const dateThreshold = new Date()
dateThreshold.setDate(dateThreshold.getDate() - 90)
const newItems = items.filter(({ CreatedOn }) =>
CreatedOn && CreatedOn < dateThreshold)
CodePudding user response:
code is missing ) of filter function
var currentDate = new Date();
var newResult = result.items?.filter(function(i) {
return ((currentDate - Date.parse(i.CreatedOn)) > 90);
} ) //<= misss