1

I have the following object structure:

{
    a: {
        b: "abc",
        c: [
            {
                d: "pqr",
                e: true
            },
            {
                f: 1,
                e: ["xyz", 1]
            }
        ]
    }
}

Now I want to convert these structure in specific format that are shown in expected output.

I'm trying to traverse above structure but the constructed path not correct Here is what I'm doing

const getFinalNode = function(data, path){
  if (typeof data === 'object'){
    for (let key in data) {
      // console.log(key);
      if (typeof data[key] === 'object'){
        path += key+'/'
        getFinalNode(data[key], path)
        // console.log(true);
      }else{
        console.log({
          value: data[key],
          key: key,
          path:path
        })
      }
    }
  }
}
getFinalNode(data, ""); 

My expected output should be

[
    {
        name: "b",
        value: "abc",
        path: "a/b"
    },
    {
        name: "d",
        value: "pqr",
        path: "a/c/0/d"
    },
    {
        name: "e",
        value: true,
        path: "a/c/0/e"
    },
    {
        name: "f",
        value: 1,
        path: "a/c/1/f"
    },
    {
        name: "e",
        value: 'xyz',
        path: "a/c/1/e"
    },
    {
        name: "e",
        value: 1,
        path: "a/c/1/e"
    }
]

So how can I traverse above structure and convert it in expected output or is there any solution that can I use in my existing code.

Now I getting following output

enter image description here

10
  • Take a look into this Commented Dec 6, 2017 at 15:31
  • 1
    I'm curious as to why the second object in your array would have an expected path of a/b/c/0/d, instead of a/c/0/d? Neat question by the way. :-) Commented Dec 6, 2017 at 15:33
  • Shouldn't the array e: ["xyz", 1] be separated into two objects (like the array c is)? Commented Dec 6, 2017 at 15:33
  • What exactly is your question? and what does constructed path not correct mean? Commented Dec 6, 2017 at 15:36
  • 2
    @Liam doesn't seem that way to me. The OP has provided his input, the expected output, and the code he's built to try to produce said output. The only thing that would be helpful here is to see what incorrect output you're currently getting from the code you've built. Commented Dec 6, 2017 at 15:44

5 Answers 5

0

You mostly just needed to pass an array through and give a little adjustment to the path you're building.

The inclusion of the last item in the final path is a little surprising, and took some extra tweaking, but I think it's what you're looking for in the result.

const getFinalNode = function(data, path, result){
  result = result || [];

  // This check isn't needed if the initial call will always be an object.
  if (typeof data === 'object'){ 
    for (let [key, val] of Object.entries(data)) {
      if (typeof val === 'object'){
        getFinalNode(val, path ? (path + "/" + key) : key, result);

      }else{
        const notNum = isNaN(key);
        result.push({
          name: notNum ? key : path.slice(path.lastIndexOf("/") + 1),
          value: val,
          path:path + (notNum ? "/" + key : "")
        });
      }
    }
  }
  return result;
}

const data = {
    a: {
        b: "abc",
        c: [
            {
                d: "pqr",
                e: true
            },
            {
                f: 1,
                e: ["xyz", 1]
            }
        ]
    }
};

console.log(getFinalNode(data, ""));

I used Object.entries() with destructuring assignment in a for-of just for the convenience of having the key and value assigned to variables. Your original for-in loop was fine too.

Sign up to request clarification or add additional context in comments.

Comments

0

You should pass both the path and the accumulated array acc to the recursive call of getFinalNode:

const getFinalNode = function(data, acc = [], path = "") {         // default values for acc and path, so we won't bother ourselves with that
    for(let key in data) {                                         // for each key in the object/array data
        if(data.hasOwnProperty(key)) {                             // if this is an own property
            if(data[key] && typeof(data[key]) === "object") {      // if the value of the current property is an object/array (not null: typeof null === "object")
                getFinalNode(data[key], acc, path + "/" + key);    // then traverse it, passing acc to add to what we already have
            } else {                                               // otherwise
                acc.push({                                         // make a leaf object and push it to the accumulator acc
                    path: path.slice(1) + "/" + key,               // slice to remove the first "/" from path
                    name: key,
                    value: data[key]
                });
            }
        }
    }
    return acc;
}


let data = {"a":{"b":"abc","c":[{"d":"pqr","e":true},{"f":1,"e":["xyz",1]}]}};

let result = getFinalNode(data);
console.log(result);

Comments

0

You could take a function for collecting the items and a function for a recursive call for iterating the object.

function traverse(object) {

    function iter(o, p) {
        Object.keys(o).forEach(function (k) {
            if (o[k] && typeof o[k] === 'object') {
                return iter(o[k], p.concat(k));
            }
            result.push({ name: k, value: o[k], path: p.join('/') });
        });
    }

    var result = [];
    iter(object, []);
    return result;
}

var object = { a: { b: "abc", c: [{ d: "pqr", e: true }, { f: 1, e: ["xyz", 1] }] } };

console.log(traverse(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

0

there are two things you are missing to get the expected output: - You are not adding the path when data["key"] is not an object - You are looking for get an array output but you are printing every element separatedly.

Looking at you desired output i see the path always includes b, even when b and c are siblings, and that doesn´t allow have them both in the same path.Taking this as a typing mistake, I modified a bit the script in order to get the correct output:

var data = {a: { b: "abc", c: [ { d: "pqr", e: true},{ f: 1, e: ["xyz", 1] }]}};

var output = [];

getFinalNode = function(data, path){
  if (typeof data === 'object'){
    for (let key in data) {
      // console.log(key);
        path.push(key)
        getFinalNode(data[key], path)

        // console.log(true);
        if (typeof data[key] != 'object'){ 
        output.push({
          value: data[key],
          key: key,
          path:path.join("/")
        });}      
    }
  }
  path.splice(-1,1) 
}

getFinalNode(data, []);

console.log(output);

Comments

0

For fun, dirty solution, kids don't do that at home !

function getFinalNode(node) {
  return (function rec(obj, path='', results=[]) {
    path = path && (path + '/')
    return Object.entries(obj)
      .reduce((arr, [name, value]) => void (typeof value === 'object'
          ? rec(value, path + name, arr)
          : arr.push({ name, value, path: path + name })) || arr,
        results)
  })(node)
}

console.log(getFinalNode({a:{b:"abc",c:[{d:"pqr",e:true},{f:1,e:["xyz",1]}]}}))

To explain a bit:

path = path && (path + '/')

If path is empty, the first part of the condition will be evaluated, so will get an empty string. Otherwise, the second part is evaluated, and we do the concatenation.

void (whatEverExpression) || arr

void will make whatEverExpression returning undefined, so arr will be returned instead.

(It's a trick, I find handful when reducing something. Even if ESLint forbid the use of void :D)


Edit: Inspired by rock star's answer, with entries, it's even better :D

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.