I have an dot notated array such as:
var base = ['a.b.c','a.e','h'];
I also have a reference object:
var reference = {
a: {
b: {
c:"Hello",
d:"you"
},
e:"beautiful",
f:"and"
},
g:"kind",
h:"World"
};
I need to build an object out of the base notated string and get the values from the reference object. Unfortunately, I cannot change the base nor the reference.
I have it working for each individual part of the array but the problem is when I try to merge the object. It overwrites the value a.b.c with a.e.
My desired output would be:
var desiredOutput = {
a: {
b: {
c:"Hello"
},
e:"beautiful"
},
h:"World"
};
However, with the code below my output is:
var output = {
a: {
e: "beautiful"
},
h: "World"
};
Any help would be appreciated. I am limited to < ES5 but could use some polyfills such as Object.assign if needed.
function convert(base,reference) {
var obj = {};
var getObjectValue = function(string, reference) {
var i = 0;
while (reference && i < string.length) {
reference = reference[string[i++]];
}
return reference;
};
for (var n = 0; n < base.length; n++) {
var s = base[n].split("."),
x = obj;
for(var i = 0; i < s.length-1; i++) {
x = x[s[i]] = {};
}
x[s[i]] = getObjectValue(s,reference);
}
return obj;
}
var base = ['a.b.c','a.e','h'];
var reference = {
a: {
b: {
c:"Hello",
d:"you"
},
e:"beautiful",
f:"and"
},
g:"kind",
h:"World"
};
var desiredOutput = {
a: {
b: {
c:"Hello"
},
e:"beautiful"
},
h:"World"
};
console.log(convert(base,reference));
With your
x = x[s[i]] = {};
You're unconditionally overwriting the s[i] property even if it's already been populated. Only assign a new object if another object doesn't already exist at that property.
if (x[s[i]]) {
x = x[s[i]];
} else {
x = x[s[i]] = {};
}
function convert(base,reference) {
var obj = {};
var getObjectValue = function(string, reference) {
var i = 0;
while (reference && i < string.length) {
reference = reference[string[i++]];
}
return reference;
};
for (var n = 0; n < base.length; n++) {
var s = base[n].split("."),
x = obj;
for(var i = 0; i < s.length-1; i++) {
if (x[s[i]]) {
x = x[s[i]];
} else {
x = x[s[i]] = {};
}
}
x[s[i]] = getObjectValue(s,reference);
}
return obj;
}
var base = ['a.b.c','a.e','h'];
var reference = {
a: {
b: {
c:"Hello",
d:"you"
},
e:"beautiful",
f:"and"
},
g:"kind",
h:"World"
};
var desiredOutput = {
a: {
b: {
c:"Hello"
},
e:"beautiful"
},
h:"World"
};
console.log(convert(base,reference));
This is how I'd approach it:
var base = ['a.b.c','a.e','h'];
var reference = {
a: {
b: {
c:"Hello",
d:"you"
},
e:"beautiful",
f:"and"
},
g:"kind",
h:"World"
};
const results = {};
// Helper function to get the nested value from an array of properties:
const getVal = props => props.reduce((a, b) => a[b], reference);
for (const propStr of base) {
// Turn string into an array of properties:
const props = propStr.split('.');
// Get nested value to assign at the end:
const val = getVal(props);
// Get last property to assign on the last object:
const lastProp = props.pop();
let obj = results;
// Iterate through to the nested object on the `results`,
// creating objects on the way only if they don't exist yet:
while (props.length) {
const prop = props.shift();
if (obj[prop]) obj = obj[prop];
else {
obj[prop] = {};
obj = obj[prop];
}
}
obj[lastProp] = val;
}
console.log(results);
This can be achieved without while loops!
I've only used es6's Array.prototype.reduce (and semantics, but you can convert/transpile those your self). You can also easily poly/ponyfill it, see this article.
const base = ['a.b.c', 'a.e', 'h'];
const reference = {
a: {
b: {
c: 'Hello',
d: 'you'
},
e: 'beautiful',
f: 'and'
},
g: 'kind',
h: 'World'
};
const isObject = item => (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
const pick = (obj, keys) => keys.split('.').reduce((prev, key) => {
const ret = (prev || obj);
if (isObject(ret) && key in ret) {
return ret[key];
}
return prev;
}, null);
const extend = (target, source) => {
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach((key) => {
if (isObject(source[key])) {
if (!target[key] || !isObject(target[key])) {
target[key] = source[key];
}
extend(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
});
}
return target || source;
};
const fromDotNotation = (dotNotation, value) => {
const ret = {};
dotNotation.split('.').reduce((prev, key, i, self) => {
return prev[key] = (i === self.length - 1) ? value : prev[key] || {};
}, ret);
return ret;
}
const fromDotNotations = (source, dotNotations) => dotNotations.reduce((prev, dotNotation) => {
return extend(prev, fromDotNotation(dotNotation, pick(source, dotNotation)));
}, {});
const output = fromDotNotations(reference, base);
console.log(JSON.stringify(output, null, 2));
In here, we want to be able to build an object from any dot notation, with an initial value at its tail. We'll use a plucking method to pick the initial value from the reference object. Looping over multiple dot notations, we can create an array of constructed objects. simply using a merging method, we can create a target object to which we want to merge our constructed objects into.
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