How to trim all string properties of a class instance? - arrays

I have an instance of some class.
Let's say this class is Person:
class Person {
name?: string | null;
age?: number | null;
friends!: Person[];
isLucky: boolean;
}
How to iterate over this instance and call trim() method on all properties that are strings? Because if I'm trying to do this:
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (typeof value === 'string') {
person[key] = value.trim();
}
});
My friend Typescript shows this error:
Type 'string' is not assignable to type 'person[keyof person]'.
I want to write an all around method suitable for instances of different classes with many different properties.
Is there a way to achieve this in Typescript? May be some typing magic?

I would do it this way. Just cast it to any.
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (typeof value === 'string') {
(person as any)[key] = value.trim();
}
});

TypeScript isn't able to determine if the key is associated with a specific typed key from Person, only that it's one of them. So you'll get a type of never when accessing person[key] without any other specific checks on key.
The quickest answer is to narrow your key:
(Object.keys(person) as (keyof typeof person)[]).forEach((key) => {
const value = person[key];
if (key === 'name' && typeof value === 'string') {
person[key] = value.trim();
}
});
Alternatively you could do the .trim() in your class constructor and avoid this entirely, but it's unclear what the context is for your issue.
Generally I avoid doing a forEach to map an Object and might favor Object.from over an Object.entries with a .map() or reconsider the data structure entirely depending on what your actual use case might be.

Related

How to access typescript type keys?

There was a following task and I cannot understand in any way how to make it.
I have TypeScript type. It is an object with enumerated keys. I want to make a universal function for filtering an array, which consists of objects of this type.
In the function below, oldArray is the array of objects to filter, and keyOldArray is one of the keys of type TObject . I know that I need to use this function in two different places and that the two keys that will be used are name and description for example. How do I specify this for TypeScript? Thank you all in advance!
type TObject = {
id: string;
description: string;
name: string;
tob: string;
};
const arrayFilter = (oldArray: TObject[], keyOldArray: ???) => {
return oldArray.filter((item) => item.keyOldArray === somethingConst);
};
You can use typescript's keyof operator: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html
It looks like you are trying to filter based on the index of keyOldArray, so you'll have to pull out index from the filter callback as well.
const arrayFilter = (oldArray: TObject[], keyOldArray: (keyof TObject)[]) => {
return oldArray.filter((item, index) => item[keyOldArray[index]] === somethingConst);
};

TypeScript iterating over object and creating a new one

I have two objects of the same interface.
interface SizeFilter {
key: string | undefined
}
const defaultFilter = {
key: undefined,
} as SizeFilter;
I have a function which basically checks if there is a key set in a state and accordingly get filter options:
const { filters } = useSelector(({ sizeCurves }) => sizeCurves);
// Get FilterOptions for filtering on the page.
const getSelectedFilters = () => {
const pageFilter = defaultFilter;
// TODO: add an Object loop to check all properties and define value.
pageFilter.sizeCurveKey = filters.sizeCurveKey ?? undefined ;
return pageFilter;
};
I have to decide whether I should take a filter value from the filters or from a default filter. And for that, I am setting pageFilter in the getSelectedFilters function.
When I try to iterate over defaultFilter using Object.Keys().map,
Object.keys(defaultFilter).map(key =>{
const pageFilter[item] = (defaultFilter[ key ])? defaultFilter[ key ] : undefined;
});
My editor throws warning on defaultFilter[ key ]
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type 'SizeFilter'. No index
signature with a parameter of type 'string' was found on type
'SizeFilter'.
I am not sure how to fix this issue.
The reason for this behavior is to prevent runtime errors that come from indexing an object by unknown keys. To prevent that you need to check first if the key is valid for defaultFilter. You can achieve this with a small helper function:
function hasKey<O>(obj: O, key: keyof any): key is keyof O { return key in obj }
You can use the function like this afterwards:
if (hasKey(defaultFilter, key)) {
defaultFilter[key]
}

Types for a function that receives an object of type `[key: string]: SOME_TYPE` and returns an array of type `SOME_TYPE[]`

