4
\$\begingroup\$

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

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

A few points first.

  1. 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;
    }
    
  2. Object.keys and Object.entries are a better fit for looping through an object if you are going to check hasOwnProperty. I prefer Object.entries when possible, if you have the browser support.

  3. Choose const or let, 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;
}
\$\endgroup\$
6
  • \$\begingroup\$ What would the generic parameters for In and Out be, and isn't classOfIn(obj[key]) supposed to be classOfIn(val) \$\endgroup\$
    – Pavlo
    Commented 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 the classOfIn parameter. \$\endgroup\$
    – Gerrit0
    Commented Feb 3, 2018 at 21:00
  • \$\begingroup\$ Most of the time In and Out would be the same type though right? \$\endgroup\$
    – Pavlo
    Commented 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
    Commented 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
    Commented Feb 3, 2018 at 21:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.