Sayem
Sayem

Reputation: 6099

Full path of a json object

I'm trying to flatten an object where the keys will be the full path to the leaf node. I can recursively identify which are the leaf nodes but stuck trying to construct the whole path.

Sample Input:

{
  one: 1,
  two: {
    three: 3
  },
  four: {
    five: 5,
    six: {
      seven: 7
    },
    eight: 8
  },
  nine: 9
}

Output:

{
  one: 1,
  'two.three': 3,
  'four.five': 5,
  'four.six.seven': 7,
  'four.eight': 8,
  nine: 9
}

Upvotes: 9

Views: 6845

Answers (10)

jack
jack

Reputation: 101

Try this

let x;
try{
  x = JSON.parse(prompt("Input your JSON"))
}
catch(e) {
   alert("not a valid json input")
}
var res = {};
var constructResultCurry = function(src){ return constructResult(res,src); }
        
function constructResult(target, src) {
  if(!src) return;
  target[src.key] = src.val;
}
        
function buildPath(key, obj, overAllKey) {
  overAllKey += (overAllKey ? "." : "") + key;
  if(typeof obj[key] != "object") return { key : overAllKey, val : obj[key] };
  Object.keys(obj[key]).forEach(function(keyInner) {
     constructResultCurry(buildPath(keyInner, obj[key], overAllKey));  
  });
}
        
Object.keys(x).forEach(function(k){
  constructResultCurry(buildPath(k, x, ""));
});
console.log("**************ALL FIELDS****************")
console.log(res);
console.log("******************************************")

let conf = confirm("do you need a specific field from JSON");
if ( conf )
{
   let field = prompt("Input field name")
   let results = Object.fromEntries(
  Object.entries(res).filter(([key]) => (key.toLowerCase()).includes((field.toLowerCase()))))
  prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(results));
   console.log(results)
   
} 
else {
   prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(res));
}

https://jsfiddle.net/amars404/2n9fprz8/57/

Upvotes: 0

vincent
vincent

Reputation: 2181

Here is an interative solution using object-scan.

object-scan is a data processing tool, so the main advantage here is that it would be easy to do further processing or processing while extracting the desired information

// const objectScan = require('object-scan');

const myData = { one: 1, two: { three: 3 }, four: { five: 5, six: { seven: 7 }, eight: 8 }, nine: 9 };

const flatten = (data) => {
  const entries = objectScan(['**'], {
    reverse: false,
    rtn: 'entry',
    joined: true,
    filterFn: ({ isLeaf }) => isLeaf
  })(data);
  return Object.fromEntries(entries);
};

console.log(flatten(myData));
// => { one: 1, 'two.three': 3, 'four.five': 5, 'four.six.seven': 7, 'four.eight': 8, nine: 9 }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Upvotes: 0

David
David

Reputation: 958

You can achieve it by using this function:

const obj = {
  one: 1,
  two: {
    three: 3
  },
  four: {
    five: 5,
    six: {
      seven: 7
    },
    eight: 8
  },
  nine: 9
}


const flatObject = (obj, keyPrefix = null) =>
  Object.entries(obj).reduce((acc, [key, val]) => {
    const nextKey = keyPrefix ? `${keyPrefix}.${key}` : key

    if (typeof val !== "object") {
      return {
        ...acc,
        [nextKey]: val
      };
    } else {
      return {
        ...acc,
        ...flatObject(val, nextKey)
      };
    }

  }, {});
  
  console.log(flatObject(obj))

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386868

You could use a recursive approch and collect the keys of the object. This proposal looks for arrays as well.

