Replace a property in arbitrary data structure - javascript

Let's say I have an array of arbitrary objects I don't know structure of. I would like to process it in a way that when a property matching some criteria in terms of property name is found at any nesting level, there is some action (mutation) done on it.
As an example, "find all properties with exact name" or "find all properties with a hyphen" and "replace the value with {redacted: true}".
I tried with R.set and R.lensProp but it seems to act on properties at the root level only. Here's my playground. Let's say I'd like to replace baz value with {redacted: true} or run R.map on it when it's an array.
const arr = [
{
id: 5,
name: "foo",
baz: [
{
a: 1,
b: 2
},
{
a: 10,
b: 5
}
],
other: {
very: {
nested: {
baz: [
{
a: 1,
b: 2
}
]
}
}
}
},
{
id: 6,
name: "bar",
baz: []
}
];
const result = R.set(
R.lensProp("baz"),
{
replaced: true,
},
arr[0]
);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
I feel I'm missing something very basic.

You'll need a recursive function that handles:
Arrays - mapping and calling itself on each item
Other objects - converting the object to array with R.toPairs, mapping the array of pairs, calling the predicate on each [key, value] pair, and calling itself on each value which is also an array
Primitives returning as is
const { curry, cond, is, pipe, toPairs, map, when, last, evolve, identity, fromPairs, T } = R;
const transform = curry((pred, arr) => cond([
[is(Array), map(a => transform(pred, a))], // handle array - map each item
[is(Object), pipe( // handle objects which are not arrays
toPairs, // convert to pairs
map(pipe( // map each pair
pred, // call predicate on the pair
when(pipe(last, is(Object)), evolve([ // handle array values
identity, // return key as is
a => transform(pred, a) // transform the array
])),
)),
fromPairs // convert back to an array
)],
[T, identity], // handle primitives
])(arr))
const redactBaz = transform(
R.when(R.pipe(R.head, R.equals('baz')), R.always(['redacted', true]))
);
const arr = [{"id":5,"name":"foo","baz":[{"a":1,"b":2},{"a":10,"b":5}],"other":{"very":{"nested":{"baz":[{"a":1,"b":2}]}}}},{"id":6,"name":"bar","baz":[]}];
const result = redactBaz(arr);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

Similar to Ori Drori's answer, we can create generic top-down or bottom-up traversals over all possible JS types, allowing transformation at each step by passing in the current value and, where appropriate, the associated object key or array index to the provided function.
Given you want to replace the entire subtree when finding a matching node, you can process this top-down.
const mapIndexed = R.addIndex(R.map)
function bottomUp(f, val) {
return R.is(Array, val) ? mapIndexed((v, i) => f(bottomUp(f, v), i), val)
: R.is(Object, val) ? R.mapObjIndexed((v, i) => f(bottomUp(f, v), i), val)
: f(val, null)
}
function topDown(f, val) {
function go(val, i) {
const res = f(val, i)
return R.is(Array, res) ? mapIndexed(go, res)
: R.is(Object, res) ? R.mapObjIndexed(go, res)
: res
}
return go(val, null)
}
/////
const arr = [{"id":5,"name":"foo","baz":[{"a":1,"b":2},{"a":10,"b":5}],"other":{"very":{"nested":{"baz":[{"a":1,"b":2}]}}}},{"id":6,"name":"bar","baz":[]}];
const result = topDown(
(v, k) => k == 'baz' ? { redacted: false } : v,
arr
)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>

