'Print object key/values in a csv format

I'm trying to print a object in comma seperated strings format. but I'm unable to understand how to concat keys for objects. Here is an example.

let obj = {
  "name": "Family",
  "count": 6,
  "Children": {
    "name": "Jim",
    "age": "24",
    "address": {
      "street": "5th ave",
      "state": "NY",
      "country":"US"
    }
  },
  "parents": {
    "name": "John",
    "age": "45",
    "address": {
      "street": "New brew st",
      "state": "CA",
      "country":null
    }
  }
};

let data = '';
let tempKey = '';

function printValues(obj) {
  for (var key in obj) {
    if (typeof obj[key] === "object") {
       printValues(obj[key]);
    } else {
      data += key + ' - ' + obj[key] + ',';
    }
    tempKey = '';
  }
  return data;
} 
console.log(printValues(obj));

Here my target is to get the output below format.

"name - Family,count - 6,Children_name - Jim,Children_age - 24,Children_address_street - 5th ave,Children_address_state - NY,Children_address_country - US,parents_name - John,parents_age - 45,parents_address_street - New brew st,parents_address_state - CA,parents_address_country - null"

my current output is

"name - Family,count - 6,name - Jim,age - 24,street - 5th ave,state - NY,country - US,name - John,age - 45,street - New brew st,state - CA,"

I'm a bit confused about how to do this.



Solution 1:[1]

You should keep track of the parent property name on your recursive calls so that you'll append that prefix if any was passed.

Here I added that argument to your function and I use it to compose the key string used when you "serialize" your object.

I used template literals to make string composition:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

let obj = {
  "name": "Family",
  "count": 6,
  "Children": {
    "name": "Jim",
    "age": "24",
    "address": {
      "street": "5th ave",
      "state": "NY",
      "country":"US"
    }
  },
  "parents": {
    "name": "John",
    "age": "45",
    "address": {
      "street": "New brew st",
      "state": "CA",
      "country":null
    }
  }
};

let data = '';
let tempKey = '';

function printValues(obj, parent = '') {
  for (var key in obj) {
    if (obj[key] != null && typeof obj[key] === "object") {
       printValues(obj[key], `${parent}${key}_`);
    } else {
      data += `${parent}${key}` + ' - ' + obj[key] + ',';
    }
    tempKey = '';
  }
  return data;
} 
console.log(printValues(obj));

Solution 2:[2]

Recursive solution (based on your solution)

You could use an additional path parameter. The rest of your code is already working fine. Whenever you encounter an object add the new key to the path array.

let obj = {
  name: "Family",
  count: 6,
  Children: {
    name: "Jim",
    age: "24",
    address: {
      street: "5th ave",
      state: "NY",
      country: "US",
    },
  },
  parents: {
    name: "John",
    age: "45",
    address: {
      street: "New brew st",
      state: "CA",
      country: null,
    },
  },
};

let data = '';
let tempKey = '';

function printValues(obj, path = []) {
  for (var key in obj) {
    if (typeof obj[key] === "object") {
       printValues(obj[key], path=[...path, key]);
    } else {
      
      data += path.join("_") + (path.length !== 0 ? "_": "") + key + ' - ' + obj[key] + ',';
    }
    tempKey = '';
  }
  return data;
} 
console.log(printValues(obj));

Another hint:

In order to check if something is an Object typeof x === "object" is not enough, as that would also be true for an Array or null. Use the following instead to make sure you are actually dealing with an object.

/**
 * Checks whether an item is a JavaScript object
 * @param {any} item item to be checked
 * @returns true, if it is an object, false otherwise
 */
function isObject(item) {
  return typeof item === "object" && !Array.isArray(item) && item !== null;
}

Iterative solution

I've gone ahead and also implemented a iterative solution for your problem. The result will be the same but instead of using recursion for backtracking one has to do the backtracking oneself. I do that here using a Stack and a Queue of props to investigate.

let obj = {
  name: "Family",
  count: 6,
  Children: {
    name: "Jim",
    age: "24",
    address: {
      street: "5th ave",
      state: "NY",
      country: "US",
    },
  },
  parents: {
    name: "John",
    age: "45",
    address: {
      street: "New brew st",
      state: "CA",
      country: null,
    },
  },
};

/**
 * Checks whether an item is a JavaScript object
 * @param {any} item item to be checked
 * @returns true, if it is an object, false otherwise
 */
function isObject(item) {
  return typeof item === "object" && !Array.isArray(item) && item !== null;
}

function printValuesIter(obj) {
  let str = "";
  if (!isObject(obj)) return str;
  // have a stack which determine which is the next object to investigate
  // for each stack object keep:
  // - a reference to the object
  // - a list ob properties that have yet to be investigated
  // - a path
  const stack = [{ cur: obj, propQueue: Object.keys(obj), path: [] }];
  // as long as we have not investigated every single object 
  while (stack.length !== 0) {
    // get next object from the stack
    const { cur, propQueue, path } = stack.pop();
    // as long as this object still has props we have not investigated
    while (propQueue.length !== 0) {
      // get the next prop from the queue
      const curProp = propQueue.shift();
      // check whether this prop is an object
      if (isObject(cur[curProp])) {
        // we have a new object
        if (propQueue.length !== 0){
          // the object we're currently investigating still has props that we have not yet looked at
          // => push it onto the stack again 
          stack.push({ cur: cur, propQueue: propQueue, path: path });
        }
        // add new object to stack as this is the next object we need to investigate
        stack.push({ cur: cur[curProp], propQueue: Object.keys(cur[curProp]), path: [...path, curProp]});
        // to get the new object from the stack we need to break here 
        break;
      // no object we can output this prop  
      } else str += `${path.length ? `${path.join("_")}_`: ""}${curProp} - ${cur[curProp]}, `;
    }
  }
  return str;
}

console.log(printValuesIter(obj));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2