Iterative depth-first traversal with remembering value's paths - javascript

I need help with specific implementation of iterative depth first traversal algorithm.
I have an object like this (it's just an example, object might have more properties and be deeper nested):
const root = {
a: 1,
b: {
c: {
d: {
e: 2,
f: 3,
}
},
g: [
{
h: 4,
i: 5,
},
{
j: 6,
k: 7,
}
]
}
}
What I need is a function that would traverse the whole object and return an array like this:
[
{"a": 1},
{"b.c.d.e": 2},
{"b.c.d.f": 3},
{"b.g.0.h": 4},
{"b.g.0.i": 5},
{"b.g.1.j": 6},
{"b.g.1.k": 7},
]
I managed to create an algorithm that sort of solves my problem, but needs one additional step in the end. Result of the algorithm is an array of strings like that:
[
'a^1',
'b.c.d.e^2',
'b.c.d.f^3',
'b.g.0.h^4',
'b.g.0.i^5',
'b.g.1.j^6',
'b.g.1.k^7'
]
so in order to achieve what I want I have to do one full iteration over the result of my algorithm, split strings by ^ symbol and then create objects based on that.
This is the part that I need help with - how can I improve/change my solution so I don't need to do that last step?
function dft(root) {
let stack = [];
let result = [];
const isObject = value => typeof value === "object";
stack.push(root);
while (stack.length > 0) {
let node = stack.pop();
if (isObject(node)) {
Object.entries(node).forEach(([childNodeKey, childNodeValue]) => {
if (isObject(childNodeValue)) {
const newObject = Object.fromEntries(
Object.entries(childNodeValue).map(([cnk, cnv]) => {
return [`${childNodeKey}.${cnk}`, cnv];
})
);
stack.push(newObject);
} else {
stack.push(`${childNodeKey}^${childNodeValue}`);
}
})
} else {
result.push(node);
}
}
return result.reverse();
}

I'd keep pairs <keys,value> in the stack and only create a string key when storing a newly created object:
function dft(obj) {
let stack = []
let res = []
stack.push([[], obj])
while (stack.length) {
let [keys, val] = stack.pop()
if (!val || typeof val !== 'object') {
res.push({
[keys.join('.')]: val
})
} else {
Object.entries(val).forEach(p => stack.push([
keys.concat(p[0]),
p[1],
]))
}
}
return res.reverse()
}
//
const root = {
a: 1,
b: {
c: {
d: {
e: 2,
f: 3,
}
},
g: [
{
h: 4,
i: 5,
},
{
j: 6,
k: 7,
}
]
}
}
console.log(dft(root))

You can push the childNodeKey childNodeValue pair directly as an object to your result array.
Change
stack.push(`${childNodeKey}^${childNodeValue}`);
to
const newEntry = {}
newEntry[childNodeKey] = childNodeValue
result.push(newEntry);
or with ES2015 syntax (you would need a transpiler for browser compatibility)
result.push({[childNodeKey]: childNodeValue});
Complete function:
const root = {
a: 1,
b: {
c: {
d: {
e: 2,
f: 3,
}
},
g: [
{
h: 4,
i: 5,
},
{
j: 6,
k: 7,
}
]
}
}
function dft(root) {
let stack = [];
let result = [];
const isObject = value => typeof value === "object";
stack.push(root);
while (stack.length > 0) {
let node = stack.pop();
if (isObject(node)) {
Object.entries(node).forEach(([childNodeKey, childNodeValue]) => {
if (isObject(childNodeValue)) {
const newObject = Object.fromEntries(
Object.entries(childNodeValue).map(([cnk, cnv]) => {
return [`${childNodeKey}.${cnk}`, cnv];
})
);
stack.unshift(newObject);
} else {
const newEntry = {}
newEntry[childNodeKey] = childNodeValue
result.push({[childNodeKey]: childNodeValue});
}
})
} else {
result.push(node);
}
}
return result;
}
console.log(dft(root))

As you mentioned, you almost got it complete. Just make the array entry an object just before pushing it into result. By splitting Array.prototype.split('^') you can get 'b.g.0.h^4' >>> ['b.g.0.h', '4']. So, rest is a cake:
if (isObject(node)) {
...
} else {
const keyAndValue = node.split('^')
// approach 1)
// const key = keyAndValue[0]
// const value = keyAndValue[1]
// dynamic key setting
// result.push({[key]: value});
// approach 2)
// or in short,
// dynamic key setting
result.push({[keyAndValue[0]]: keyAndValue[1]});
}

You could use a stack where each item has an iterator over the children, and the path up to that point:
function collect(root) {
const Node = (root, path) =>
({ iter: Object.entries(root)[Symbol.iterator](), path });
const result = [];
const stack = [Node(root, "")];
while (stack.length) {
const node = stack.pop();
const {value} = node.iter.next();
if (!value) continue;
stack.push(node);
const [key, child] = value;
const path = node.path ? node.path + "." + key : key;
if (Object(child) !== child) result.push({ [path]: child });
else stack.push(Node(child, path));
}
return result;
}
const root = {a:1,b:{c:{d:{e:2,f:3}},g:[{h:4,i:5},{j:6,k:7}]}};
console.log(collect(root));

I would suggest that the quickest fix to your code is simply to replace
return result.reverse();
with
return result.reverse()
.map ((s, _, __, [k, v] = s .split ('^')) => ({[k]: v}));
But I also think that we can write code to do this more simply. A function I use often will convert your input into something like this:
[
[["a"], 1],
[["b", "c", "d", "e"], 2],
[["b", "c", "d", "f"], 3],
[["b", "g", 0, "h"], 4],
[["b", "g", 0, "i"], 5],
[["b", "g", 1, "j"], 6],
[["b", "g", 1, "k"], 7]
]
and a fairly trivial wrapper can then convert this to your output. It could look like this:
const pathEntries = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (([p, v]) => [[Array.isArray(obj) ? Number(k) : k, ... p], v])
)
: [[[], obj]]
const transform = (o) =>
pathEntries (o)
.map (([k, v]) => ({[k .join ('.')] : v}))
const root = {a: 1, b: {c: {d: {e: 2, f: 3, }}, g: [{h: 4, i: 5, }, {j: 6, k: 7}]}}
console .log (transform (root))
.as-console-wrapper {max-height: 100% !important; top: 0}
I don't know your usecase, but I would find this output generally more helpful:
{
"a": 1,
"b.c.d.e": 2,
"b.c.d.f": 3,
"b.g.0.h": 4,
"b.g.0.i": 5,
"b.g.1.j": 6,
"b.g.1.k": 7
}
(that is, one object with a number of properties, rather than an array of single-property objects.)
And we could do this nearly as easily, with a small change to transform:
const transform = (o) =>
pathEntries (o)
.reduce ((a, [k, v]) => ((a[k .join ('.')] = v), a), {})

