How can I get all possible paths to traverse the tree? - javascript

The question is: How can I get an array of all tree paths?
In this example there are 5 ways:
[
[{ 1: "2" }, { 3: "4" }],
[{ 1: "2" }, { 3: "5" }],
[{ 1: "6" }, { 7: "8" }],
[{ 1: "6" }, { 7: "9" }, { 10: "11" }],
[{ 1: "6" }, { 7: "9" }, { 10: "12" }],
]
I can loop through all nested objects with recursion, but I don't know how to get that array...
Here is my code:
const data = {
"1": {
"2": {
"3": {
"4": {},
"5": {}
}
},
"6": {
"7": {
"8": {},
"9": {
"10": {
"11": {},
"12": {}
}
}
}
}
}
}
let list = [];
function iterate(object) {
if( object !== null && typeof object == "object" ) {
Object.entries(object).forEach(([key, value]) => {
if( JSON.stringify(value) != JSON.stringify({})) {
let obj = {};
obj[key] = Object.keys(value)
list.push(obj);
iterate(value);
}
});
}
}
iterate(data);

I would first create a recursive function that will iterate the paths in a more intuitive format: each path will be represented as an array of nodes. This function could be a generator function.
Then create a mapping function that converts such path to a list of objects with single key/value pairs, assuming the given path has an even number of nodes.
Finally, use Array.from to consume the iterator returned by the generator function, and use the mapping argument to perform the mapping defined by the second function:
function* iteratePaths(object) {
const entries = Object.entries(Object(object));
if (entries.length == 0) return yield [];
for (const [key, value] of entries) {
for (const path of iteratePaths(value)) {
yield [key, ...path];
}
}
}
const toPairs = path =>
path.reduce((acc, val, i) => i % 2 ? [...acc, { [path[i-1]]: val }] : acc, []);
// Example input:
const data = {"1": {"2": {"3": {"4": {},"5": {}}},"6": {"7": {"8": {},"9": {"10": {"11": {},"12": {}}}}}}}
const result = Array.from(iteratePaths(data), toPairs);
console.log(result);

Related

How to push a consecutive number as string into an json object?

I want to push some data into a JSON file, which has just only consecutive numbers as keys and hash string as a value.
var readyFiles = JSON.parse(fs.readFileSync('requests.json', 'utf8'));
console.log(readyFiles)
const num = readyFiles.length
readyFiles.fileHashList.push({ num : _fileHash })
console.log(readyFiles)
var json = JSON.stringify(readyFiles)
fs.writeFileSync('requests.json', json, 'utf8');
the expected output should be like that:
{
"fileHashList": [
{
"1": "QmWpVdqGqPGo9ApLErzxBYfxD2eABKtYCQYCXQpnbYWjro"
},
{
"2": "QmTYsKnWBsVQTC1ioBHX7VWeY9tZLJXXzxuVkGiBSdBDea"
},
{
"3": "QmPiszxoFdCfwtLvWihaqFuBVpbU168WNVDbHUqtmuCDj3"
},
{
"4": "QmdLQ8TTg7EvJ4jvvbQiNWQUniKz6yKQugsvzVTMwNLJu1"
},
{
"5": "QmcziropQqSUDbNfAkYBwsLQXdEVf9N88Nqz4K1dfKeQGL"
},
{
"6": "QmSiEYVCY8G7zrc5X8UamkA6TymDyAg8dM39L7qnNFt7tY"
}
]
}
but instead, the actual behaviour is that:
{
"fileHashList": [
{
"1": "QmWpVdqGqPGo9ApLErzxBYfxD2eABKtYCQYCXQpnbYWjro"
},
{
"2": "QmTYsKnWBsVQTC1ioBHX7VWeY9tZLJXXzxuVkGiBSdBDea"
},
{
"3": "QmPiszxoFdCfwtLvWihaqFuBVpbU168WNVDbHUqtmuCDj3"
},
{
"4": "QmdLQ8TTg7EvJ4jvvbQiNWQUniKz6yKQugsvzVTMwNLJu1"
},
{
"5": "QmcziropQqSUDbNfAkYBwsLQXdEVf9N88Nqz4K1dfKeQGL"
},
{
"num": "QmSiEYVCY8G7zrc5X8UamkA6TymDyAg8dM39L7qnNFt7tY"
}
]
}
as you can see, there is 'num' as key in the last entry.
How can I have the next number of the keys there, espacially "6" in the above case?
const readyFiles = JSON.parse(fs.readFileSync('requests.json', 'utf8'));
const readyFilesKeys = Object.keys(readyFiles.fileHashList);
const countKey = readyFilesKeys.length + 1;
readyFiles.fileHashList.push({ [countKey]: _fileHash })
var json = JSON.stringify(readyFiles)
fs.writeFileSync('requests.json', json, 'utf8');
count the keys and + 1
use brackets [] outside the keys makes it possible, as #Taplar commented (THANKS :) )

