1

I have a function which converts a set of Javascript objects into a map. However, I get an object which cannot be navigated for its values; Here is the sample json

{
   "products":{
      "lemonade":{
         "product-type":"beverage",
         "product-details":{
            "calories":"129",
            "product-categories":[
               222,
               444
            ]
         }
      },
      "limeade":{
         "product-type":"beverage",
         "product-details":{
            "calories":"220",
            "product-categories":[
               222,
               444
            ],
            "salesIndex":{
               "percentage":1101,
               "demographics":"region-1"
            }
         }
      }
   }
}

Here is my function to convert to a map:

function objectToMap(o) {
  const m = new Map();
  console.log(`Processing for fragment ${fragment}`);
  for (const k of Object.keys(o)) {
    if (o[k] instanceof Object) {
      console.log(`Key is ${k} and object is ${JSON.stringify(o[k])}`);
      m.set(k, objectToMap(o[k]));
    } else {
      console.log(`Not an object ::Key is ${k} and object is
           ${JSON.stringify(o[k])}`);
      m.set(k, o[k]);
    }
  }
  return m;
}

Here is how I try to use and print the map:

const m1 = objectToMap(obj.products);
printMap(m1);

function printMap(map) {
  for (const k of map.keys()) {
    console.log(`Current key is ${k} and value is
${map.get(k)}`);
    if (map.get(k) instanceof Map) {
      printMap(map.get(k));
    } else {
      console.log(`No it is not a map :: Key is ${k} and value is ${map.get(k)}`);
    }
  }
}

However, I get for some this key, salesIndex, [object Map], why are the key, values not printed?

Nickofthyme
  • 3,032
  • 23
  • 40
BreenDeen
  • 623
  • 1
  • 13
  • 42

2 Answers2

0

The simple answer to your question is that you get the [object Map] string whenever you try to interpolate a Map type into a String type. So say

const myMap = new Map([['key', 'value']])

// calling toString implicitly
console.log('implicitly ->', `${myMap}`)

// calling toString explicitly
console.log('explicitly ->', myMap.toString())

Similarly you get [object Object] for objects

const myObj = { key: 'value' };

// calling toString implicitly
console.log('implicitly ->', `${myObj}`)

// calling toString explicitly
console.log('explicitly ->', myObj.toString())

Array types toString method is a join so essentially myArray.join(',') === myArray.toString().

const myArr = [1, 2, 3];

// calling toString implicitly
console.log('implicitly ->', `${myArr}`)

// calling toString explicitly
console.log('explicitly ->', myArr.toString())

// calling toString explicitly
console.log('join ->', myArr.join(','))

So in the case of your example, you are implicitly calling to string in your for loop via m.get(k) in your printMap function before you know if the value is a Map or not. See this line...

console.log(`Current key is ${k} and value is
${map.get(k)}`);

An easier way to check this is you just use the browser console. This way you can just console.log the Map and view it like the screenshot below, see following script.

enter image description here

const obj = {
  products: {
    lemonade: {
      'product-type': 'beverage',
      'product-details': {
        calories: '129',
        'product-categories': [222, 444],
      },
    },
    limeade: {
      'product-type': 'beverage',
      'product-details': {
        calories: '220',
        'product-categories': [222, 444],
        salesIndex: {
          percentage: 1101,
          demographics: 'region-1',
        },
      },
    },
  },
};

function objectToMap(o) {
  const m = new Map();
  for (const k of Object.keys(o)) {
    const value = o[k];
    // if (value instanceof Object) { // this will include arrays and nulls
    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      m.set(k, objectToMap(o[k]));
    } else {
      if (k === 'salesIndex') debugger
      m.set(k, o[k]);
    }
  }
  return m;
}

const m1 = objectToMap(obj.products);

console.log(m1) // view this from browswer console

One thing to note is that using obj instanceof Object is not ideal as it catches more than you want, namely Array and null values. So using typeof value === 'object' && value !== null && !Array.isArray(value) checks if it's an object then ensures it is neither null nor an Array. I would look at the difference between typeof and instanceof, see https://stackoverflow.com/a/2449264/6943587. instanceof is more for checking for certain Classes than generic Objects.

Nickofthyme
  • 3,032
  • 23
  • 40
  • The JSON object cannot simply check for the salesIndex key, as the JSOn can be nested with many different objects. I chose a simple snippet. From your answer is there no way to get the object without toString()? – BreenDeen May 26 '22 at 20:45
  • No you should not use `toString` what you have already works, as far as I understand. Your issue is in the `printMap` function that stringifies the output. Are you looking for a `printMap` to work correctly or just for `objectToMap` to work correctly? – Nickofthyme May 27 '22 at 00:26
  • Its the printMap which always prints [object Map ] when i want to be able to navigate the map entries but it does not work, only shows me [object Map. ]. – BreenDeen May 27 '22 at 02:58
  • Yes! Have you read my answer is full? I explain exactly why your `printMap` function does this, you are calling `console.log` before you check if the value of the map is a map. Checking FIRST this will solve your issue! – Nickofthyme May 31 '22 at 14:08
0

why are the key, values not printed?

They are printed, but the primitive values only get printed (later) when the object is passed to the recursive call, and there the else block is executed, i.e. when you arrive at the base case of the recursion.

Your code is fine, but you should avoid printing the value when you're not yet at that base case, as that value still needs to be passed to the recursive call, which will take care of printing the deeper key/values.

I would suggest:

  • To only print the key (not the value) when the value is still an object
  • Print with indentation so it is much clearer what the structure is of the data
  • In objectToMap, support null values: for that you need to change the is-object test.

That's the most important, but I'd also:

  • In objectToMap, use Object.entries instead of Object.keys so you get both the key and the value as your loop variables
  • Similarly, in printMap, use map.entries instead of map.values so you get both the key and the value as your loop variables
  • In objectToMap, as you call map.set() in both if and else cases, start with the call, and differentiate the argument with a conditional (ternary) operator

So like this:

// For the purpose of this answer, console.log removed from this function:
function objectToMap(o) {
  const m = new Map();
  for (const [k, v] of Object.entries(o)) { // Get both key and value
    // Use conditional operator and better way to check for objects:
    m.set(k, Object(v) === v ? objectToMap(v) : v);
  }
  return m;
}

// Extra argument for printing with indentation
function printMap(map, tab="") { 
  for (const [k, v] of map.entries()) {
    // Don't print here yet...
    if (v instanceof Map) {
      console.log(`${tab}${k}:`); // Only print the key here...
      printMap(v, tab + "    "); // ...as recursion will take care of the value(s)
    } else {
      console.log(`${tab}${k} = ${v}`);
    }
  }
}

// demo
const obj = {"products":{"lemonade":{"product-type":"beverage","product-details":{"calories":"129","product-categories":[222,444]}},"limeade":{"product-type":"beverage","product-details":{"calories":"220","product-categories":[222,444],"salesIndex":{"percentage":1101,"demographics":"region-1"}}}}};

const m = objectToMap(obj.products);
printMap(m);
trincot
  • 317,000
  • 35
  • 244
  • 286