One approach you could take is recursively finding all the paths in your object that match the provided key. An example of this can be seen in the findPaths function below. This makes use of R.chain to collect a matching path in a singleton array, or ignore unwanted keys with an empty array. If an object or array is found, it will recursively call the go function over them.
Once you have a list of all the paths you'd like to update, you can make use of R.assocPath to update the values at each path. For example, I have used reduce to iterate through each of the found paths in the redact function below.
const indexedChain = R.addIndex(R.chain)
const findPaths = (prop, obj) => {
function go(prefix, el) {
if (R.is(Array, el)) return indexedChain((v, i) => go(R.append(i, prefix), v), el)
if (R.is(Object, el)) return R.chain(k => {
if (k == prop) return [R.append(k, prefix)]
return go(R.append(k, prefix), el[k])
}, R.keys(el))
return []
}
return go([], obj)
}
const redact = (prop, obj) =>
R.reduce(
(obj_, path) => R.assocPath(path, { redacted: true }, obj_),
obj,
findPaths(prop, obj)
)
//////
const arr = [
{
id: 5,
name: "foo",
baz: [{a: 1, b: 2}, {a: 10, b: 5}],
other: {
very: {
nested: {
baz: [{a: 1, b: 2}]
}
}
}
},
{
id: 6,
name: "bar",
baz: []
}
]
//////
console.log(redact('baz', arr))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>