How to sort an object of objects based on the keys in descending order?

I'm trying to order an array of object of objects based on the object's key.
How do I go about sorting a JSON Object that's already in ascending order? Please see below.
I've tried to convert the data value object into its own array, then sort it that way, but I'm getting syntax errors.
var object = [
A1: {
errors: {}
otherData: {}
data: {
"1": {
"name": "Ashley",
},
"2": {
"name": "Cardiff",
},
"3": {
"name": "Reading",
}
}},
A2: {
errors: {}
otherData: {}
data: {
"4": {
"name": "Susan",
},
"5": {
"name": "Bee",
},
"6": {
"name": "Bob",
}
}}];
I want it to be:
var object = [
A1: {
errors: {}
otherData: {}
data: {
"3": {
"name": "Reading",
},
"2": {
"name": "Cardiff",
},
"1": {
"name": "Ashley",
}
}},
A2: {
errors: {}
otherData: {}
data: {
"6": {
"name": "Bob",
},
"5": {
"name": "Bee",
},
"4": {
"name": "Susan",
}
}}];
If I understand correctly you want to sort the in alphabetical order by first letter. This is kind of out there but it should do what you're looking for
const arr1 = object.map(function(o) {
return Object.values(o[Object.keys(o)].data).sort((a, b) => a - b);
})
I'll try to explain what's happening here. map is iterating over each object in the array and return a brand new array. o[Object.keys(o)].data is selecting A1 and A2 data keys. Then we're saying we want the values of the objects in those data objects with the surrounds Object.keys() which is giving us the names. From there we're just calling sort and giving it the callback.
const sortByField = (field, isRevered = false, primerFn) => {
if (field) {
var key = primerFn ? (x) => primerFn(x[field]) : (x) => x[field];
isRevered = !isRevered ? 1 : -1;
return (a, b) => {
/*eslint-disable */
return a = key(a), b = key(b), isRevered * ((a > b) - (b > a));
/*eslint-enable */
}
}
else {
return (a, b) => {
return isRevered ? a < b : a > b;
}
}
}
var dataToSort = {
A1: {
errors: {},
otherData: {},
data: {
"1": { "name": "Ashley", },
"2": { "name": "Cardiff", },
"3": { "name": "Reading", }
}
},
A2: {
errors: {},
otherData: {},
data: {
"4": { "name": "Susan", },
"5": { "name": "Bee", },
"6": { "name": "Bob", }
}
}
};
const sortObjectByKeys = (obj) => {
let values = [];
let keys = [];
Object.keys(obj).forEach(key => {
keys.push(key);
values.push(obj[key])
})
values.sort(sortByField("name", true, (value) => value.toLowerCase()));
let sortedObject = {};
values.forEach((value, index) => {
sortedObject[keys[index]] = value;
})
return sortedObject;
}
let sortedData = {};
Object.keys(dataToSort).forEach(dataKey => {
sortedData[dataKey] = {
...dataToSort[dataKey],
data: sortObjectByKeys(dataToSort[dataKey].data)
}
})
console.log(JSON.stringify(sortedData));

Map object values to keys - Javascript