Related

Flatten nested object/array in javascript

I'm new to Javascript and I have nested objects and arrays that I would like to flatten.
I have...
[{ a: 2, b: [{ c: 3, d: [{e: 4, f: 5}, {e: 5,f: 6}]},
{ c: 4, d: [{e: 7, f: 8}]}
]
}]
and would like...
[{a:2,c:3,e:4,f:5}, {a:2,c:3,e:5,f:6}, {a:2,c:4,e:7,f:8}]
I've tried to adapt the following function written for an object for my array but i only get the final object within the array [{a:2,c:4,e:7,f:8}] https://stackoverflow.com/a/33158929/14313188. I think my issue is knowing how to iterate through arrays and objects?
original script:
function flatten(obj) {
var flattenedObj = {};
Object.keys(obj).forEach(function(key){
if (typeof obj[key] === 'object') {
$.extend(flattenedObj, flatten(obj[key]));
} else {
flattenedObj[key] = obj[key];
}
});
return flattenedObj;
}
my scripts (same result for both):
flat_array=[];
function superflat(array){
for (var i = 0; i < array.length; i++) {
var obj = array[i]
var flattenedObj = {};
Object.keys(obj).forEach(function(key){
if (typeof obj[key] === 'object') {
$.extend(flattenedObj, flatten(obj[key]));
} else {
flattenedObj[key] = obj[key];
}
});
flat_array.push(flattenedObj);
}
};
mega_flat_array=[];
function megaflatten(obj) {
Object.keys(obj).forEach(function(key){
var flattenedObj = {};
if (typeof obj[key] === 'object') {
$.extend(flattenedObj, flatten(obj[key]));
} else {
flattenedObj[key] = obj[key];
}
mega_flat_array.push(flattenedObj);
});
}
Thanks for your help
I would suggest starting with simpler data objects to test your function with, then progressively add more complex objects until your function performs as expected. forEach
Here you can see how I started with a simple test1 object, then test2 and so on so that the logic gets broken down into smaller increments.
To remove the duplicates we previously had, I had to throw and catch an error to break out of the recursive forEach loops, which added the unnecessary duplicate "rows" - perhaps it is better to use a normal for loop out of which you can simply break; and then use error handling for real errors.
The basic idea of the recursive function is to check the type of object (either array or object) and then loop through them to add values, but another check is needed on those to see if they are not Arrays and not Objects, if they are, call the function again. When a duplicate key is found i.e { c: 3}, remove the current key and add the new one before continuing the loop.
You can add some more tests if you have some more sample data, but there are better libraries to help you with TDD (test-driven development).
const isArray = (arr) => {
return Array.isArray(arr);
};
const isObject = (obj) => {
return typeof obj === "object" && obj !== null;
};
const flatten = (tree, row, result) => {
try {
if (isArray(tree)) {
tree.forEach((branch, index) => {
flatten(branch, row, result);
});
} else if (isObject(tree)) {
Object.keys(tree).forEach((key) => {
//we don't want to add objects or arrays to the row -
if (!isArray(tree[key]) && !isObject(tree[key])) {
if (key in row) {
// new row detected, get existing keys to work with
let keysArray = Object.keys(row);
// we are going to loop backwards and delete duplicate keys
let end = Object.keys(row).length;
let stopAt = Object.keys(row).indexOf(key);
//delete object keys from back of object to the newly found one
for (let z = end; z > stopAt; z--) {
delete row[keysArray[z - 1]];
}
row[key] = tree[key];
} else {
row[key] = tree[key];
}
} else {
flatten(tree[key], row, result);
throw "skip";
}
});
//all other rows in results will be overridden if we don't stringify
result.push(JSON.stringify(row));
}
} catch (e) {
//console.log(e)
} finally {
return result.map((row) => JSON.parse(row));
}
};
///tests
const test1 = [
{
a: 2,
b: 3,
},
];
const expected1 = [{ a: 2, b: 3 }];
const test2 = [
{
a: 2,
b: [
{
c: 3,
},
],
},
];
const expected2 = [{ a: 2, c: 3 }];
const test3 = [
{
a: 2,
b: [
{
c: 3,
},
{ c: 4 },
{ c: 5 },
],
},
];
const expected3 = [
{ a: 2, c: 3 },
{ a: 2, c: 4 },
{ a: 2, c: 5 },
];
let test4 = [
{
a: 2,
b: [
{
c: 3,
d: [
{ e: 4, f: 5 },
{ e: 5, f: 6 },
],
},
{ c: 4, d: [{ e: 7, f: 8 }] },
],
},
];
const expected4 = [
{ a: 2, c: 3, e: 4, f: 5 },
{ a: 2, c: 3, e: 5, f: 6 },
{ a: 2, c: 4, e: 7, f: 8 },
];
const test = (name, res, expected) => {
console.log(
`${name} passed ${JSON.stringify(res) === JSON.stringify(expected)}`
);
//console.log(res, expected);
};
//test("test1", flatten(test1, {}, []), expected1);
//test("test2", flatten(test2, {}, []), expected2);
//test("test3", flatten(test3, {}, []), expected3);
test("test4", flatten(test4, {}, []), expected4);
It's a bit of a behemoth, and it doesn't preserve the keys' order, but it does work with no duplicates.
It is recursive, so watch out for the call stack.
First, loop through the items in the array,
If an item is an array, make a recursive call.
On returning from that call, if the number of objects returned is more than there currently are in the final result, then update the returned objects with the properties from the objects in the final result, being careful to avoid overwriting pre-existing properties.
Otherwise update the final results with the properties in the returned result, again being careful not to overwrite existing properties.
If the item is not an array
If this is the first item put it into the final result
Otherwise add the item's properties to all the items in the final result, without overwriting any.
function makeFlat(arr) //assume you're always passing in an array
{
let objects = [];
arr.forEach(item =>
{
let currentObject = {};
const keys = Object.keys(item);
keys.forEach(key =>
{
const obj = item[key];
if(Array.isArray(obj))
{
let parts = makeFlat(obj);
if(objects.length > 0)
{
if(parts.length > objects.length)
{
parts.forEach(part =>
{
objects.forEach(ob =>
{
Object.keys(ob).forEach(k =>
{
if(Object.keys(part).indexOf(k) == -1)
{
part[k] = ob[k];
}
});
});
});
objects = parts;
}
else
{
objects.forEach(ob =>
{
parts.forEach(part =>
{
Object.keys(part).forEach(k =>
{
if(Object.keys(ob).indexOf(k) == -1)
{
ob[k] = part[k];
}
});
});
});
}
}
else
{
objects = parts;
}
}
else
{
if(Object.keys(currentObject).length == 0)
{
objects.push(currentObject);
}
currentObject[key] = item[key];
objects.forEach(ob =>
{
if(Object.keys(ob).indexOf(key) == -1)
{
ob[key] = currentObject[key]
}
});
}
});
});
return objects;
}
const inp = [{ a: 2, b: [{ c: 3, d: [{e: 4, f: 5}, {e: 5,f: 6}]},
{ c: 4, d: [{e: 7, f: 8}]}
], g:9
}];
let flattened = makeFlat(inp);
flattened.forEach(item => console.log(JSON.stringify(item)));