Generic Powerhouse
We could write a fairly generic nested object transformation function, which takes two callbacks, one a predicate which given a key and a value decides whether we're going to handle this node, and a second which takes a key and a value and returns a collection of key-value pairs. This lets us not only filter nodes or transform individual nodes but also create new nodes or split a single node in pieces. This has the great power / great responsibility feature, though. Writing the action callback can be uglier than we might want for simple transforms. But we then can write simpler functions built atop this to handle the simpler transforms.
Here's one implementation of such a broad function:
const transform = (pred, action) => (obj) =>
Array .isArray (obj)
? obj .map (transform (pred, action))
: Object (obj) === obj
? Object .fromEntries (
Object .entries (obj) .flatMap (
([k, v]) => pred (k, v)
? action (k, v).map (transform (pred, action))
: [[k, transform (pred, action) (v)]]
)
)
: obj
We could use it like this:
const splitHyphensAndIncrement = transform (
(k) => k .includes ('-'),
(k, v) => k .split ('-') .map ((_k, i) => [_k, v + i])
)
splitHyphensAndIncrement ({a: 1, b: [{c: 2, 'x-y': 10}, {c: 3, 'x-y': 20}], 'x-y-z': 42})
//=> {a: 1, b: [{c: 2, x: 10, y: 11}, {c: 3, x: 20, y: 21}], x: 42, y: 43, z: 44}
Note what it does. It replaces single nodes including hyphens with multiple node, for instance, it replaces the x-y-x: 42 node with three nodes: x: 42, y: 43, z: 44.
This is quite powerful. But often, we don't want to deal with having to return an array of key-value pairs, when all we want to do is to convert our node into another node.
Changing Single Values
We can write a simpler transformer on top of this:
const transformSimple = (pred, action) =>
transform (pred, (k, v) => [[k, action(v)]])
The predicate here still can use the key and the value, but the action does nothing but transform a value into another one. Here's a simple example:
const squareNumbers = transformSimple (
(k, v) => typeof v == 'number',
(n) => n * n
)
squareNumbers({foo: 'a', x: 3, y: 5, z: {bar: 42, x: 7}})
//=> {foo: 'a', x: 9, y: 25, z: {bar: 1764, x: 49}}
Perhaps we want a version that only squares with certain keys. We can change only the predicate:
const squareXyzNumbers = transformSimple (
(k, v) => ['x', 'y', 'z'] .includes (k) && typeof v == 'number',
(n) => n * n
)
squareXyzNumbers ({foo: 'a', x: 3, y: 5, z: {bar: 42, x: 7}})
//=> {foo: "a", x: 9, y: 25, z: {bar: 42, x: 49}}
We can also use this to add sequential id numbers to each object encountered:
const addSequentialIds = transformSimple (
(k, v) => Object (v) === v,
((n) => (obj) => ({_id: ++n, ...obj}))(0)
)
addSequentialIds ({foo: {bar: 'a', baz: {qux: 'b', corge: {grault: 'c'}}}})
//=> {foo: {_id :1, bar: 'a', baz: {_id: 2, qux: 'b', corge: {_id: 3, grault: 'c'}}}}
Changing Keys
We can write a simple wrapper which transforms all keys like this:
const transformKey = (fn) =>
transform (() => true, (k, v) => [[fn(k), v]])
and use it like this:
const shoutKeys = transformKey (
s => s.toUpperCase()
)
shoutKeys ({foo: 1, bar: {baz: 2, qux: {corge: 3, grault: 4}}})
//=> {FOO: 1, BAR: {BAZ: 2, QUX: {CORGE: 3, GRAULT: 4}}}
Or of course we could choose to write a version that uses a predicate to decide whether to transform the key or not.
The point is that this powerful function easily offers us ways to write the simpler generic transformation functions we want, and those can lead to fairly nice code.
More Power?
Could we make this more powerful? Certainly. I'm not going to try it now, but I keep imagining changing
? action (k, v).map (transform (pred, action))
to
? action (k, v, transform (pred, action))
so that your action function receives the key, the value, and the recursive transformer, which you can choose to apply or not to the nodes you generate.
This is undoubtedly more powerful, but also gives you more chances to mess up. That might be worth the trade-off, especially if you mostly use it to create things like transformSimple or transformKey.
Original Question
Here's how we might use this to handle the original question, replacing baz: <something> with baz: {redacted: true}. We don't need the full power of transform, but can use transformSimple:
const redactBaz = transformSimple (
(k, v) => k === 'baz',
(v) => ({redacted: true})
)
const arr = [{id: 5, name: "foo", baz: [{a: 1, b: 2}, {a: 10, b: 5}], other: {very: {nested: {baz: [{a: 1, b: 2}]}}}}, {id: 6,name: "bar", baz: []}]
redactBaz (arr) //=>
// [
// {id: 5, name: 'foo', baz: {redacted: true}, other: {very: {nested: {baz: {redacted :true}}}}},
// {id: 6, name: 'bar', baz: {redacted: true}}
// ]
In Action
We can see all this in action in this snippet:
const transform = (pred, action) => (obj) =>
Array .isArray (obj)
? obj .map (transform (pred, action))
: Object (obj) === obj
? Object .fromEntries (
Object .entries (obj) .flatMap (
([k, v]) => pred (k, v)
? action (k, v) .map (transform (pred, action))
: [[k, transform (pred, action) (v)]]
)
)
: obj
const res1 = transform (
(k) => k .includes ('-'),
(k, v) => k .split ('-') .map ((_k, i) => [_k, v + i])
) ({a: 1, b: [{c: 2, 'x-y': 10}, {c: 3, 'x-y': 20}], 'x-y-z': 42})
const transformSimple = (pred, action) =>
transform (pred, (k, v) => [[k, action(v)]])
const res2 = transformSimple (
(k, v) => typeof v == 'number',
(n) => n * n
)({foo: 'a', x: 3, y: 5, z: {bar: 42, x: 7}})
const res3 = transformSimple (
(k, v) => ['x', 'y', 'z'] .includes (k) && typeof v == 'number',
(n) => n * n
) ({foo: 'a', x: 3, y: 5, z: {bar: 42, x: 7}})
const res4 = transformSimple (
(k, v) => Object (v) === v,
((n) => (obj) => ({_id: ++n, ...obj}))(0)
)({foo: {bar: 'a', baz: {qux: 'b', corge: {grault: 'c'}}}})
const transformKey = (fn) =>
transform (() => true, (k, v) => [[fn(k), v]])
const res5 = transformKey (
s => s.toUpperCase()
) ({foo: 1, bar: {baz: 2, qux: {corge: 3, grault: 4}}})
const arr = [{id: 5, name: "foo", baz: [{a: 1, b: 2}, {a: 10, b: 5}], other: {very: {nested: {baz: [{a: 1, b: 2}]}}}}, {id: 6,name: "bar", baz: []}]
const res6 = transformSimple (
(k, v) => k === 'baz',
(v) => ({redacted: true})
) (arr)
console .log (res1)
console .log (res2)
console .log (res3)
console .log (res4)
console .log (res5)
console .log (res6)
.as-console-wrapper {max-height: 100% !important; top: 0}
Ramda
I'm one of the founders of Ramda and a big fan, but I rarely use it in recursive situations. It isn't particularly designed to help with them.
But if I was writing this in an environment that was already using Ramda, it can certainly help around the edges. is, map, pipe, toPairs, fromPairs, chain are all slightly nicer than their native counterparts, and there's a reasonable ifElse lurking in the Object branch. So we could write this like:
const transform = (pred, action) => (obj) =>
is (Array) (obj)
? map (transform (pred, action)) (obj)
: is (Object) (obj)
? pipe (
toPairs,
chain (apply (ifElse (
pred,
pipe (action, map (transform(pred, action))),
(k, v) => [[k, transform (pred, action) (v)]]
))),
fromPairs
) (obj)
: obj
There also looks to be a cond lurking at the root of this function (note that all the conditions and consequents end by applying our parameter, except the final clause, which could easily be replaced by identity), but I'm not quite sure how to make that work with the recursion.
It's not that Ramda offers nothing here. I slightly prefer this version to the original. But there's not enough difference that I would introduce Ramda to a codebase that wasn't using it just for those minor advantages. And if I were to get that cond working, it might well be a clearly better version.
Update
I did figure out how to get that cond working, and it's nicer code, but still not quite what I'd like. That version looks like this:
const transform = (pred, action) => (o) => cond ([
[is (Array), map (transform (pred, action))],
[is (Object), pipe (
toPairs,
chain (apply (ifElse (pred, pipe (action, map (transform (pred, action))), (k, v) => [[k, transform (pred, action) (v)]]))),
fromPairs
)],
[T, identity]
]) (o)
If I didn't have to have the (o) => cond ([...]) (o) and could just use cond ([...]) I would be very happy with this. But the recursive calls inside the cond won't permit that.