I have an array from an API call.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
I connect to a lot of other API's and they return me a similar response but the keys change. Sometimes it might be
var response = {
"data": {
"data": [{
"values": "Harry",
"index": "name"
}, {
"values": 45,
"index": "score"
}]
}
}
var response = {
"data": {
"data": [{
"4": "Richard",
"index": "name"
}, {
"4": 98,
"index": "score"
}]
}
}
I would like to get an array like this.
[
{
name: 'Arun',
score: 70.78
}
]
This is what I did.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const result = [];
const mappedData = _.map(response.data.data, (item) => {
return {
[item.index]: item[1]
};
});
const resultObject = _.reduce(mappedData, (result, currentObject) => {
for (const key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
result.push(resultObject)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
So instead of hardcoding "1" or "values" in the map function, is there a more universal way to get the key and achieve the same result?
Thanks.
Use reduce rather than map, so you're updating the same object, not creating an array.
And since the property containing the value can vary, I use a loop to look for the first property that isn't named index, and use its value.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item) => {
var value;
// find the property that isn't named "item"
for (var i in item) {
if (i != "index") {
value = item[i];
break;
}
}
acc[item.index] = value;
return acc;
}, {});
console.log(mappedData)
There's no need for lodash for this, the built-in reduce function is fine (but _.reduce will work similarly).
Since you only care about the values of that object and it only has two keys you can do this quite easily in lodash with reduce & fromPairs:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => _.reverse(_.values(o))
const r = _.reduce(response.data.data, (a,c) => _.fromPairs([rv(a), rv(c)]))
console.log(r)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The same thing converted to ES6 would be:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => Object.values(o).reverse() // reverse values
const fp = (arr) => arr.reduce((r, [k,v]) => (r[k] = v, r), {}) // from pairs
const result = response.data.data.reduce((a,c) => fp([rv(a), rv(c)]))
console.log(result)
The main idea here is to first get the object values in an array form, reverse them so the key & value are in the correct order and then reduce that array via from pairs to create the final object.
The main advantage of this approach is that we never deal with the object keys and only focus on the values which is what you really care about. This way the keys can be any value and it would still not matter.
You could try deleting the key-pair index and using the first value of the resulting object:
const mappedData = _.map(response.data.data, (item) => {
var tempObj = Object.assign({}, item)
var index = tempObj.index;
delete tempObj.index;
var otherData = Object.values(tempObj)[0];
return {
[index]: otherData
};
});
Just modified the #barmar approach. I have used Object.keys to get keys from object. This will remove the any hard-coded dependency.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item,i) => {
var key = Object.keys(item);
acc[item[key[1]]] = item[key[0]]
return acc ;
}, {});
console.log(mappedData)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

Single variable to stand in for multiple key names - javascript

I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');

Combine matching object arrays

I am trying do combine the nested objects inside items with the same key.
Find 'top level' values that are duplicated,
Combine the duplicated 'top level' items into one object (including their children.
There should be no duplicate values inside the 'type' arrays
I tried it here https://jsfiddle.net/Lpq6huvw/410/
Input data:
[{
"a": "Mon",
"type": [{
"b": 1
}, {
"b": 3
}]
}, {
"a": "Mon",
"type": [{
"b": 2
}]
}, {
"a": "Tue",
"type": [{
"b": 40
}]
}, {
"a": "Tue",
"type": [{
"b": 50
}]
}, {
"a": "Wed",
"type": [{
"b": 30
}]
}]
Into this array:
[{
"a": "Mon",
"type": [{
"b": 1
}, {
"b": 3
},
{
"b": 2
}]
},
{
"a": "Tue",
"type": [{
"b": 40
},
{
"b": 50
}]
}, {
"a": "Wed",
"type": [{
"b": 30
}]
}]
I attempted this below, which maps all the duplicated items as ONE object. However, I want it to map each under its' 'top level' predecessor.
const z = _.uniqBy(_.filter(data.map(e=>e.a), v => _.filter(data.map(e=>e.a), v1 => v1 === v).length > 1))
const dupes = data.filter(itm => z.includes(itm.a))
const flat = _.flatMap(dupes, item =>
_(item.type)
.map(v => ({b: v.b}))
.value()
)
I personally find Javascript's built in functions work nice, and seem easier to follow than some of lodash functions.
eg.
var data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2},{"b":3}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
var result = data.reduce((acc, val) => {
var found = acc.find((findval) => val.a === findval.a);
if (!found) acc.push(val)
else found.type = found.type.concat(
val.type.filter((f) => !found.type.find((findval) => f.b === findval.b)));
return acc;
}, []);
console.log(result);
Here's a answer w/o lodash:
function combine (input) {
const hash = input.reduce((result, current) => {
if (result[current['a']]) {
result[current['a']] = result[current['a']].concat(current['type'])
} else {
result[current['a']] = current['type']
}
return result
}, {})
return Object.keys(hash).map(key => {
return {
a: key,
type: hash[key]
}
})
}
ES6: you can iterate with Array#reduce, collect the items into a Map, and then convert back to an array with the spread syntax and Map#values:
const data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
const result = [...data.reduce((m, { a, type }) => {
const item = m.get(a) || { a, type: [] }; // use a Set to maintain uniqueness
item.type.push(...type);
return m.set(a, item);
}, new Map).values()]
.map(({ a, type }) => ({ // make types unique again
a,
type: [...type.reduce((m, o) => m.has(o.b) ? m : m.set(o.b, o), new Map).values()]
}));
console.log(result);
Lodash: Use _.groupBy() to get all objects with the same a property in one group. Map the groups, and merge each group using _.mergeWith(), and concat all type arrays.
Make another pass with map to make all items in type arrays unique.
const data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
const result = _(data)
.groupBy('a')
.map((group) => _.mergeWith({}, ...group, ((objValue, srcValue, key) =>
key === 'type' ? (objValue || []).concat(srcValue) : undefined
)))
.map((obj) => Object.assign(obj, { type: _.uniq(obj.type) }))
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Categories