Typescript - typing array methods - javascript

I am having trouble typing array methods
const person = students.findIndex((student) => student.id === 23)
The first error I get is on student element in the brackets
const person = students.findIndex((student) => student.id === 23)
TS7006: Parameter 'student' implicitly has an 'any' type.
This can be fixed by
const person = students.findIndex((student:any) => student.id === 23)
Which is not great so I try
const person = students.findIndex((student:Object) => student.id === 23)
but I get
TS2339: Property 'id' does not exist on type 'Object'
I assume because Object is a generic Type.
What is best practice here? I do hundreds of these with filter, map, reduce do I have to define the element being processed by the method in typesscript?

Create Student interface and provide the same in place of Object and any other places for type check. I recommend to mention type explicitly(good coding practice) even though TS can do implicit type check.
interface Student {
id: number;
name: string;
// other properties...
}
const students: Student[] = [{
id: 123,
name: "test123"
},
{
id: 456,
name: "test456"
}]; // Suppose this is the data example
const person = students.findIndex((student: Student) => student.id === 23)

It's better to use TypeScript's implicit typing.
Create a type
type Student = {
name: string;
};
const students: Student[] = [
{ name: 'ABC' },
{ name: 'DEF' }
];
const person = students.findIndex( student => student.name === 'ABC' ); // 0
When you declare your students variable as an array of Student type, your array prototype will extend itself to include Student types. So your array prototype methods will consider Student as their type as long as you call those methods on the students array.

Related

How to iterate an object on TypeScript changing a string value for its corresponding number?

I have a type coming from a 3rd party API which has a lot of properties (50+) and they take all values as string. The even number and booleans became strings ("5" and "false" respectively) and I want to fix the scary thing.
So I created a type like this to receive the response from API and to hold after fix
interface Person {
age: string | number,
name: string,
hasChildren: string | boolean,
...
}
And I want to transform this
const responseFromApi: Person = {
age: "20",
name: "John",
numberOfChildren: "true"
...
}
to
const afterTreatment: Person = {
age: 21,
name: "John",
numberOfChildren: true
...
}
This is an example... My object, again, is much bigger than this, with a lot of props in this situation so treat them individually is not the kind of solution I'm looking for.
My intention is to iterate over the object and change to number or boolean what can be changed following type.
You could for-loop the array, check if the element.age is a string and if yes parseInt the age and set it again.
A better solution would maybe be to map though the list, and do the same thing as above, just so it creates a new array, which you could then overwrite/do what you need.
Idea:
const changed = persons.map((p: Person) => {
if (typeof p.age === "string") {
return {
...p,
age:parseInt(p.age)
}
}
return p
});
This should work as long as the variable conventions are consistent with person1, person2, person3 etc and also you need to know the total number of added persons for the forloop to work
interface Person {
age: string | number,
name: string
}
const person1: Person = {
age: 20,
name: "Jonh",
}
const person2: Person = {
age: "21",
name: "Marie",
}
const lengthOfPersons: number = 2;
const newArray = [];
for(let i = 0; i < lengthOfPersons; i++)
{
const n = i + 1;
const row = eval('person'+n);
newArray.push({...row, age: +row.age})
}
console.log(newArray);

Why missing properties are not checked while using type assertion?

TS playground
Why when I use as with type then required object keys are not checked anymore? For example I have Person type where I need to have name and age.
type Person = {
name: string,
age: number,
}
const demo: Person = { // OK - missing 'age' property error
name: 'test',
}
const demo2 = { // no missing 'age' property error?
name: 'test',
} as Person
const demo3 = { // no missing 'age' property error?
people: [{
name: 'test',
}] as Person[]
}
With type assertion you are actually "forcing" the data to be considered as some type so, as long as the two types overlap in some way TypeScript allows you to do this.
Sometimes TypeScript also reminds you (when the data and the type does not overlap at all) to declare that "you are sure of what you are doing" declaring the data as unknown and then as the wanted type, like in this case:
// No overlap at all
const demo3 = {
people: [{
foo: 'test',
}] as Person[]
}
// telling TypeScript "yes, I'm sure of what I am doing"
const demo4 = {
people: [{
foo: 'test',
}] as unknown as Person[] // also "as any as Person[]" will work
}