I have some objects containing DB documents that I constantly need to convert to arrays.
Example:
const MY_OBJECT = {
docId_1: {...doc1},
docId_2: {...doc2},
docId_3: {...doc3},
// AND SO ON
}
I need to convert it to an array like this:
const MY_ARRAY_FROM_OBJECT = [
{...doc1},
{...doc2},
{...doc3},
// AND SO ON...
]
And this is code I use to do the conversion:
function buildArrayFromObject(obj) {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
And I need to type that function so I can use it with objects that has types like these:
interface BLOGPOSTS_ALL {
[key: string]: BLOGPOST
}
interface PRODUCTS_ALL {
[key: string]: PRODUCT
}
So when I call them with each different object, I want Typescript to know what the return array type will be.
For example:
const BLOGPOSTS_ALL_ARRAY = buildArrayFromObject(BLOGPOSTS_ALL); // SHOULD BE "BLOGPOST[]"
const PRODUCTS_ALL_ARRAY = buildArrayFromObject(PRODUCTS_ALL); // SHOULD BE "PRODUCT[]"
Here is what I've tried:
function buildArrayFromObject<T, K extends keyof T>(obj: T): T[K][] {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
But I'm getting this error:
And the returning type of the function is being evaluated by Typescript as type never[]
What you could do is the following:
Create an abstraction type to define what your database handles look like:
// Create an Entity type to define how your database objects look like
type Entities<T> = { [key: string]: T | undefined }
Therefore you can express your products and blogposts like this:
type BLOGPOSTS = Entities<BLOGPOST>;
type PRODUCTS = Entities<PRODUCT>;
In order to convert them in type-safe manner into an array you can use the Object.values method provided by the JavaScript API - for more information please see MDN
It's possible to replace the method buildArrayFromObject by something like this:
const isNil = <T>(a: T | null | undefined): a is null | undefined => a === null || a === undefined;
const isAssigned = <T>(a: T | null | undefined): a is T => !isNil(a);
const entitiesToArray = <T>(entity: Entities<T>): T[] => Object.values(entity).filter(isAssigned);
This method uses the Object.values method to convert the object into an array. Afterwards it get's filtered to contain an array without undefined values. Therefore I made use of two helper methods isAssigned and isNil. Please see this CodeSandbox for an example: Code SandBox
Based on your concern, that the Object.value method is not supported by IE11, you can add a polyfill for that one. Either by pasting a polyfill in or by adding it using npm to your project.
Alternatively you can replace this code by the following answer Use Object.keys to mimick Object.values

How to write mobx action methods in separate files and import them to the actual mobx class having observable variables?

I have a mobx class which has an observable variable named dataObject. This observable variable is an object and its structure is:
{
name:string;
dataType:string;
value: string;
description:string;
.
.
.
.
.
..... total around 45 such properties
}
Each object property has an action method to update it. For example
#action updateName = (name:string) =>{
this.dataObject.name = name;
}
It is difficult to maintain all such 45 action methods in same class. Is there any way to write these methods in a separate ts file and import them to the current file?
EDIT: Adding a part of dataObject interface along with few of its action methods
interface IDataObject{
name:string;
dataType:string;
value:any;
valueType:VALUE_TYPE|any;
hasCustomValueType:boolean;
customValueType:string|undefined;
description:string;
isRegistered:boolean;
associatedVariableDetails:IVariableDetails[];
hasMultipleValues:boolean;
associatedService:SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
//////////////
method to update dataType
#action updateDataType = (dataType:string) =>{
this.dataObject.dataType = dataType;
if(dataType === 'string'){
this.dataObject.value = ''
}
else if(dataType === 'boolean'){
this.dataObject.value = false
}
else if(dataType === 'integer'){
this.dataObject.value = 0
}
}
methods to modify associatedVariableDetails
#action addVariableDetails = (variableDetails:IVariableDetails) =>{
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues:boolean) =>{
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
Create an interface for the object that you want as the observable and add an index signature.
This looks something like this:
interface foo {
[key: string]: type;
bar: string;
}
Then pass the key to your update action and update the value like object[key] = newValue.
Example:
#action update = (key: string, value: type) => {
object[key] = value;
}
This will allow you to use one single function to patch any value into your observable object.
This is a good article that explains index signatures in depth: https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html
To answer your original question though. You could create 45 functions and export all of them in an object and import them into this file, but that solution may be overkill and tedious to maintain if types or fields change. I can show an example of that if this solution does not work. The solution I have explained above is the more efficient way of handling object patches.
EDIT:
I apologize for the delay in editing my answer. I have made edits to your code above showing the approach I would be taking with this kind of object you have. Here is the edit:
interface IDataObject{
[key: string]: string | boolean | VALUE_TYPE | SERVICE | IVariableDetails[];
name: string;
dataType: string;
value: any; // THIS SHOULD BE STRICTLY DEFINED
valueType: VALUE_TYPE|any; // THIS SHOULD ALSO BE STRICTLY DEFINED
hasCustomValueType: boolean;
customValueType?: string;
description: string;
isRegistered:boolean;
associatedVariableDetails: IVariableDetails[];
hasMultipleValues: boolean;
associatedService: SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
#action updateField = (key: string, value: string | boolean | VALUE_TYPE | SERVICE) => {
this.dataObject[key] = value;
}
#action addVariableDetails = (variableDetails: IVariableDetails) => {
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues: boolean) => {
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
What I have done here is exposed a single method to manipulate the semi primitive values that do not need extra work after, they just want new values. Then I would create more complex #actions as needed based on the type of work you need to do on your object. As shown the updateField action does not allow you to use it with a VariableDetails type of parameter which will make it so typescript complains if you try to send a variableDetails parameter to that action.
I have also noted that the any attributes should definitely be some type if the rest of the object has strict typings. If you are going to have any types in your object you might as well make an interface like interface IAny { [key: string]: any; } and use that everywhere.
I hope this can be of help, and again I apologize for the gap in responses.

How to define type of an object to accept only keys from enum?

I have an enum:
enum MyEnum {
One,
Two,
Three
}
Then I have a method of React Component which should return an object, whose keys can only be string keys of MyEnum (meaning, so far: One, Two, Three):
myFunction = (someValue: {}[]): {} => {
return someValue.reduce((res, v, i) => {
res[v.name].push(v)
return res
}, {})
}
Question: How can I dynamically define the type of the return value of this function to be an object, whose keys can only be string keys of MyEnum?
Right now there are only 3 of them, but if I add another one to the enum, the type of the return value of myFunction should also be updated.
Update 1:
Tried assigning Record<keyof typeof MyEnum, any> as a return value:
myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
return someValue.reduce((res: any, v: SomeInterface) => {
res[v.name].push(v)
return res
}, {})
}
This does not throw any errors, but if I assign the return value of reduce to a variable and then add another property on it (whose key is not MyEnum key), there would be no error, which is not what I seek:
myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
let someVar = someValue.reduce((res: any, v: SomeInterface) => {
res[v.name].push(v)
return res
}, {})
someVar.keyName = 'value'
return someVar
}
So, it looks like there's a lot of stuff going on here. My observations and guesses:
Since v is SomeInterface, it seems that someValue must be an array of SomeInterface.
I am guessing that res in the body of reduce's callback function is the desired return value of myMethod, since you're setting values on it in the callback function. If we call the type of return value of myMethod by the name RetType, that means the keys of RetType must be keyof typeof MyEnum.
You are using v.name as those keys, and since v is SomeInterface, then I guess that SomeInterface's name property should also be keyof typeof MyEnum.
You are pushing v onto the values of res, so you expect the properties of res to be arrays of SomeInterface. Hence, RetType is something like Record<keyof typeof MyEnum, SomeInterface[]>, where Record<K,V> is a type from the standard library representing an object with keys in K and values in V.
But of course, since someValue might be an empty array, or otherwise not have as many elements as the keys of MyEnum, then RetType might be missing some properties. So it is more like Partial<Record<keyof typeof MyEnum, SomeInterface[]>>, where Partial<T> is a type from standard library which makes the properties in T optional.
Now, I'm not sure if you fully understand the way reduce works, but the callback needs to return something of the same type as the intended return value. And the second argument is "initial value" which must also be the same type as the intended return value. Thus I have to change your callback so that it returns res, and the initial value so that it is too of RetType.
I also note that since the initial value is an empty object, the first time you try to push something onto one of its array properties, you will find that it is undefined. So you need to check for that and set it to an empty array before you try to push onto it.
That leads me to something like this:
enum MyEnum {
One,
Two,
Three
}
interface SomeInterface {
name: keyof typeof MyEnum
};
type RetType = Partial<Record<keyof typeof MyEnum, SomeInterface[]>>
const myMethod = (someValue: SomeInterface[]): RetType => {
let someVar = someValue.reduce((res: RetType, v: SomeInterface) => {
const resVName = res[v.name] || []; // make sure it's set
resVName.push(v);
res[v.name]=resVName; // copy back in case it's a new array
return res;
}, {} as RetType)
//someVar.keyName = 'value' // errors now
return someVar
}
Note that I copy to and from a new value called resVName. This allows the compiler's control flow analysis to work and be sure that resVName is actually a SomeInterface[] and not a SomeInterface[] | undefined. It doesn't work on res[v.name] because of a bug/limitation in TypeScript whereby control flow analysis doesn't work with bracket property access notation. It's a bit annoying but ensuring type safety is probably worth it.
That code is all well typed and may possibly work at runtime (you'd need to check). But reduce doesn't seem like what you're trying to do. Instead, I'd say you're looking for the simpler forEach method, like this:
const myMethod = (someValue: SomeInterface[]): RetType => {
let someVar: RetType = {};
someValue.forEach((v: SomeInterface) => {
const someVarVName = someVar[v.name] || []; // make sure it's set
someVarVName.push(v);
someVar[v.name] = someVarVName; // copy back in case it's a new array
});
//someVar.keyName = 'value'
return someVar
}
That also works and doesn't mess around with passing around the accumulator. Either way is fine, probably.
Okay hope that makes some sense and gives you ideas. Good luck!

Resources