Related

Compare JSON Keys to get missing elements

I want to compare to Nested Objects and create a new Object with all the missing fields.
I have a main JSON respone in a Javascript Object:
var MainFile = {
"id": 0,
"name": 'test',
"info": {
"data_11":0,
"data_12":0,
"data_13":{
"data_131":0,
"data_132":0,
"data_133":0,
},
},
"info2": {
"data_21":0,
"data_22":0,
"data_23":0,
}
}
And now I have x amount of objects that I have to compare against the main and check that object has all the same keys.
var obj2 = {
"id": 0,
"info": {
"data_11":0,
"data_13":{
"data_131":0,
"data_133":0,
},
},
"info2": {
"data_22":0,
"data_23":0,
}
}
}
So seeing both objects we can see the differences. I've tried recursive functions to check with Object.hasProperty, but I never get the result I'm looking for. that would be an object that would look like the following:
result = {
"id": true,
"name": false,
"info": {
"data_11":true,
"data_12":false,
"data_13":{
"data_131":false,
"data_132":true,
"data_133":false,
},
},
"info2": {
"data_21":true,
"data_22":false,
"data_23":false,
}
}
Has anyone tried anything like this? I've looked everywhere, but everyone compares the value of the key, not if the actual key is missing in the nested array.
Any help would be appreciated
One advantage of keeping around a collection of useful utility functions is that we can combine them quickly to create new functionality. For me, this function is as simple as this:
const diffs = (o1, o2) =>
hydrate (getPaths (o1) .map (p => [p, hasPath (p) (o2)]))
We first call getPaths (o1), which yields
[
["id"],
["name"],
["info", "data_11"],
["info", "data_12"],
["info", "data_13", "data_131"],
["info", "data_13", "data_132"],
["info", "data_13", "data_133"],
["info2", "data_21"],
["info2", "data_22"],
["info2", "data_23"]
]
Then we map these into path entry arrays, using hasPath against our test object, which yields:
[
[["id"], true],
[["name"], false],
[["info", "data_11"], true],
[["info", "data_12"], false],
[["info", "data_13", "data_131"], true],
[["info", "data_13", "data_132"], false],
[["info", "data_13", "data_133"], true],
[["info2", "data_21"], false],
[["info2", "data_22"], true],
[["info2", "data_23"], true]
]
This is very similar to the format used by Object .fromEntries, except that the keys are arrays of values rather than single strings. (Those values could be strings for object keys or integers for array indices.) To turn this back into an object, we use hydrate, which in turn depends upon setPath. Again hydrate tis a generalization of Object .fromEntries, and setPath is a recursive version of setting a value.
Put together it looks like this:
// utility functions
const getPaths = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, v]) => getPaths (v) .map (p => [Array .isArray (obj) ? Number(k) : k, ... p])
)
: [[]]
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
const hasPath = ([p, ...ps]) => (obj) =>
p == undefined ? true : p in obj ? (ps .length > 0 ? hasPath (ps) (obj [p]) : true) : false
// main function
const diffs = (o1, o2) =>
hydrate (getPaths (o1) .map (p => [p, hasPath (p) (o2)]))
// sample data
const MainFile = {id: 0, name: "test", info: {data_11: 0, data_12: 0, data_13: {data_131: 0, data_132: 0, data_133: 0}}, info2: {data_21: 0, data_22: 0, data_23: 0}}
const obj2 = {id: 0, info: {data_11: 0, data_13: {data_131: 0, data_133: 0}}, info2: {data_22: 0, data_23: 0}};
// demo
console .log (diffs (MainFile, obj2))
.as-console-wrapper {max-height: 100% !important; top: 0}
The functions that this is built upon are all generally useful for many problems, and I simply keep them in my back pocket. You may note that this also handles arrays; that just comes along with those functions
diffs ({foo: ['a', 'b', 'c']}, {foo: ['a', 'b']})
//=> {"foo": [true, true, false]}
I also want to note that if you're looking for a more complete diff function, an old answer by user Mulan is a great read.