Iterating through a an array executing a switch statement returns TypeError: Cannot assign to read only property 'location' of object '#<Object>'

I have a read only array that i copied to become a mutable array let mutableForecast = [...forecast] I am taking that new array and iterating through it with forEach so i can mutate the array. im trying to use some flow control with a switch statement, but I am getting TypeError: Cannot assign to read only property 'location' of object '#<Object>'
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
switch (obj.location) {
case obj.location === "BRITISH_COLUMBIA":
obj.location = "BC"
break;
default:
obj.location = "oother"
}
})
Whats the issue here? I've look at this, this, this and some others but cannot find an answer.
This is what the forecast array looks like before i copied it
It's hard to be sure without knowing where forecast comes from, but I suspect the problem is that the elements of the array are not plain objects, but instances of a custom type that are defined as immutable. Your third link has the likely solution. The key is that you can't convert an array of immutables into an array of mutables simply by using rest & spread in this way. You need to modify the mutability of each item in the array individually.
You probably need something like this:
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
// make this element's location property mutable
Object.defineProperty(obj, 'location', { writable: true })
// calculate and set new value
switch (obj.location) {
case 'BRITISH_COLUMBIA':
obj.location = 'BC'
break;
default:
obj.location = 'other'
}
})
This might also work, and I think it's cleaner. You'd have to try it to be sure:
let mutableForecast = Array.from(forecast)
.map(forecastItem => ({
...forecastItem,
location: getShortLocation(forecastItem.location)
}))
function getShortLocation( sourceLocation ) {
switch (sourceLocation) {
case 'BRITISH_COLUMBIA': return 'BC'
default: return 'other'
}
}
The core problem we're working around is that whatever package gives you forecast, it clearly trafficks in some custom datatype, some of whose properties are defined as immutable. That fact doesn't show up when you log the objects, and it isn't changed when you convert an array-like container into an array.
That's because [...forecast] doesn't edit the items, it just copies them as-is from one data structure into another. Actually, be to precise, it copies references to those objects into a new array. If the original objects are weird things with locked properties, then your new array will consist of weird things with locked properties. If we want to change the value of that property on each element, we need to redefine the property before doing so.
Consider a case like this:
let myDog = {
species: 'dog',
name: 'Fido'
}
//> myDog { species: 'dog', name: 'Fido' }
We can create another object with the same properties like so:
let congruentAnimal = {
...myDog
}
//> congruentAnimal { species: 'dog', name: 'Fido' }
If the same property names occurs twice, the engine will only honor the last one:
let myDog = {
species: 'cat',
name: 'Fido',
species: 'dog' // this will cause cat to be ignored
}
//> myDog { name: 'Fido', species: 'dog' }
So, we can override individual object properties while copying by re-declaring those properties last:
let anotherCongruentAnimal = {
...myDog,
species: 'NEW DOG'
}
//> anotherCongruentAnimal { name: 'Fido', species: 'NEW DOG' }
That's what is going on in that second snippet. Here's an expanded version:
// create a real array whose elements are *references* to
// the objects in the array-like forecast
let arrayOfImmutableForecasts = Array.from(forecast)
// create another real array of new objects
// whose property names, values, and metadata are
// the same as the source objects
let arrayOfMutableForecasts = arrayOfImmutableForecasts.map(originalObject => {
let newObject = {
// I think this will also preserve special rules like immutability
...originalObject,
// before we finalize the object, we declare a new simple property
// the engine will _drop_ the implied prop declaration from above
// and define the prop based on this simple declaration instead
location: 'new value'
}
return newObject
})
It seems like you are not allowed to mutate the location property of the objects in the array...
You can try creating a clone of the object and mutate that:
let mutableForecast = [...forecast]
mutableForecast = mutableForecast.map(obj => {
const location = obj.location
const objClone = {}
for (const key in obj) {
if (key !== 'location') objClone[key] = obj[key]
}
switch (location) {
case "BRITISH_COLUMBIA":
objClone.location = "BC"
break;
default:
objClone.location = "other"
}
return objClone
})
If that fails, you can try creating a new property insteade, and later read that property:
let mutableForecast = [...forecast]
mutableForecast.forEach((obj, i) => {
switch (obj.location) {
case "BRITISH_COLUMBIA":
obj.newLocation = "BC"
break;
default:
obj.newLocation = "other"
}
})

