admin管理员组

文章数量:1189789

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
}

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
}
Share Improve this question asked Sep 17, 2016 at 13:09 SayemSayem 6,0994 gold badges23 silver badges27 bronze badges 5
  • You can see this answer: stackoverflow.com/questions/19098797/… Hope it can help you. – Nguyen Tran Commented Sep 17, 2016 at 13:21
  • Why do you want this? – Victor Commented Sep 17, 2016 at 14:03
  • 1 @Victor I'm making api calls like api.moviestore.com/movies?where[movie.name:eq]=gravity. I need to flatten an object to construct the filter query. – Sayem Commented Sep 17, 2016 at 14:52
  • @Sayem oh of course. I thought you just wanted to be able to get data by path or something. – Victor Commented Sep 17, 2016 at 14:54
  • This question is too broad, because there are many possible solutions. Try to write the code yourself, and if you encounter any specific problem, ask about it. – Michał Perłakowski Commented Sep 17, 2016 at 17:19
Add a comment  | 

10 Answers 10

Reset to default 16

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);

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'))

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.

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));

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);

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));

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!!

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))

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

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/

本文标签: javascriptFull path of a json objectStack Overflow