Find path to ALL matching key values in nested object

I have a function that gets me the path to the first finding of a nested object where the key and the value matches.
function getPath(obj, givenKey, givenValue) {
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") {
var result = getPath(obj[key], givenValue, givenKey);
if(result) {
result.unshift(key)
return result;
}
} else if(obj[key] === givenValue && key === givenKey ) {
return [key];
}
}
}
sample data
var myObj = [
{
"name": "needle",
"children": [
{
"name": "group2",
"children": [
{
"name": "item0"
}]
}]
},
{
"name": "item1"
},
{
"name": "needleGroup",
"children": [
{
"name": "needleNestedGroup",
"children": [
{
"name": "item3"
},
{
"name": "needleNestedDeeperGroup",
"children": [
{
"name": "needle"
}]
}]
}]
}];
expected output
getPath(myObj, "name", "needle"):
[0, "name"]
["2","children","0","children","1","children","0","name"]
However, I have now an object that contains these key-values multiple times, so I have multiple matches.
How can I get all of them in an array?
My current function is just stopping after it finds the first match. The fact, that it's recursive makes things very complicated for me
I would write this atop a more generic findAllPaths function that accepts a predicate and finds the paths of all nodes in the object that match that predicate. With that, then findPathsByName is as simple as (target) => findAllPaths (({name}) => name == target).
In turn, I build findAllPaths on pathEntries, variants of which I use all the time. This function turns an object into an array of path/value pairs. Some versions only generate the leaf nodes. This one generate it for all nodes, including the root (with an empty path.) The basic idea of this function is to turn something like:
{a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}
into this:
[
[[], {a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}],
[['a'], 'foo'],
[['b'], {c: ['bar', 'baz'], f: 'qux'}],
[['b', 'c'], ['bar', 'baz']],
[['b', 'c', 0], 'bar'],
[['b', 'c', 1], 'baz'],
[['b', 'f'], 'qux']
]
where the first item in every subarray is a path and the second a reference to the value at that path.
Here is what it might look like:
const pathEntries = (obj) => [
[[], obj],
...Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (
([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v]
)
)
: []
]
const findAllPaths = (predicate) => (o) =>
[...pathEntries (o)] .filter (([p, v]) => predicate (v, p)) .map (([p]) => p)
const findPathsByName = (target) => findAllPaths (({name}) => name == target)
const myObj = [{name: "needle", children: [{name: "group2", children: [{name: "item0"}]}]}, {name: "item1"}, {name: "needleGroup", children: [{name: "needleNestedGroup", children: [{name: "item3"}, {name: "needleNestedDeeperGroup", children: [{name: "needle"}]}]}]}]
console .log (findPathsByName ('needle') (myObj))
.as-console-wrapper {max-height: 100% !important; top: 0}
The question asked for string values for the array indices. I prefer the integer values myself as done here, but you simplify the function a bit:
- ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v]
+ ([p, v]) => [[k, ... p], v]
Instead of returning the value, you could push it to an array and keep iterating.
At the end of the function, you return the array.
function getPath(obj, givenKey, givenValue) {
let matches = [];
for (var key in obj) {
if (obj[key] && typeof obj[key] === "object") {
var result = getPath(obj[key], givenValue, givenKey);
if (result) {
result.unshift(key)
matches.push(...result);
}
} else if (obj[key] === givenValue && key === givenKey) {
matches.push(key);
}
}
return matches;
}