Type 'string | number | boolean' is not assignable to type 'undefined'. Type 'string' is not assignable to type 'undefined'.ts(2322)

I'm trying to create a partial object that only has certain fields of the full object that meet a criteria. However, I get the subject typescript error message when I try to assign the property. I created a test module to illustrate the concept/problem. Note that this is only to illustrate the problem. It is not the actual code.
type FullObject = {
id: number
name: string
active: boolean
}
type PartialObject = Partial<FullObject>
const myFullObj: FullObject = {
id: 1,
name: 'First Object',
active: true,
}
const myPartialObj: PartialObject = {}
let k: keyof PartialObject
for (k in myFullObj) {
if (myFullObj[k] !== undefined) myPartialObj[k] = myFullObj[k] // Error here
if (k === 'name') myPartialObj[k] = myFullObj[k] // No error here
}
Note that it is only the first "if" statement that has the error. After some research and trying various things, I worked around the problem by initializing the partial object to the full object and then deleting properties that did not meet a criteria. Since this is a backwards way of solving the problem, I would prefer to create the partial object with properties that meet criteria.
I came up with the following solution. It clearly illustrates what I am trying to do: if a criteria is met with a source object property, then copy that property into the partial destination object. In this example I'm using "not undefined" as the criteria. In the real code the criteria is more complex.
type FullObject = {
id: number
name: string
active: boolean
}
type PartialObject = Partial<FullObject>
const myFullObj: FullObject = {
id: 1,
name: 'First Object',
active: true,
}
let myPartialObj: PartialObject = {}
let k: keyof PartialObject
for (k in myFullObj) {
if (myFullObj[k] !== undefined) myPartialObj = { ...myPartialObj, ...Object.fromEntries([[k, myFullObj[k]]]) }
}
console.log(JSON.stringify(myPartialObj, null, ' '))
It seems that there must be a better way to accomplish this. However, the example illustrates what is intended.

Flow: Inference error in for-of loop

I have a code this is trying to validate object with attributes id and _name for specified type. Attribute id should be a number and name should be a string as declared in FooT type.
function number(value: mixed): number {
if (typeof value === "number") return value
throw new TypeError("number required")
}
function string(value: mixed): string {
if (typeof value === "string") return value
throw new TypeError("string required")
}
function objectOf(attrs, value) {
const obj = {}
for (const key of Object.keys(attrs)) {
const typeFn = attrs[key]
obj[key] = typeFn(value[key])
}
return obj
}
type FooT = {
id: number,
name: string
}
const fooT: FooT = objectOf(
{
id: number,
name: string
},
{
id: 1,
name: "Foo"
}
)
Running flow shows this error. For some reason inferred return type of typeFn is not correctly determined in for-of loop when accessing object attribute values dynamically.
Cannot assign objectOf(...) to fooT because:
• string [1] is incompatible with number [2] in property id.
• number [3] is incompatible with string [4] in property name.
[3] 3│ function number(value: mixed): number {
:
[1] 8│ function string(value: mixed): string {
:
[2] 22│ id: number,
[4] 23│ name: string
24│ }
25│
26│ const fooT: FooT = objectOf(
27│ {
28│ id: number,
29│ name: string
30│ },
31│ {
32│ id: 1,
33│ name: "Foo"
34│ }
35│ )
36│
Is this an issue with flow or am I missing something?
It looks like you are running into issue #935 Type of object value not inferred correctly in for-in loop. You should be able to use the suppress_comment config and just put $FlowFixMe in the code to tell Flow to ignore that.

Categories

Resources