How to obtain a master structure for a json file?

I have a JSON file as follows:
[
{
"dog": "lmn",
"tiger": [
{
"bengoltiger": {
"height": {
"x": 4
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"width": {
"a": 8
}
},
"indiantiger": {
"b": 3
}
}
]
},
{
"dog": "pqr",
"tiger": [
{
"bengoltiger": {
"width": {
"m": 3
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"height": {
"n": 8
}
},
"indiantiger": {
"b": 3
}
}
],
"lion": 90
}
]
I want to transform this to obtain all possible properties of any object at any nesting level. For arrays, the first object should contain all the properties. The values are trivial, but the below solution considers the first encountered value for any property. (For ex. "lmn" is preserved for the "dog" property)
Expected output:
[
{
"dog": "lmn",
"tiger": [
{
"bengoltiger": {
"height": {
"x": 4,
"n": 8
},
"width": {
"a": 8,
"m": 3
}
},
"indiantiger": {
"paw": "a",
"foor": "b",
"b": 3
}
}
],
"lion": 90
}
]
Here's a recursive function I tried before this nesting problem struck me
function consolidateArray(json) {
if (Array.isArray(json)) {
const reference = json[0];
json.forEach(function(element) {
for (var key in element) {
if (!reference.hasOwnProperty(key)) {
reference[key] = element[key];
}
}
});
json.splice(1);
this.consolidateArray(json[0]);
} else if (typeof json === 'object') {
for (var key in json) {
if (json.hasOwnProperty(key)) {
this.consolidateArray(json[key]);
}
}
}
};
var json = [
{
"dog": "lmn",
"tiger": [
{
"bengoltiger": {
"height": {
"x": 4
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"width": {
"a": 8
}
},
"indiantiger": {
"b": 3
}
}
]
},
{
"dog": "pqr",
"tiger": [
{
"bengoltiger": {
"width": {
"m": 3
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"height": {
"n": 8
}
},
"indiantiger": {
"b": 3
}
}
],
"lion": 90
}
];
consolidateArray(json);
alert(JSON.stringify(json, null, 2));
General logic using this new JNode IIFE with comments - ask someone more clever if you do not understand something as me ;-)
And level starts from 1 as there is no root object #start.
var json;
function DamnDemo() {
json = DemoJSON();
var it = new JNode(json), it2 = it;
var levelKeys = []; /* A bit crazy structure:
[
levelN:{
keyA:[JNode, JNode,...],
keyB:[JNode, JNode,...],
...
},
levelM:...
]
*/
do {
var el = levelKeys[it.level]; // array of level say LevelN or undefined
el = levelKeys[it.level] = el || {}; // set 2 empty it if does not exist
el = el[it.key] = el[it.key] || []; // key array in say levelN
el.push(it); // save current node indexing by level, key -> array
} while (it = it.DepthFirst()) // traverse all nodes
for(var l1 in levelKeys) { // let start simply by iterating levels
l2(levelKeys[l1]);
}
console.log(JSON.stringify(json, null, 2));
}
function l2(arr) { // fun starts here...
var len = 0, items = []; // size of arr, his items to simple Array
for(var ln in arr) { // It's a kind of magic here ;-) Hate recursion, but who want to rewrite it ;-)
if (arr[ln] instanceof JNode) return 1; // End of chain - our JNode for traverse of length 1
len += l2(arr[ln]);
items.push(arr[ln]);
}
if (len == 2) { // we care only about 2 items to move (getting even 3-5)
//console.log(JSON.stringify(json));
if (!isNaN(items[0][0].key) || (items[0][0].key == items[1][0].key)) { // key is number -> ignore || string -> must be same
console.log("Keys 2B moved:", items[0][0].key, items[1][0].key, "/ level:", items[0][0].level);
var src = items[1][0]; // 2nd similar JNode
moveMissing(items[0][0].obj, src.obj); // move to 1st
//console.log(JSON.stringify(json));
if (src.level == 1) { // top level cleaning
delete src.obj;
delete json[src.key]; // remove array element
if (!json[json.length-1]) json.length--; // fix length - hope it was last one (there are other options, but do not want to overcomplicate logic)
} else {
var parent = src.parent;
var end = 0;
for(var i in parent.obj) {
end++;
if (parent.obj[i] == src.obj) { // we found removed in parent's array
delete src.obj; // delete this empty object
delete parent.obj[i]; // and link on
end = 1; // stupid marker
}
}
if (end == 1 && parent.obj instanceof Array) parent.obj.length--; // fix length - now only when we are on last element
}
} else console.log("Keys left:", items[0][0].key, items[1][0].key, "/ level:", items[0][0].level); // keys did not match - do not screw it up, but report it
}
return len;
}
function moveMissing(dest, src) {
for(var i in src) {
if (src[i] instanceof Object) {
if (!dest[i]) { // uff object, but not in dest
dest[i] = src[i];
} else { // copy object over object - let it bubble down...
moveMissing(dest[i], src[i]);
}
delete src[i];
} else { // we have value here, check if it does not exist, move and delete source
if (!dest[i]) {
dest[i] = src[i];
delete src[i];
}
}
}
}
// JSON_Node_Iterator_IIFE.js
'use strict';
var JNode = (function (jsNode) {
function JNode(json, parent, pred, key, obj, fill) {
var node, pred = null;
if (parent === undefined) {
parent = null;
} else if (fill) {
this.parent = parent;
this.pred = pred;
this.node = null;
this.next = null;
this.key = key;
this.obj = obj;
return this;
}
var current;
var parse = (json instanceof Array);
for (var child in json) {
if (parse) child = parseInt(child);
var sub = json[child];
node = new JNode(null, parent, pred, child, sub, true);
if (pred) {
pred.next = node;
node.pred = pred;
}
if (!current) current = node;
pred = node;
}
return current;
}
JNode.prototype = {
get hasNode() {
if (this.node) return this.node;
return (this.obj instanceof Object);
},
get hasOwnKey() { return this.key && (typeof this.key != "number"); },
get level() {
var level = 1, i = this;
while(i = i.parent) level++;
return level;
},
Down: function() {
if (!this.node && this.obj instanceof Object) {
this.node = new JNode(this.obj, this);
}
return this.node;
},
Stringify: function() { // Raw test stringify - #s are taken same as strings
var res;
if (typeof this.key == "number") {
res = '[';
var i = this;
do {
if (i.node) res += i.node.Stringify();
else res += "undefined";
i = i.next;
if (i) res += ','
} while(i);
res += ']';
} else {
res = '{' + '"' + this.key + '":';
res += (this.node?this.node.Stringify():this.hasNode?"undefined":'"'+this.obj+'"');
var i = this;
while (i = i.next) {
res += ',' + '"' + i.key + '":';
if (i.node) res += i.node.Stringify();
else {
if (i.obj instanceof Object) res += "undefined";
else res += '"' + i.obj + '"';
}
};
res += '}';
}
return res;
},
DepthFirst: function () {
if (this == null) return 0; // exit sign
if (this.node != null || this.obj instanceof Object) {
return this.Down(); // moved down
} else if (this.next != null) {
return this.next;// moved right
} else {
var i = this;
while (i != null) {
if (i.next != null) {
return i.next; // returned up & moved next
}
i = i.parent;
}
}
return 0; // exit sign
}
}
return JNode;
})();
// Fire test
DamnDemo();
function DemoJSON() {
return [
{
"dog": "lmn",
"tiger": [
{
"bengoltiger": {
"height": {
"x": 4
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"width": {
"a": 8
}
},
"indiantiger": {
"b": 3
}
}
]
},
{
"dog": "pqr",
"tiger": [
{
"bengoltiger": {
"width": {
"m": 3
}
},
"indiantiger": {
"paw": "a",
"foor": "b"
}
},
{
"bengoltiger": {
"height": {
"n": 8
}
},
"indiantiger": {
"b": 3
}
}
],
"lion": 90
}
]
;}
This was an interesting problem. Here's what I came up with:
// Utility functions
const isInt = Number.isInteger
const path = (ps = [], obj = {}) =>
ps .reduce ((o, p) => (o || {}) [p], obj)
const assoc = (prop, val, obj) =>
isInt (prop) && Array .isArray (obj)
? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
: {...obj, [prop]: val}
const assocPath = ([p = undefined, ...ps], val, obj) =>
p == undefined
? obj
: ps.length == 0
? assoc(p, val, obj)
: assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)
// Helper functions
function * getPaths(o, p = []) {
if (Object(o) !== o || Object .keys (o) .length == 0) yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, isInt (Number (k)) ? Number (k) : k])
}
const canonicalPath = (path) =>
path.map (n => isInt (Number (n)) ? 0 : n)
const splitPaths = (xs) =>
Object .values ( xs.reduce (
(a, p, _, __, cp = canonicalPath (p), key = cp .join ('\u0000')) =>
({...a, [key]: a [key] || {canonical: cp, path: p} })
, {}
))
// Main function
const canonicalRep = (data) => splitPaths ([...getPaths (data)])
.reduce (
(a, {path:p, canonical}) => assocPath(canonical, path(p, data), a),
Array.isArray(data) ? [] : {}
)
// Test
const data = [{"dog": "lmn", "tiger": [{"bengoltiger": {"height": {"x": 4}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"width": {"a": 8}}, "indiantiger": {"b": 3}}]}, {"dog": "pqr", "lion": 90, "tiger": [{"bengoltiger": {"width": {"m": 3}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"height": {"n": 8}}, "indiantiger": {"b": 3}}]}]
console .log (
canonicalRep (data)
)
The first few functions are plain utility functions that I would keep in a system library. They have plenty of uses outside this code:
isInt is simply a first-class function alias to Number.isInteger
path finds the nested property of an object along a given pathway
path(['b', 1, 'c'], {a: 10, b: [{c: 20, d: 30}, {c: 40}], e: 50}) //=> 40
assoc returns a new object cloning your original, but with the value of a certain property set to or replaced with the supplied one.
assoc('c', 42, {a: 1, b: 2, c: 3, d: 4}) //=> {a: 1, b: 2, c: 42, d: 4}
Note that internal objects are shared by reference where possible.
assocPath does this same thing, but with a deeper path, building nodes as needed.
assocPath(['a', 'b', 1, 'c', 'd'], 42, {a: {b: [{x: 1}, {x: 2}], e: 3})
//=> {a: {b: [{x: 1}, {c: {d: 42}, x: 2}], e: 3}}
Except for isInt, these borrow their APIs from Ramda. (Disclaimer: I'm a Ramda author.) But these are unique implementations.
The next function, getPaths, is an adaptation of one from another SO answer. It lists all the paths in your object in the format used by path and assocPath, returning an array of values which are integers if the relevant nested object is an array and strings otherwise. Unlike the function from which is was borrowed, it only returns paths to leaf values.
For your original object, it returns an iterator for this data:
[
[0, "dog"],
[0, "tiger", 0, "bengoltiger", "height", "x"],
[0, "tiger", 0, "indiantiger", "foor"],
[0, "tiger", 0, "indiantiger", "paw"],
[0, "tiger", 1, "bengoltiger", "width", "a"],
[0, "tiger", 1, "indiantiger", "b"],
[1, "dog"],
[1, "lion"],
[1, "tiger", 0, "bengoltiger", "width", "m"],
[1, "tiger", 0, "indiantiger", "foor"],
[1, "tiger", 0, "indiantiger", "paw"],
[1, "tiger", 1, "bengoltiger", "height", "n"],
[1, "tiger", 1, "indiantiger", "b"]
]
If I wanted to spend more time on this, I would replace that version of getPaths with a non-generator version, just to keep this code consistent. It shouldn't be hard, but I'm not interested in spending more time on it.
We can't use those results directly to build your output, since they refer to array elements beyond the first one. That's where splitPaths and its helper canonicalPath come in. We create the canonical paths by replacing all integers with 0, giving us a data structure like this:
[{
canonical: [0, "dog"],
path: [0, "dog"]
}, {
canonical: [0, "tiger", 0, "bengoltiger", "height", "x"],
path: [0, "tiger", 0, "bengoltiger", "height", "x"]
}, {
canonical: [0, "tiger", 0, "indiantiger", "foor"],
path: [0, "tiger", 0, "indiantiger", "foor"]
}, {
canonical: [0, "tiger", 0, "indiantiger", "paw"],
path: [0, "tiger", 0, "indiantiger", "paw"]
}, {
canonical: [0, "tiger", 0, "bengoltiger", "width", "a"],
path: [0, "tiger", 1, "bengoltiger", "width", "a"]
}, {
canonical: [0, "tiger", 0, "indiantiger", "b"],
path: [0, "tiger", 1, "indiantiger", "b"]
}, {
canonical: [0, "lion"],
path: [1, "lion"]
}, {
canonical: [0, "tiger", 0, "bengoltiger", "width", "m"],
path: [1, "tiger", 0, "bengoltiger", "width", "m"]
}, {
canonical: [0, "tiger", 0, "bengoltiger", "height", "n"],
path: [1, "tiger", 1, "bengoltiger", "height", "n"]
}]
Note that this function also removes duplicate canonical paths. We originally had both [0, "tiger", 0, "indiantiger", "foor"] and [1, "tiger", 0, "indiantiger", "foor"], but the output only contains the first one.
It does this by storing them in an object under a key created by joining the path together with the non-printable character \u0000. This was the easiest way to accomplish this task, but there is an extremely unlikely failure mode possible 1 so if we really wanted we could do a more sophisticated duplicate checking. I wouldn't bother.
Finally, the main function, canonicalRep builds a representation out of your object by calling splitPaths and folding over the result, using canonical to say where to put the new data, and applying the path function to your path property and the original object.
Our final output, as requested, looks like this:
[
{
dog: "lmn",
lion: 90,
tiger: [
{
bengoltiger: {
height: {
n: 8,
x: 4
},
width: {
a: 8,
m: 3
}
},
indiantiger: {
b: 3,
foor: "b",
paw: "a"
}
}
]
}
]
What's fascinating for me is that I saw this as an interesting programming challenge, although I couldn't really imagine any practical uses for it. But now that I've coded it, I realize it will solve a problem in my current project that I'd put aside a few weeks ago. I will probably implement this on Monday!
Update
Some comments discuss a problem with a subsequent empty value tries to override a prior filled value, causing a loss in data.
This version attempts to alleviate this with the following main function:
const canonicalRep = (data) => splitPaths ([...getPaths (data)])
.reduce (
(a, {path: p, canonical}, _, __, val = path(p, data)) =>
isEmpty(val) && !isEmpty(path(canonical, a))
? a
: assocPath(canonical, val, a),
Array.isArray(data) ? [] : {}
)
using a simple isEmpty helper function:
const isEmpty = (x) =>
x == null || (typeof x == 'object' && Object.keys(x).length == 0)
You might want to update or expand this helper in various ways.
My first pass worked fine with the alternate data supplied, but not when I switched the two entries in the outer array. I fixed that, and also made sure that an empty value is kept if it's not overridden with actual data (that's the z property in my test object.)
I believe this snippet solves the original problem and the new one:
// Utility functions
const isInt = Number.isInteger
const path = (ps = [], obj = {}) =>
ps .reduce ((o, p) => (o || {}) [p], obj)
const assoc = (prop, val, obj) =>
isInt (prop) && Array .isArray (obj)
? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
: {...obj, [prop]: val}
const assocPath = ([p = undefined, ...ps], val, obj) =>
p == undefined
? obj
: ps.length == 0
? assoc(p, val, obj)
: assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)
const isEmpty = (x) =>
x == null || (typeof x == 'object' && Object.keys(x).length == 0)
function * getPaths(o, p = []) {
if (Object(o) !== o || Object .keys (o) .length == 0) yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, isInt (Number (k)) ? Number (k) : k])
}
// Helper functions
const canonicalPath = (path) =>
path.map (n => isInt (Number (n)) ? 0 : n)
const splitPaths = (xs) =>
Object .values ( xs.reduce (
(a, p, _, __, cp = canonicalPath (p), key = cp .join ('\u0000')) =>
({...a, [key]: a [key] || {canonical: cp, path: p} })
, {}
))
// Main function
const canonicalRep = (data) => splitPaths ([...getPaths (data)])
.reduce (
(a, {path: p, canonical}, _, __, val = path(p, data)) =>
isEmpty(val) && !isEmpty(path(canonical, a))
? a
: assocPath(canonical, val, a),
Array.isArray(data) ? [] : {}
)
// Test data
const data1 = [{"dog": "lmn", "tiger": [{"bengoltiger": {"height": {"x": 4}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"width": {"a": 8}}, "indiantiger": {"b": 3}}]}, {"dog": "pqr", "lion": 90, "tiger": [{"bengoltiger": {"width": {"m": 3}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"height": {"n": 8}}, "indiantiger": {"b": 3}}]}]
const data2 = [{"d": "Foreign Trade: Export/Import: Header Data", "a": "false", "f": [{"g": "TRANSPORT_MODE", "i": "2"}, {"k": "System.String", "h": "6"}], "l": "true"}, {"a": "false", "f": [], "l": "false", "z": []}]
const data3 = [data2[1], data2[0]]
// Demo
console .log (canonicalRep (data1))
console .log (canonicalRep (data2))
console .log (canonicalRep (data3))
.as-console-wrapper {max-height: 100% !important; top: 0}
Why not change assoc?
This update grew out of discussion after I rejected an edit attempt to do the same sort of empty-checking inside assoc. I rejected that as too far removed from the original attempt. When I learned what it was supposed to do, I knew that what had to be changed was canonicalRep or one of its immediate helper functions.
The rationale is simple. assoc is a general-purpose utility function designed to do a shallow clone of an object, changing the named property to the new value. This should not have complex logic regarding whether the value is empty. It should remain simple.
By introducing the isEmpty helper function, we can do all this with only a minor tweak to canonicalRep.
1That failure mode could happen if you had certain nodes containing that separator, \u0000. For instance, if you had paths [...nodes, "abc\u0000", "def", ...nodes] and [...nodes, "abc", "\u0000def", ...nodes], they would both map to "...abc\u0000\u0000def...". If this is a real concern, we could certainly use other forms of deduplication.