Iterative depth-first traversal with remembering value's paths

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), {})

How can I deeply map over object with Ramda

I'm trying to find all "template values" e.g. { template: 'Date: <now>'} using a map function to get this basic behaviour:
deepMap(mapFn, {a: 1, b: { c: 2, d: { template: 'Date: <now>'}}})
>> {a: 1, b: { c: 2, d: 'Date: 13423234232'}}
This is what I have so far. The interpolation of the template object does happen, but it does not replace the value.
const obj = {a: 1, b: { c: 2, d: { template: 'Date: <now>'}}};
const deepMap = (fn, xs) =>
mapObjIndexed(
(val, key, obj) =>
or(is(Array, val), is(Object, val))
? deepMap(fn, fn(val))
: fn(val),
xs
);
const checkFn = ({ template }) => template;
const transformFn = (val, key) => {
const interpolated = val.template.replace('<now>', Date.now())
console.log(interpolated);
return interpolated;
};
const mapFn = n =>
checkFn(n)
? transformFn(n)
: n;
console.clear();
deepMap(mapFn, obj);
>> {"a": 1, "b": {"c": 2, "d": {}}}
The problem is you are calling deepMap on the mapped value again - but the mapped value isn't an object anymore, but a string.
or(is(Array, val), is(Object, val))
? deepMap(fn, fn(val))
: fn(val),
In case val is { template: 'Date: <now>'}, val is an object and could be deep-mapped, but fn(val) is a String ("Date: 123123123") which should simply be returned. One solution is to make the is checks on the mapped value, not the original value:
(val, key) => {
const mappedVal = fn(val);
return or(is(Array, mappedVal), is(Object, mappedVal))
? deepMap(fn, mappedVal)
: mappedVal;
},
Another possibility would be to check whether the map-function returned something else than the original value and don't recurse in this case.
Something like this should work:
const {map, has, is} = R
const transformTemplate = ({template}) => template.replace('<now>', Date.now())
const deepMap = (xs) => map(x => has('template', x)
? transformTemplate(x)
: is(Object, x) || is(Array, x)
? deepMap(x)
: x, xs)
const result = deepMap({a: 1, b: { c: 2, d: { template: 'Date: <now>'}}})
// => {a: 1, b: {c: 2, d: "Date: 1542046789004"}}
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
If you wanted to pass in the transformation function, you can change it slightly to
const deepMap = (transformer, xs) => map(x => has('template', x)
? transformer(x)
: is(Object, x) || is(Array, x)
? deepMap(transformer, x)
: x, xs)
const result = deepMap(transformTemplate, {a: 1, b: { c: 2, d: { template: 'Date: <now>'}}})
And of course you can wrap that in curry if you like.
I don't have time right now to investigate why this approach, which looks right at first glance, doesn't work. I'm hoping it's something simple:
const deepMap = map(cond([
[has('template'), transformTemplate],
[is(Object), deepMap],
[is(Array), deepMap],
[T, identity]
]))

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