Problem
I am loading things from the localStorage
and this has to be saved as json, so it needs to have a simple Object
structure to be possible to JSON.parse()
.
However, som methods do not accept <any>
as parameter, because they want a concrete class
or interface
, but i want to send my object as parameter, so i have to convert it to a Map
in order to have the same structure, but seen as it has a type, it is now accepted as parameter.
My problem lies within the conversion from Object
to Map
Solution
public static convertObjectToMap<V>(obj: any, classOfV): Map<string, V> {
let objectMap = new Map<string, V>();
if (obj !== undefined && obj !== null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
const initObject = new classOfV(obj[key]);
objectMap.set(key, initObject);
}
}
}
return objectMap;
}
I take an obj
and the class
which all values are going to be in the same type.
Example of usage
//This is purely for the example
const fibonacciObject:any = {"0": 1, "1": 1, "2": 2, "3": 3, "4": 5};
const fibonacciMap:Map<string, Number> = convertObjectToMap<Number>(fibonacciObject, Number);
fibonacciMap.get("0"); //1
fibonacciMap.get("4"); //5
Question
Is there a better way to do this conversion, i know about new () => V
, but since i need it for each key, then it is not really feasible.
Also what Type would class
of V
be, i keep getting type errors when i try to give it a Type
1 Answer 1
A few points first.
Avoid
any
like the plague. You can nearly always figure out a better type. When dealing with a JSON serialized data, I like to have a function similar to this to get rid of any as soon as possible:function verify<T>(obj: any, fallback: T, isT: (obj: any) => obj is T): T { return isT(obj) ? obj : fallback; }
Object.keys
andObject.entries
are a better fit for looping through an object if you are going to checkhasOwnProperty
. I preferObject.entries
when possible, if you have the browser support.Choose
const
orlet
, don't mix them without good reason.const
can result in better type inference so I prefer to use it when possible.
Here is how I would implement this function.
function convertObjectToMap<In, Out>(
obj: { [K: string]: In } | undefined | null,
classOfIn: new (v: In) => Out
): Map<string, Out> {
const result = new Map<string, Out>();
for (const [key, val] of Object.entries(obj || {})) {
result.set(key, new classOfIn(val));
}
return result;
}
-
\$\begingroup\$ What would the generic parameters for
In
andOut
be, and isn'tclassOfIn(obj[key])
supposed to beclassOfIn(val)
\$\endgroup\$Pavlo– Pavlo2018年02月03日 20:56:05 +00:00Commented Feb 3, 2018 at 20:56 -
\$\begingroup\$ Fixed
classOfIn
, In your example case it would be<number, Number>
, but it can be left off since Typescript can infer the type from theclassOfIn
parameter. \$\endgroup\$Gerrit0– Gerrit02018年02月03日 21:00:11 +00:00Commented Feb 3, 2018 at 21:00 -
\$\begingroup\$ Most of the time In and Out would be the same type though right? \$\endgroup\$Pavlo– Pavlo2018年02月03日 21:11:05 +00:00Commented Feb 3, 2018 at 21:11
-
\$\begingroup\$ That greatly depends on how you use the function, if you are just using strings and numbers, yes. However this could work with more complex classes. \$\endgroup\$Gerrit0– Gerrit02018年02月03日 21:12:44 +00:00Commented Feb 3, 2018 at 21:12
-
\$\begingroup\$ My constructor for the classes for this looks like this
constructor({name, age}){ //Unpacked can be used right away }
I would probably not use it for Number, but it was the simplest example i could make up \$\endgroup\$Pavlo– Pavlo2018年02月03日 21:16:51 +00:00Commented Feb 3, 2018 at 21:16