flattening the nested object in javascript

I ran into this problem, I was able to write solution which can handle array of object (not posted here) or one level deep nested object but i couldn't solve when the given object has nested structure like below. I am curious to know how we can solve this.
const source = {
a: 1,
b: {
c: true,
d: {
e: 'foo'
}
},
f: false,
g: ['red', 'green', 'blue'],
h: [{
i: 2,
j: 3
}]
};
solution should be
const solution = {
'a': 1,
'b.c': true,
'b.d.e': 'foo',
'f': false,
'g.0': 'red',
'g.1': 'green',
'g.2': 'blue',
'h.0.i': 2,
'h.0.j': 3
};
attempt for one deep nested object
let returnObj = {}
let nestetdObject = (source, parentKey) => {
let keys = keyFunction(source);
keys.forEach((key) => {
let values = source[key];
if( typeof values === 'object' && values !== null ) {
parentKey = keys[0]+'.'+keyFunction(values)[0]
nestetdObject(values, parentKey);
}else{
let key = parentKey.length > 0 ? parentKey : keys[0];
returnObj[key] = values;
}
})
return returnObj
};
// Key Extractor
let keyFunction = (obj) =>{
return Object.keys(obj);
}
calling the function
nestetdObject(source, '')
But my attempt will fail if the object is like { foo: { boo : { doo : 1 } } }.
You should be able to do it fairly simply with recursion. The way it works, is you just recursively call a parser on object children that prepend the correct key along the way down. For example (not tested very hard though):
const source = {
a: 1,
b: {
c: true,
d: {
e: 'foo'
}
},
f: false,
g: ['red', 'green', 'blue'],
h: [{
i: 2,
j: 3
}]
}
const flatten = (obj, prefix = '', res = {}) =>
Object.entries(obj).reduce((r, [key, val]) => {
const k = `${prefix}${key}`
if(typeof val === 'object'){
flatten(val, `${k}.`, r)
} else {
res[k] = val
}
return r
}, res)
console.log(flatten(source))
I am very late to the party but it can be easily achieved with a module like Flatify-obj.
Usage:
const flattenObject = require('flatify-obj');
flattenObject({foo: {bar: {unicorn: '🦄'}}})
//=> { 'foo.bar.unicorn': '🦄' }
flattenObject({foo: {unicorn: '🦄'}, bar: 'unicorn'}, {onlyLeaves: true});
//=> {unicorn: '🦄', bar: 'unicorn'}
// Licensed under CC0
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
const source = {
a: 1,
b: { c: true, d: { e: "foo" } },
f: false,
g: ["red", "green", "blue"],
h: [{ i: 2, j: 3 }],
};
function flatten(source, parentKey, result = {}) {
if (source?.constructor == Object || source?.constructor == Array) {
for (const [key, value] of Object.entries(source)) {
flatten(
value,
parentKey != undefined ? parentKey + "." + key : key,
result
);
}
} else {
result[parentKey] = source;
}
return result;
}
console.log(flatten(source));
one more simple example with Object.keys
const apple = { foo: { boo : { doo : 1 } } }
let newObj = {}
const format = (obj,str) => {
Object.keys(obj).forEach((item)=>{
if(typeof obj[item] ==='object'){
const s = !!str? str+'.'+item:item
format(obj[item],s)
} else {
const m = !!str?`${str}.`:''
newObj[m+item]= obj[item]
}
})
}
format(apple,'')
console.log(newObj)