function getFlatObject(object) {
    function iter(o, p) {
        if (o && typeof o === 'object') {
            Object.keys(o).forEach(function (k) {
                iter(o[k], p.concat(k));
            });
            return;
        }
        path[p.join('.')] = o;
    }

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

var obj = { one: 1, two: { three: 3 }, four: { five: 5, six: { seven: 7 }, eight: 8 }, nine: 9 },
    path = getFlatObject(obj);
	
console.log(path);

Upvotes: 16

Mostafa Barmshory
Mostafa Barmshory

Reputation: 2039

I find a tiny JavaScript utility to access properties using path. It is called object-path and is an opensource project on GitHub.

To get attribute from an object:

objectPath.get(obj, "a.b");

to set attribute:

objectPath.set(obj, "a.b", value);

to remove an attribute:

objectPath.del(obj, "a.b");

So easy!!

Upvotes: 0

Redu
Redu

Reputation: 26201

You might simply do as follows;

var obj = {one: 1, two: {three: 3}, four: {five: 5, six: {seven: 7}, eight: 8}, nine: 9},
flatObj = (o,p="") => { return Object.keys(o)
                                     .map(k => o[k] === null           ||
                                               typeof o[k] !== "object" ? {[p + (p ? ".":"") + k]:o[k]}
                                                                        : flatObj(o[k],p + (p ? ".":"") + k))
                                     .reduce((p,c) => Object.assign(p,c));
                      };
console.log(flatObj(obj));

Upvotes: 0

Rajaprabhu Aravindasamy
Rajaprabhu Aravindasamy

Reputation: 67217

A non fancy approach, internally uses recursion.

var x = { one:1,two:{three:3},four:{five: 5,six:{seven:7},eight:8},nine:9};
var res = {};
var constructResultCurry = function(src){ return constructResult(res,src); }
        
function constructResult(target, src) {
  if(!src) return;
  target[src.key] = src.val;
}
        
function buildPath(key, obj, overAllKey) {
  overAllKey += (overAllKey ? "." : "") + key;
  if(typeof obj[key] != "object") return { key : overAllKey, val : obj[key] };
  Object.keys(obj[key]).forEach(function(keyInner) {
     constructResultCurry(buildPath(keyInner, obj[key], overAllKey));  
  });
}
        
Object.keys(x).forEach(function(k){
  constructResultCurry(buildPath(k, x, ""));
});

console.log(res);

Upvotes: 0

knutesten
knutesten

Reputation: 594

var obj = {
  one: 1,
  two: {
    three: 3
  },
  four: {
    five: 5,
    six: {
      seven: 7
    },
    eight: 8
  },
  nine: 9
};

function flatten(obj) {
  var flatObj = {}

  function makeFlat(obj, path) {
    var keys = Object.keys(obj);
    if (keys.length) {
      keys.forEach(function (key) {
        makeFlat(obj[key], (path ? path + "." : path) + key);
      })
    } else {
      flatObj[path] = obj;
    }
  }
  makeFlat(obj, "");
  return flatObj;
}

console.log(flatten(obj));

Upvotes: 0

mdziekon
mdziekon

Reputation: 3627

Using newest JS features like Object spread and Object.entries it should be pretty easy:

function flatObj(obj, path = []) {
    let output = {};

    Object.entries(obj).forEach(([ key, value ]) => {
        const nextPath = [ ...path, key ];

        if (typeof value !== 'object') {
            output[nextPath.join('.')] = value;

            return;
        }

        output = {
            ...output,

            ...flatObj(value, nextPath)
        };
    });
}

Please note that this code is probably not the most optimal one as it copies the object each time we want to merge it. Treat it more as a gist of what would it look like, rather than a complete and final solution.

Upvotes: 0

Bharath kumar Jalla
Bharath kumar Jalla

Reputation: 26

Partial solution : Give the input as a full path to the function and it gives you the respective output

var obj = {
  one: 1,
  two: {
    three: 3
  },
  four: {
    five: 5,
    six: {
      seven: 7
    },
    eight: 8
  },
  nine: 9
};

function deepFind(obj, path) {
  var paths = path.split('.')
    , current = obj
    , i;

  for (i = 0; i < paths.length; ++i) {
    if (current[paths[i]] == undefined) {
      return undefined;
    } else {
      current = current[paths[i]];
    }
  }
  return current;
}

console.log(deepFind(obj, 'four.six.seven'))

Upvotes: 0

Related Questions