Get last defined element in path of lodash.get(object, path) - javascript

Given an object:
const obj = {
a: {
b: {
c: {
d: 5
}
}
}
};
I want a function similar to lodash.get(obj, path), which returns the last defined variable in the path:
console.log(_.something(obj, 'a.b.c.d')) // 5, which is obj.a.b.c.d
// because obj.a.b.c.d is defined
console.log(_.something(obj, 'a.b.c.e')) // { d: 5 }, which is obj.a.b.c
// because the path is only defined up to obj.a.b.c
console.log(_.something(obj, 'a.b.f')) // { c: { d: 5 } }, which is a.b
// because the path is only defined up to a.b
I can write it myself, but I wonder if I can do it with an existing lodash function. I've looked through the documentation, but nothing caught my eye.
Here's a rough implementation of what i want:
const safeGet = (object, path) => {
const keys = path.split('.');
let current = object;
for (var i = 0; i < keys.length; i++){
const val = current[keys[i]]
if (val === undefined){
return current;
}
current = val;
}
}

There isn't a way to do that in Lodash by default, but here is a simpler function that I wrote than the one you currently have displayed. Hopefully this helps!
const obj1 = {
a: {
b: {
c: {
d: 5
}
}
}
};
const obj2 = {
a: {
b: {
c: {}
}
}
};
function getLastDefined(obj, searchPath) {
return _.get(obj, searchPath) || getLastDefined(obj, _.chain(searchPath).split('.').reverse().tail().reverse().join('.').value());
}
console.log(getLastDefined(obj1, 'a.b.c.d'));
console.log(getLastDefined(obj2, 'a.b.c.d'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Related

Get parent, grandparent and key in the deep nested object structure

I have a deeply nested structure in the javascript object without any arrays in it.
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
let arr = [];
const findParentGrandparent = (obj, target) => {
Object.entries(obj).forEach(child => {
if (typeof child[1] === 'object') {
findParentGrandparent(child[1]);
}
});
};
findParentGrandparent(data, 'c');
When I call the function with a target, I want to get the taget key itself, parent and grandparent.
For example, if the target is 'c', arr should become
['c', 'greatgrand', 'grand', 'child'];
if target is 'greatgrand', it should become
['greatgrand', 'grand', 'child'];
Thanks
I did it using your recursive pattern, you can change the way it handle errors also, here I throw if there is no result.
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
let arr = [];
const findParentGrandparent = (obj, target) => {
for (const child of Object.entries(obj)) {
if (typeof child[1] === 'object' && child[0] !== target) {
const result = findParentGrandparent(child[1], target);
return [...result, child[0]];
} else if (child[0] === target) {
return [child[0]];
}
};
throw new Error("not found"); // If it goes there the object is not found, you can throw or return a specific flag, as you wish.
};
console.log(findParentGrandparent(data, 'c'));
var data = {
bar: 'a',
child: {
b: 'b',
grand: {
greatgrand: {
c: 'c'
}
}
}
};
/**
* #param validate {boolean} = true - Pass true if need to check for existance of `target`
*/
const findParentGrandparent = (obj, target, validate = true) => {
let result = [];
for (let [key, value] of Object.entries(obj)) {
if (key === target) {
result.push(key);
break;
}
if (value.toString() === '[object Object]') {
result.push(key);
result = result.concat(findParentGrandparent(value, target, false))
}
}
if (validate && !result.includes(target)) {
return 'Not found';
}
return result;
};
let resultC = findParentGrandparent(data, 'c').reverse();
let resultGreatgrand = findParentGrandparent(data, 'greatgrand').reverse();
console.log('Result for "c":', resultC);
console.log('Result for "greatgrand":', resultGreatgrand);
You can use a recursive generator function:
function* get_vals(d, target, c = []){
for (var i of Object.keys(d)){
if (i === target){
yield [target, ...c.slice(0, 3)]
}
if (typeof d[i] === 'object'){
yield* get_vals(d[i], target, c = [i, ...c])
}
}
}
var result = get_vals(data, 'c').next().value
Output:
["c", "greatgrand", "grand", "child"]

How to print the value of data dynamically. The position of data can be up to nth level

How to print the value of data dynamically. The position of data can be up to nth level.
x = {
a: {
b: {
c: {
d: {
data: 'hello'
}
}
}
}
}
You use a recursive function like the following way:
const x = {
a: {
b: {
c: {
d: {
data: 'hello'
}
}
}
}
}
function getValue(obj){
var keys = Object.keys(obj);
for(var k of keys){
if (typeof obj[keys] === 'object') {
return getValue(obj[keys]);
}
else if(keys == 'data'){
return obj[keys];
}
else{
return 'not found';
}
}
}
console.log(getValue(x));
Here is a sample recursive javascript function :
function find(obj,keyword){
if(obj[keyword]){
return obj[keyword];
}
for(key in obj){
if(typeof obj[key] === 'object'){
var result=find(obj[key],keyword);
if(result){
return result;
}
}
}
return false;
}
x = {
a: {
b: {
c: {
d: {
data: 'hello'
}
}
}
}
};
result = find(x,"data");
console.log(result);

Javascript unflatten array/change datastructure

So heres the problem and heres the fiddle: https://jsfiddle.net/6zqco0mj/
const start = [{'a':'b'}, {'b':'c'}, {'c':'d'}, {'d':'e'}]
end =
{a:
{b:
{c:
{
d: {}
}
}
}
}
I have some code but not sure how would I dig deeper into an object
const start = [{'b':'c'}, {'a':'b'}, {'c':'d'}, {'d':'e'}];
const end = {};
function convert(key) {
const obj = getObj(key);
if(obj) {
const temp = {};
temp[obj[key]] = convert(obj[key]);
//findKey(obj[key]);
end[key] = temp;
}
}
function getObj(key) {
const foo = start.find((el, i) => { if(el[key]) { return el[key] } });
return foo;
}
function findKey(k) {
// what goes here?
}
convert('a');
console.log(end);
i think you went in the other way around for your aproach of recursivity; i gave it a try and this is what i got so far
const start = [{'b':'c'}, {'a':'b'}, {'c':'d'}, {'d':'e'}];
function convert(key, object) {
const obj = getObj(key);
if(obj) {
object[obj[key]] = {};
convert(obj[key], object[obj[key]]);
}
}
function getObj(key) {
const foo = start.find((el, i) => { if(el[key]) { return el[key] }});
return foo;
}
const end = { a: {}};
convert('a', end.a);
console.log(end);
You could use an object structure for collecting the data and build the tree.
This solutiotion works with unsorted data.
For getting the root properties, all keys and values are collected and only the subset is taken.
var data = [{ d: 'e' }, { b: 'c' }, { c: 'd' }, { a: 'b' }, { b : 'z' }],
tree = function (data) {
var keys = new Set,
values = new Set,
r = Object.create(null);
data.forEach(function (o) {
var [key, value] = Object.entries(o)[0];
keys.add(key);
values.add(value);
r[value] = r[value] || {};
r[key] = r[key] || {};
r[key][value] = r[key][value] || r[value];
});
values.forEach(v => keys.delete(v));
return Object.assign(...Array.from(keys, k => ({ [k]: r[k] })));
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

jscodeshift change object literal value

Using jscodeshift, how can I transform
// Some code ...
const someObj = {
x: {
foo: 3
}
};
// Some more code ...
to
// Some code ...
const someObj = {
x: {
foo: 4,
bar: '5'
}
};
// Some more code ...
?
I have tried
module.exports = function(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);
return root
.find(j.Identifier)
.filter(path => (
path.node.name === 'someObj'
))
.replaceWith(JSON.stringify({foo: 4, bar: '5'}))
.toSource();
}
but I just end up with
// Some code ...
const someObj = {
{"foo": 4, "bar": "5"}: {
foo: 3
}
};
// Some more code ...
which suggests that replaceWith just changes the key instead of the value.
You have to search for the ObjectExpression rather than for the Identifier:
module.exports = function(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);
j(root.find(j.ObjectExpression).at(0).get())
.replaceWith(JSON.stringify({
foo: 4,
bar: '5'
}));
return root.toSource();
}
Demo

Transform object literal to object containing paths?

I'd like to take an object literal, and convert it to a new object where the new object keys are paths.
const obj = {
test: {
qwerty: 'text',
abc: {
testing: 123
}
}
}
To:
{
'test.qwerty': 'text',
'test.abc.testing': 123
}
I've been looking through the lodash docs but I can't see anything that might do this quickly.
The object literal could be any number of levels deep.
What's the best way to approach this..?
Don't know if lodash has any built in function, but this is an approach using ES6 arrow functions, optional parameters and recursive call:
const obj = {
test: {
qwerty: 'text',
abc: {
testing: 123
}
}
}
pathMyObject = (obj, prefix, newObj = {}) => {
Object.keys(obj).forEach( key => {
const value = obj[key];
const newPrefix = (!prefix ? key : prefix + "." + key)
if (typeof value === 'object') {
pathMyObject(value, newPrefix, newObj)
} else {
newObj[newPrefix] = value;
}
})
return newObj
}
const path = pathMyObject(obj);
console.log(path);
/*
{
'test.qwerty': 'text',
'test.abc.testing': 123
}
*/
Here's a recursive function using Lodash's reduce function.
const obj = {
test: {
qwerty: 'text',
abc: {
testing: 123
}
}
};
function toPaths(obj, path) {
if (typeof obj !== 'object') { return { [path]: obj }; }
return _.reduce(obj, (result, value, key) =>
Object.assign({}, result, toPaths(value, path ? `${path}.${key}` : key))
, {});
}
console.log(toPaths(obj));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
It's only slightly less verbose than the non-Lodash equivalent:
const obj = {
test: {
qwerty: 'text',
abc: {
testing: 123
}
}
};
function toPaths(obj, path) {
if (typeof obj !== 'object') { return { [path]: obj }; }
return Object.keys(obj).reduce((result, key) =>
Object.assign({}, result, toPaths(obj[key], path ? `${path}.${key}` : key))
, {});
}
console.log(toPaths(obj));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
You can create recursive function to build object like this.
const obj = {
test: {
qwerty: 'text',
abc: {
testing: 123
}
}
}
function toStrings(data) {
var r = {}
function inner(data, c) {
for (var i in data) {
if (typeof data[i] == 'object') inner(data[i], c + i + '.')
else r[(c + i).replace(/\.$/, "")] = data[i]
}
}
inner(data, '');
return r;
}
console.log(toStrings(obj))

Categories