Unfold a plain-object with array of plain-objects into a flat plain-object

update: I described the problem in a wrong way and have rewritten the description completely, along with the code that works but is ugly as hell as well as limited.
Let's pretend there's an object
const input = {
a: 1,
b: '2',
c: {
d: true,
e: '4'
},
f: [{
g: 5,
h: {
i: '6'
}
}, {
g: 7,
h: {
i: '8'
}
}]
}
what I'm looking for is a collection of all possible arrangements of nested arrays, with object's keys flattened and joined with ".", like
[{
a: 1,
b: '2',
'c.d': true,
'c.e': '4',
'f.g': 5,
'f.h.i': '6'
}, {
a: 1,
b: '2',
'c.d': true,
'c.e': '4',
'f.g': 7,
'f.h.i': '8'
}]
Note that there are no keys that would have non-primitive values, for example, 'f.h' that would point at an object.
So, what I do first, is collect all the keys, and artificially add # sign to every key that points at an array item, so # kind of means "every index in that array":
function columns(data, prefix = '') {
if (_.isArray(data)) {
return columns(_.first(data), `${prefix}.#`);
} else if (_.isObject(data)) {
return _.filter(_.flatMap(_.keys(data), key => {
return _.concat(
!_.isObject(_.result(data, key)) ? `${prefix}.${key}` : null,
columns(data[key], `${prefix}.${key}`)
);
}));
} else {
return null;
}
}
console.log(columns(input)); // -> [".a", ".b", ".c.d", ".c.e", ".f.#.g", ".f.#.h.i"]
Now, I wield lodash. The leading "." in keys isn't a problem for lodash, so I just leave it as is. With lodash, I squash the object into a one-level object with weird keys:
function flattenKeys(original, keys) {
return _.mapValues(_.groupBy(_.map(keys, key => ({
key,
value: _.result(original, key)
})), 'key'), e => _.result(e, '0.value'));
}
console.log(flattenKeys(input, columns(input))) // -> {".a":1,".b":"2",".c.d":true,".c.e":"4"}
And now I run (in a very wrong way) through every array-like property of original object and produce an array of objects, setting keys like .f.#.h.i with the values of .f.0.h.i for first element, etc.:
function unfold(original, keys, iterables) {
if (!_.isArray(iterables)) {
return unfold(original, keys, _.uniq(_.map(_.filter(keys, key => /#/i.test(key)), key => _.replace(key, /\.\#.*/, ''))));
} else if (_.isEmpty(iterables)) {
return [];
} else {
const first = _.first(iterables);
const rest = _.tail(iterables);
const values = _.result(original, first);
const flatKeys = _.mapKeys(_.filter(keys, key => _.includes(key, first)));
const updated = _.map(values, (v, i) => ({
...flattenKeys(original, keys),
..._.mapValues(flatKeys, k => _.result(original, _.replace(k, /\#/, i)))
}));
return _.concat(updated, unfold(original, keys, rest));
}
}
console.log(unfold(input, columns(input))) // -> [{".a":1,".b":"2",".c.d":true,".c.e":"4",".f.#.g":5,".f.#.h.i":"6"},{".a":1,".b":"2",".c.d":true,".c.e":"4",".f.#.g":7,".f.#.h.i":"8"}]
So in the end, I only need to clean keys, which, in fact, isn't necessary in my case.
The question is, aside of ugliness of the code, how can I make it work with possible multiple array-like properties in original objects?
Now, I understand, that this question is more suitable for CodeReview StackExchange, so if somebody transfers it there, I'm okay with that.
Based on your updated structure, the following recursive function does the trick:
function unfold(input) {
function flatten(obj) {
var result = {},
f,
key,
keyf;
for(key in obj) {
if(obj[key] instanceof Array) {
obj[key].forEach(function(k) {
f = flatten(k);
for(keyf in f) {
result[key+'.'+keyf] = f[keyf];
}
output.push(JSON.parse(JSON.stringify(result))); //poor man's clone object
});
} else if(obj[key] instanceof Object) {
f = flatten(obj[key]);
for(keyf in f) {
result[key+'.'+keyf] = f[keyf];
}
} else {
result[key] = obj[key];
}
}
return result;
} //flatten
var output = [];
flatten(input);
return output;
} //unfold
Snippet:
function unfold(input) {
function flatten(obj) {
var result = {},
f,
key,
keyf;
for(key in obj) {
if(obj[key] instanceof Array) {
obj[key].forEach(function(k) {
f = flatten(k);
for(keyf in f) {
result[key+'.'+keyf] = f[keyf];
}
output.push(JSON.parse(JSON.stringify(result))); //poor man's clone object
});
} else if(obj[key] instanceof Object) {
f = flatten(obj[key]);
for(keyf in f) {
result[key+'.'+keyf] = f[keyf];
}
} else {
result[key] = obj[key];
}
}
return result;
} //flatten
var output = [];
flatten(input);
return output;
} //unfold
const input = {
a: 1,
b: '2',
c: {
d: true,
e: '4'
},
f: [{
g: 5,
h: {
i: '6'
}
}, {
g: 7,
h: {
i: '8'
}
}]
};
document.body.innerHTML+= '<pre>' + JSON.stringify(unfold(input), null, 2) + '</pre>';
I'll leave my original answer, which worked with your original structure:
var o = {a: [{b: 1, c: 2}], d: [{e: 4, f: 5}]},
keys = Object.keys(o),
result = [];
keys.forEach(function(i, idx1) {
keys.forEach(function(j, idx2) {
if(idx2 > idx1) { //avoid duplicates
for(var k in o[i][0]) {
for(var l in o[j][0]) {
result.push({
[i + '.' + k]: o[i][0][k],
[j + '.' + l]: o[j][0][l]
});
}
}
}
});
});
console.log(JSON.stringify(result));

Swap key with value in object

I have an extremely large JSON object structured like this:
{A : 1, B : 2, C : 3, D : 4}
I need a function that can swap the values with keys in my object and I don't know how to do it. I would need an output like this:
{1 : A, 2 : B, 3 : C, 4 : D}
Is there any way that I can do this would manually created a new object where everything is swapped?
Thanks
function swap(json){
var ret = {};
for(var key in json){
ret[json[key]] = key;
}
return ret;
}
Example here FIDDLE don't forget to turn on your console to see the results.
ES6 versions:
static objectFlip(obj) {
const ret = {};
Object.keys(obj).forEach(key => {
ret[obj[key]] = key;
});
return ret;
}
Or using Array.reduce() & Object.keys()
static objectFlip(obj) {
return Object.keys(obj).reduce((ret, key) => {
ret[obj[key]] = key;
return ret;
}, {});
}
Or using Array.reduce() & Object.entries()
static objectFlip(obj) {
return Object.entries(obj).reduce((ret, entry) => {
const [ key, value ] = entry;
ret[ value ] = key;
return ret;
}, {});
}
Now that we have Object.fromEntries:
const f = obj => Object.fromEntries(Object.entries(obj).map(a => a.reverse()))
console.log(
f({A : 'a', B : 'b', C : 'c'})
) // => {a : 'A', b : 'B', c : 'C'}
or:
const f = obj => Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k]))
console.log(
f({A : 'a', B : 'b', C : 'c'})
) // => {a : 'A', b : 'B', c : 'C'}
(Updated to remove superfluous parentheses - thanks #devin-g-rhode)
you can use lodash function _.invert it also can use multivlaue
var object = { 'a': 1, 'b': 2, 'c': 1 };
_.invert(object);
// => { '1': 'c', '2': 'b' }
// with `multiValue`
_.invert(object, true);
// => { '1': ['a', 'c'], '2': ['b'] }
Using ES6:
const obj = { a: "aaa", b: "bbb", c: "ccc", d: "ddd" };
Object.assign({}, ...Object.entries(obj).map(([a,b]) => ({ [b]: a })))
Get the keys of the object, and then use the Array's reduce function to go through each key and set the value as the key, and the key as the value.
const data = {
A: 1,
B: 2,
C: 3,
D: 4
}
const newData = Object.keys(data).reduce(function(obj, key) {
obj[data[key]] = key;
return obj;
}, {});
console.log(newData);
In ES6/ES2015 you can combine use of Object.keys and reduce with the new Object.assign function, an arrow function, and a computed property name for a pretty straightforward single statement solution.
const foo = { a: 1, b: 2, c: 3 };
const bar = Object.keys(foo)
.reduce((obj, key) => Object.assign({}, obj, { [foo[key]]: key }), {});
If you're transpiling using the object spread operator (stage 3 as of writing this) that will simplify things a bit further.
const foo = { a: 1, b: 2, c: 3 };
const bar = Object.keys(foo)
.reduce((obj, key) => ({ ...obj, [foo[key]]: key }), {});
Finally, if you have Object.entries available (stage 4 as of writing), you can clean up the logic a touch more (IMO).
const foo = { a: 1, b: 2, c: 3 };
const bar = Object.entries(foo)
.reduce((obj, [key, value]) => ({ ...obj, [value]: key }), {});
2021's answer
The concise way by using ES6 syntax like this.
const obj = {A : 1, B : 2, C : 3, D : 4}
console.log(
Object.entries(obj).reduce((acc, [key, value]) => (acc[value] = key, acc), {})
);
Explain:
(acc[value] = key, acc)
Using Comma operator (,) syntax.
The comma operator (,) evaluates each of its operands (from left to
right) and returns the value of the last operand.
As a complement of #joslarson and #jPO answers:
Without ES6 needed, you can use Object.keys Array.reduce and the Comma Operator:
Object.keys(foo).reduce((obj, key) => (obj[foo[key]] = key, obj), {});
Some may find it ugly, but it's "kinda" quicker as the reduce doesn't spread all the properties of the obj on each loop.
Using Ramda:
const swapKeysWithValues =
R.pipe(
R.keys,
R.reduce((obj, k) => R.assoc(source[k], k, obj), {})
);
const result = swapKeysWithValues(source);
Try
let swap = (o,r={})=> Object.keys(o).map(k=> r[o[k]]=k) && r;
let obj = {A : 1, B : 2, C : 3, D : 4};
let swap = (o,r={})=> Object.keys(o).map(k=> r[o[k]]=k) && r;
console.log(swap(obj));
With pure Ramda in a pure and point-free style:
const swapKeysAndValues = R.pipe(
R.toPairs,
R.map(R.reverse),
R.fromPairs,
);
Or, with a little more convoluted ES6 version, still pure functional:
const swapKeysAndValues2 = obj => Object
.entries(obj)
.reduce((newObj, [key, value]) => ({...newObj, [value]: key}), {})
Shortest one I came up with using ES6..
const original = {
first: 1,
second: 2,
third: 3,
fourth: 4,
};
const modified = Object
.entries(original)
.reduce((all, [key, value]) => ({ ...all, [value]: key }), {});
console.log('modified result:', modified);
var data = {A : 1, B : 2, C : 3, D : 4}
var newData = {};
Object.keys(data).forEach(function(key){newData[data[key]]=key});
console.log(newData);
Here is a pure functional implementation of flipping keys and values in ES6:
TypeScript
const flipKeyValues = (originalObj: {[key: string]: string}): {[key: string]: string} => {
if(typeof originalObj === "object" && originalObj !== null ) {
return Object
.entries(originalObj)
.reduce((
acc: {[key: string]: string},
[key, value]: [string, string],
) => {
acc[value] = key
return acc;
}, {})
} else {
return {};
}
}
JavaScript
const flipKeyValues = (originalObj) => {
if(typeof originalObj === "object" && originalObj !== null ) {
return Object
.entries(originalObj)
.reduce((acc, [key, value]) => {
acc[value] = key
return acc;
}, {})
} else {
return {};
}
}
const obj = {foo: 'bar'}
console.log("ORIGINAL: ", obj)
console.log("FLIPPED: ", flipKeyValues(obj))
function swapKV(obj) {
const entrySet = Object.entries(obj);
const reversed = entrySet.map(([k, v])=>[v, k]);
const result = Object.fromEntries(reversed);
return result;
}
This can make your object, {A : 1, B : 2, C : 3, D : 4}, array-like, so you can have
const o = {A : 1, B : 2, C : 3, D : 4}
const arrayLike = swapKV(o);
arrayLike.length = 5;
const array = Array.from(arrayLike);
array.shift(); // undefined
array; // ["A", "B", "C", "D"]
Here is an option that will swap keys with values but not lose duplicates, if your object is : { a: 1, b: 2, c: 2}, it will always return an array in the output :
function swapMap(map) {
const invertedMap = {};
for (const key in map) {
const value = map[key];
invertedMap[value] = invertedMap[value] || [];
invertedMap[value].push(key);
}
return invertedMap;
}
swapMap({a: "1", b: "2", c: "2"})
// Returns => {"1": ["a"], "2":["b", "c"]}
A simple TypeScript variant:
const reverseMap = (map: { [key: string]: string }) => {
return Object.keys(map).reduce((prev, key) => {
const value = map[key];
return { ...prev, [value]: [...(prev.value || []), key] };
}, {} as { [key: string]: [string] })
}
Usage:
const map = { "a":"1", "b":"2", "c":"2" };
const reversedMap = reverseMap(map);
console.log(reversedMap);
Prints:
{ "1":["a"], "2":["b", "c"] }
Rewriting answer of #Vaidd4, but using Object.assign (instead of comma operator):
/**
* Swap object keys and values
* #param {Object<*>} obj
* #returns {Object<string>}
*/
function swapObject(obj) {
return Object.keys(obj).reduce((r, key) => (Object.assign(r, {
[obj[key]]: key,
})), {});
}
Or, shorter:
Object.keys(obj).reduce((r, key) => (Object.assign(r, {[obj[key]]: key})), {});
function myFunction(obj) {
return Object.keys(obj).reduce((acc, cur) => {
return { ...acc, [obj[cur]]: cur };
}, {});
}
This is the solution that I'm using:
function objSwap(obj, tl = false) {
return Object.entries(obj).reduce((a, [k, v]) => (a[v = tl ? v.toLowerCase() : v] = k = tl ? k.toLowerCase() : k, a), {});
}
As a bonus: if you need to swap then check some values I added the possibility to lowercase keys and values. Simply you've to set tl = true, else if you don't need it ...
function objSwap(obj) {
return Object.entries(obj).reduce((a, [k, v]) => (a[v] = k, a), {});
}
Using a for...of loop:
let obj = {A : 1, B : 2, C : 3, D : 4}
for (let [key, value] of Object.entries(obj)){
obj[value] = key
delete obj[key]
}
console.log(obj) // {1: 'A', 2: 'B', 3: 'C', 4: 'D'}
ONE OF THE ES6 WAYS IS HERE
const invertObject = (object) =>Object.entries(object).reduce((result, value) => ({...result, [value[1]]: value[0] }), {});
let obj = invertObject({A : 1, B : 2, C : 3, D : 4});
Here's a type-safe way using TypeScript that has not been suggested before. This solution takes two generics that means the return type will be typed as expected. It's faster than doing methods with .reduce or Object.entries.
// Easier way to type `symbol | number | string` (the only valid keys of an object)
export type AnyKey = keyof any;
export function mirror<K extends AnyKey, V extends AnyKey>(
object: Record<K, V>,
) {
const ret: Partial<Record<V, K>> = {};
for (const key in object) {
ret[object[key]] = key;
}
return ret as Record<V, K>;
}
Usage:
const obj = mirror({
a: 'b',
c: 'd',
});
// {b: 'a', d: 'c'}
obj;
Modern JS solution:
const swapKeyValue = (object) =>
Object.entries(object).reduce((swapped, [key, value]) => (
{ ...swapped, [value]: key }
), {});
Typescript:
type ValidKey = number | string;
const swapKeyValue = <K extends ValidKey, V extends ValidKey>(
object: Record<K, V>
): Record<V, K> =>
Object.entries(object)
.reduce((swapped, [key, value]) => (
{ ...swapped, [value as ValidKey]: key }
), {} as Record<V, K>);
I believe it's better to do this task by using an npm module, like invert-kv.
invert-kv: Invert the key/value of an object. Example: {foo: 'bar'} → {bar: 'foo'}
https://www.npmjs.com/package/invert-kv
const invertKv = require('invert-kv');
invertKv({foo: 'bar', unicorn: 'rainbow'});
//=> {bar: 'foo', rainbow: 'unicorn'}

Categories

Resources