I need to recursive check two objects and keys need to be sorted, so I created two functions.
buildObj - get all uniq keys from object, sort them and call buildObjKey on each key
buildObjKey - if one of values is object call buildObj for these. Other returns is example and real code is more complex.
So question: I define buildObj before buildObjKey and call buildObjKey while its not defined yet. This is bad practice, but if I move definition of buildObj after buildObjKey I will call buildObj before It was defined... This is possible to do recursive calls between two functions without this trouble?
const _ = require('lodash')
const example1 = {
key3: 'foo',
key1: {
key4: {
key6: 'boo'
},
},
}
const example2 = {
key3: 'too',
key1: {
key4: {
key6: 'hoo'
},
},
}
const buildObj = (obj1, obj2) => {
return _.uniq([...Object.keys(obj1), ...Object.keys(obj2)])
.sort()
.map(key => buildObjKey(key, obj1, obj2))
}
const buildObjKey = (key, obj1, obj2) => {
const val1 = obj1[key];
const val2 = obj2[key];
if(val1 && val2){
if(_.isObject(val1) && _.isObject(val2)){
return buildObj(val1, val2)
}
if(_.isObject(val1)){
return buildObj(val1, val1)
}
if(_.isObject(val2)){
return buildObj(val2, val2)
}
return val1
}
return val1 || val2
}
buildObj(example1, example2)
Execution example [ [ [ 'boo' ] ], 'foo' ]
The real code is doing diff of two object, i don't write it here because of complexity. Here is simple example of structure.
misunderstanding
I define
buildObj
beforebuildObjKey
and callbuildObjKey
while its not defined yet... This is bad practice, but if I move definition ofbuildObj
afterbuildObjKey
I will callbuildObj
before It was defined...
That's untrue. buildObj
is a function and it is not called before you define buildObjKey
. The fact that buildObj
contains a reference to buildObjKey
does not mean that it tries to immediately call it. To demonstrate this concretely, let's see a simplified example below.
Notice how isEven
and isOdd
do not produce output until one of the functions is actually called -
function isEven (n)
{ console.log("isEven", n)
if (n == 0)
return true
else
return isOdd(n - 1) // <- calls isOdd
}
function isOdd (n)
{ console.log("isOdd", n)
if (n == 0)
return false
else
return isEven(n - 1) // <- calls isEven
}
console.log("first line of output")
console.log("result", isOdd(3))
first line of output
isOdd 3
isEven 2
isOdd 1
isEven 0
result true
possible and powerful
This is possible to do recursive calls between two functions without this trouble?
This is a massively powerful technique known as mutual recursion. Mutual recursion is an excellent way to process recursive trees like the deeply-nested objects you have in your program. You have good intuition to process them this way. See this Q&A for a practical example and explanation.
related
Coincidentally I wrote a generic object diffing function in this Q&A. This demonstrates the strength of generic functions and reusable code. Using the test1
and test2
inputs from your question, we can calculate a precise diff without any modification to the original code -
const test1 = {
"common": {
"setting1": "Value 1",
"setting2": 200,
"setting3": true,
"setting6": {
"key": "value",
"doge": {
"wow": ""
}
}
},
"group1": {
"baz": "bas",
"foo": "bar",
"nest": {
"key": "value"
}
},
"group2": {
"abc": 12345,
"deep": {
"id": 45
}
}
}
const test2 = {
"common": {
"follow": false,
"setting1": "Value 1",
"setting3": null,
"setting4": "blah blah",
"setting5": {
"key5": "value5"
},
"setting6": {
"key": "value",
"ops": "vops",
"doge": {
"wow": "so much"
}
}
},
"group1": {
"foo": "bar",
"baz": "bars",
"nest": "str"
},
"group3": {
"fee": 100500,
"deep": {
"id": {
"number": 45
}
}
}
}
console.log(diff(test1, test2))
Expand the snippet below to verify the results of diff
in your own browser -
const isObject = x =>
Object(x) === x
const isArray =
Array.isArray
const mut = (o, [ k, v ]) =>
(o[k] = v, o)
const diff1 = (left = {}, right = {}, rel = "left") =>
Object
.entries(left)
.map
( ([ k, v ]) =>
isObject(v) && isObject(right[k])
? [ k, diff1(v, right[k], rel) ]
: right[k] !== v
? [ k, { [rel]: v } ]
: [ k, {} ]
)
.filter
( ([ _, v ]) =>
Object.keys(v).length !== 0
)
.reduce
( mut
, isArray(left) && isArray(right) ? [] : {}
)
const merge = (left = {}, right = {}) =>
Object
.entries(right)
.map
( ([ k, v ]) =>
isObject(v) && isObject(left [k])
? [ k, merge(left [k], v) ]
: [ k, v ]
)
.reduce(mut, left)
const diff = (x = {}, y = {}, rx = "left", ry = "right") =>
merge
( diff1(x, y, rx)
, diff1(y, x, ry)
)
const test1 = {
"common": {
"setting1": "Value 1",
"setting2": 200,
"setting3": true,
"setting6": {
"key": "value",
"doge": {
"wow": ""
}
}
},
"group1": {
"baz": "bas",
"foo": "bar",
"nest": {
"key": "value"
}
},
"group2": {
"abc": 12345,
"deep": {
"id": 45
}
}
}
const test2 = {
"common": {
"follow": false,
"setting1": "Value 1",
"setting3": null,
"setting4": "blah blah",
"setting5": {
"key5": "value5"
},
"setting6": {
"key": "value",
"ops": "vops",
"doge": {
"wow": "so much"
}
}
},
"group1": {
"foo": "bar",
"baz": "bars",
"nest": "str"
},
"group3": {
"fee": 100500,
"deep": {
"id": {
"number": 45
}
}
}
}
console.log(diff(test1, test2))
{
"common": {
"setting2": {
"left": 200
},
"setting3": {
"left": true,
"right": null
},
"setting6": {
"doge": {
"wow": {
"left": "",
"right": "so much"
}
},
"ops": {
"right": "vops"
}
},
"follow": {
"right": false
},
"setting4": {
"right": "blah blah"
},
"setting5": {
"right": {
"key5": "value5"
}
}
},
"group1": {
"baz": {
"left": "bas",
"right": "bars"
},
"nest": {
"left": {
"key": "value"
},
"right": "str"
}
},
"group2": {
"left": {
"abc": 12345,
"deep": {
"id": 45
}
}
},
"group3": {
"right": {
"fee": 100500,
"deep": {
"id": {
"number": 45
}
}
}
}
}
So there are a number of ways you can get around this problem & I'd honestly say that it may depend on what you consider important, i.e. it may depend on your style of coding & what preferences you have.
A simple enough technique to use, feel free to read up more on MDN, but in a nutshell hoisting is where you declare a variable towards the start of the file, then use it at some later point. But with functions specifically all you need to do to make use of this concept is define them as traditional functions. The idea being that when you define a function using the keyword function
, the function essentially gets defined before other variables within the scope.
Here's a little example:
test();
function test() {
console.log("Hello! :)")
}
Personally I'm a big fan of currying, I think it's pretty damn great to be honest with you, so much so that I've even written about how currying works great with React. If you're unsure of what currying is, it's essentially where you have one method that accepts one argument & returns a result or a function that also takes one argument & so on. If you're new to this concept, maybe read through the likes of this post, I won't go on & on about it because I know that other people will do a better job of explaining it than I will! But that aside, you could essentially achieve something like this:
const fn1 = (arg) => (callback) => {
if (callback === undefined || callback === null) {
console.log(arg);
} else if (typeof callback === "function") {
callback(arg);
} else {
throw new Error("The callback argument must be defined, fn1(arg)(callback).");
}
}
const fn2 = (arg) => {
if (arg === "test") {
fn1(arg)(() => {
console.log(`This is a ${arg}`);
});
} else {
fn1(arg)();
}
};
fn2(5);
fn2("test");
I mean you could even use a more tried & tested method & make use of a call-back function without the currying if you so desired, i.e. you could pass in an object that contains an array of keys. Then based on what keys are present in the provided arguments, you could very easily do it that way if you desired.
This is another simple way, where all of your functions are defined within a class, you could essentially make the class static/stateless if you desire, I mean it's a bit of a context specific subject matter I think, not to mention your coding style(s) & ideas. But anyway, you don't have to worry about the order in which you write your code or anything like that, you can just do something straight up simple & easy to maintain, like so:
class Test {
run () {
this.print();
}
print() {
console.log("I ran!");
}
}
new Test().run();
I mean this approach would make a bit more sense if you're using TypeScript as you can define print
as a private method, thus you'd only be able to call the run
method outside of the class, but I'm confident that you get the idea?
I think that it depends on your ESLint configuration also, but more importantly your style of coding & what you or your team value the most, I think it's reasonable to state that there are many ways you could solve this problem, it's just a matter of deciding what suits your needs best.
If anyone has anything to add, feel free to edit my answer or add a comment, but I think this is one of those matters where there's no one correct answer? Feel free to disagree! P.S. As you've tagged this question with functional-programming
, I'd suggest you make use of currying, I for one love it!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With