Related
I have treeFlatNode array i want to structure it in tree format. or can i display this array in tree directly in angular.
data=[
{
expandable: true
level: 0
name: "2021-12-31"
path: null
},
{
expandable: false
level: 2
name: "A.txt"
path: "2021-12-31/B/C/A.txt"
}
]
required format
tree=[
name:"2021-12-03",
children:[
name:"B",
children:[{
name:"C"
children:[{
name:"A.txt"
children:[]
}]
}]
]
]
You could use an object (map) that maps a (sub)path to a node in the final tree. If it doesn't exist yet, it is added to the parent's children.
As your tree structure actually represents a forest (there can be multiple roots), I would name the result variable forest instead of tree
Snippet:
function toForest(data) {
const roots = [];
const map = {};
for (const obj of data) {
let key = "";
let children = roots;
for (const name of (obj.path ?? obj.name).split("/")) {
let child = map[key += "/" + name];
if (!child) children.push(map[key] = child = { name, children: [] });
({children} = child);
}
}
return roots;
}
// Example run
let data = [{expandable: true,level: 0,name: "2021-12-31",path: null}, {expandable: false,level: 2,name: "A.txt",path: "2021-12-31/B/C/A.txt"}];
let forest = toForest(data);
console.log(forest);
So, to transform your data structure to the desired one, you can use following function (with comments =) ):
transform(data){
const tree = [];
for (let node of data) {
// If there's no path it's a parent node
// but add it only if it doesn't exist yet
if (node.path === null && tree.every(n => n.name !== node.name)) {
tree.push({ name: node.name, children: [] });
continue;
}
// Extract name of parent node and other nodes
const [parentNodeName, ...pathElems]: string[] = node.path.split('/');
// Look-up for the parent node
let parentNode = tree.find(t => t.name === parentNodeName);
// If parent doesn't exist yet, so we create it here
if (!parentNode) {
parentNode = { name: parentNodeName, children: [] }
}
let children = parentNode.children;
// If the level of the node is relevant
// otherwise simply iterate over all pathElems
for(let i = 0; i <= node.level; i ++) {
let child = children.find(c => c.name === pathElems[i]);
// If the child doesn't exist yet - create it
if (!child) {
child = {
name: pathElems[i],
children: []
}
children.push(child);
children = child.children;
continue;
}
// Child does exist, so use it's children for the next iteration
children = child.children;
}
}
return tree;
}
And you can call this function, for example, in ngOnInit:
ngOnInit() {
this.tree = this.transform(this.data);
}
I do not use Angular, but if you just need to convert your flat to nested:
var data = [
{
expandable: true,
level: 0,
name: "2021-12-31",
path: null
},
{
expandable: false,
level: 2,
name: "A.txt",
path: "2021-12-31/B/C/A.txt"
}
]
var to_nested = function(flat) {
var nested = []
var cache = {}
var l = flat.length
var cache_assert = function(name) {
if (cache[name] == null) {
cache[name] = {
name: name,
children: []
}
}
}
for (var i = 0; i < l; i++) {
var current_node = flat[i]
cache_assert(current_node.name)
if (current_node.path == null) {
nested.push(cache[current_node.name])
} else {
var names = current_node.path.split("/")
var parent_name = names.shift()
cache_assert(parent_name)
names.forEach(function(name) {
cache_assert(name)
cache[parent_name].children.push(cache[name])
parent_name = name
})
}
}
return nested
}
var a = to_nested(data)
console.log('a: ', a)
console.log('a: ', a[0].children)
console.log('a: ', a[0].children[0].children)
And if you want to return to flat:
var level = 0
var cache = []; cache[level] = a.slice(0)
var parent = []; parent[level] = null
var index = []; index[level] = 0
while (level >= 0) {
var node = cache[level][index[level]]
if (node != null) {
console.log('node: ', node)
if (
node['children'] != null &&
Object.prototype.toString.call(node['children']) === '[object Array]' &&
node['children'].length
) {
level++
index[level] = 0
parent[level] = Object.assign({}, node)
delete parent[level]['children']
cache[level] = node['children'].slice(0)
} else {
index[level]++
}
} else {
parent[level] = null
level--
index[level]++
}
}
How to create single array for same key but different values in nodeJs with unique productId
but having different productImage with same productId i want productImage should be an array
and with same productId, productImages are in this productImage array.
var data = [
{
"productID":18,
"productTitle":"Watch",
"productImage":"1588148225540.jpg"
},
{
"productID":18,
"productTitle":"Watch",
"productImage":"15881482433232.jpg"
},
{
"productID":19,
"productTitle":"Shirt",
"productImage":"1588148214343.jpg"
}
]
My expected output should be:
[
{
"productID":18,
"productTitle":"Watch",
"productImage":[
"1588148225540.jpg",
"15881482433232.jpg"
]
},
{
"productID":19,
"productTitle":"Shirt",
"productImage":[
"1588148214343.jpg"
]
}
]
You can use uniqBy function from lodash library
const result = _.uniqBy(products, 'productID');
Here is an answer
var data = [
{
"productID":18,
"productTitle":"Watch",
"productImage":"1588148225540.jpg"
},
{
"productID":18,
"productTitle":"Watch",
"productImage":"15881482433232.jpg"
},
{
"productID":19,
"productTitle":"Shirt",
"productImage":"1588148214343.jpg"
}
]
let output =[];
data.forEach(function(item) {
var existing = output.filter(function(v, i) {
return v.productID == item.productID;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
output[existingIndex].productImage =
output[existingIndex].productImage.concat(item.productImage);
} else {
if (typeof item.productImage == 'string')
item.productImage = item.productImage;
item.productThumbImage = [item.productThumbImage];
output.push(item);
}
});
I have a javascript object structured like this;
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
Given an array of keys ['brand', 'group', 'newGroup', 'newSubGroup'] I want to split the keys into found and missing keys. So for the structure above I should get back;
present = ['brand', 'group']
missing = ['newGroup', 'newSubGroup']
I'm using ES6 and have lodash available, but struggling to find a clean way to produce this.
This is not to just check existence, it's recursively find the keys and return those present and the remaining ones.
Here's a pretty sketchy way that works.
const find = (keys, obj) => {
const string = JSON.stringify(obj);
return keys.reduce(({ present, missing }, key) => {
const match = string.match(new RegExp(`"${key}":`));
if (match) {
present.push(key);
} else {
missing.push(key);
}
return { present, missing };
}, { present: [], missing: [] });
}
You can use this function made for you ;)
var getAttrs = function(obj) {
return [].concat.apply([], Object.keys(obj).map(function (key) {
var results = [key]
if (typeof obj[key] === 'object') {
Array.prototype.push.apply(results, getAttrs(obj[key]))
}
return results
}))
}
It return the list of properties and children properties.
getAttrs({brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}})
> ["brand", "group", "subGroup", "items", "otherSub", "items"]
And you can use it like so:
var lookingFor = ['brand', 'group', 'newGroup', 'newSubGroup']
var existings = getAttrs(obj)
var missings = []
var presents = []
lookingFor.forEach(attr => {
if (existings.indexOf(attr) === -1) {
missings.push(attr)
} else {
presents.push(attr)
}
})
I wrote a function to recursively get unique keys from a nested object, then filtered the array of all the keys you mentioned checking which were present in the result of my function.
var thisObject = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
};
var arr_full = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
var key_array = [];
function addToKeyArray( key_array, object ){
for( var key in object ){
// only get unique keys
if( key_array.indexOf( key ) === -1 ){
key_array.push( key );
}
// concat the result of calling this function recurrsively on object[key]
key_array.concat( addToKeyArray( key_array, object[key] ) );
}
return key_array;
}
var test = addToKeyArray( [], thisObject );
var missing = arr_full.filter( function( el ) {
return test.indexOf( el ) < 0;
});
console.log( test );
console.log( missing )
You can create recursive function using for...in loop inside another function and return object as result..
var obj = {"brand":{"group":{"subGroup":{"items":[]},"otherSub":{"items":[]}}}}
var keys = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
function findKeys(data, keys) {
keys = keys.slice();
function findPresent(data, keys) {
var result = []
for(var i in data) {
if(typeof data[i] == 'object') result.push(...findPresent(data[i], keys))
var index = keys.indexOf(i);
if(index != -1) result.push(...keys.splice(index, 1))
}
return result
}
return {present: findPresent(data, keys), missing: keys}
}
console.log(findKeys(obj, keys))
To keep things clean and readable you can use "for in", inside a nested function for your recursion.
function recur(obj) {
let preMiss = {
present: [],
missing: []
}
let root = traverse => {
for (let key in traverse) {
if (Array.isArray(traverse[key].items)) {
preMiss.missing.push(key);
}
if (typeof traverse[key] === 'object' && !Array.isArray(traverse[key].items)) {
preMiss.present.push(key);
root(traverse[key])
}
}
}
root(obj);
return preMiss;
}
const object = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
console.log(Object.entries(recur(object)));
var toFind = ['brand', 'group', 'newGroup', 'newSubGroup'],
found = [];
var o = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
//called with every property and its value
function process(key,value) {
var i = toFind.indexOf(key);
if(i !== -1){
found.push(key);
toFind.splice(i, 1);
}
}
function traverse(o,func) {
if(!toFind.length) return;
for (var i in o) {
func.apply(this,[i,o[i]]);
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
traverse(o[i],func);
}
}
}
traverse(o,process);
console.log(found); // present
console.log(toFind); // absent
Traverse method taken from https://stackoverflow.com/a/722732/1335165
Even though this question is a bit older, I want to present a rather short solution to the problem.
const recursivelyGetKeys = obj => Object.keys(obj).map(key => typeof obj[key] === 'object'
? [...recursivelyGetKeys(obj[key]), key] : [key]).reduce((p, c) => [...p, ...c], [])
This function will return all keys in the object, so a call to the array arr with
const arr = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
will output:
const keys = recursivelyGetKeys(arr) // = ["items", "subGroup", "items", "otherSub", "group", "brand"]
Now to find the intersection set of this and find = ['brand', 'group', 'newGroup', 'newSubGroup'], do:
const found = keys.filter(key => find.some(val === key))
const missing = keys.filter(key => find.every(val !== key))
This is my first question on here. Doesn't appear to be asked elsewhere, but then again I'm not sure exactly how to phrase my question.
How can I transform an array that looks like this:
var message = {
pay_key: '12345',
'transaction[0].sender_id': 'abc',
'transaction[0].is_primary_receiver': 'false',
'transaction[0].id': 'def',
'transaction[1].sender_id': 'xyz',
'transaction[1].is_primary_receiver': 'false',
'transaction[1].id': 'tuv',
};
into something like this:
{
pay_key : '12345',
transaction : [
{
sender_id : 'abc',
is_primary_receiver : 'false',
id : 'def'
},
{
sender_id : 'xyz',
is_primary_receiver : 'false',
id : 'tuv'
}
]
}
I have no control over the format of the first object as it comes from an external service. I am trying to insert the message object into a MongoDB collection, but when I try to do an insert as-is, I get an error. So I'm trying to put it into the correct form.
Should I be using Underscore for this? I've played around with _.each but can't get it to work.
my take..
var message = {
pay_key: '12345',
'transaction[0].sender_id': 'abc',
'transaction[0].is_primary_receiver': 'false',
'transaction[0].id': 'def',
'transaction[1].sender_id': 'xyz',
'transaction[1].is_primary_receiver': 'false',
'transaction[1].id': 'tuv',
};
message.transaction=[];
for (var p in message) {
var m = p.match(/^transaction\[(\d+)\]\.(.*)/);
if (m&&m[1]&&m[2]) {
message.transaction[m[1]]=message.transaction[m[1]]||{};
message.transaction[m[1]][m[2]]=message[p];
delete message[p];
}
}
Here's a generic function I just whipped up
function makeObject(message) {
var retObj = {},
makePath = function (p, pos) {
if (/\[\d+\]$/.test(p)) {
var q = p.split(/[\[\]]/),
r = q[0],
s = q[1];
if (!pos[r]) {
pos[r] = [];
}
return pos[r][s] = pos[r][s] || {};
}
return pos[p] = pos[p] || {};
};
for(var k in message) {
if (message.hasOwnProperty(k)) {
if (k.indexOf('.') < 0) {
retObj[k] = message[k];
}
else {
var path = k.split('.'),
pos = retObj,
last = path.pop();
path.forEach(function(p) {
pos = makePath(p, pos);
});
pos[last] = message[k];
}
}
}
return retObj;
}
It works as required, but I'm sure there's some better code to do it
Had a similar response, so adding it anyway:
Object.keys(message).forEach(function(key) {
var keySplit = key.split( /\[|\]\./g )
if ( keySplit.length != 1 ) {
if ( !message.hasOwnProperty(keySplit[0]) )
message[keySplit[0]] = [];
message[keySplit[0]][keySplit[1]] = message[keySplit[0]][keySplit[1]]||{};
message[keySplit[0]][keySplit[1]][keySplit[2]] = message[key];
delete message[key];
}
});
I have a backbone.js model similar to the one shown below.
Filters = Backbone.Model.extend({
defaults : {
title: [ ["title1", "easy"], ["title2", "hard"] ]
}
});
I'm trying to add an element to the first-level array, such that the model then becomes:
Filters = Backbone.Model.extend({
defaults : {
title: [ ["title1", "easy"], ["title2", "hard"], ["title3", "medium"] ]
}
});
The code I have right now is this:
function setFilters() {
var options = {};
for (var facet in facets) {
for (var facetKey in facets[facet]) {
if (!filterExists(facetKey)) {
options[facetKey] = new Array(new Array(facets[facet][facetKey], "equals"));
}
else {
(filters[facetKey]).push(new Array(facets[facet][facetKey], "equals"));
}
}
}
filters.set(options);
}
The function filterExists simply checks if the key "title" is present in the model. When I run this, it says that filters[facetKey] is undefined. But isn't this the first-level array I need to push my element into?
You can access model attributes with .get() and .set() functions, or directly via the .attributes property:
http://documentcloud.github.com/backbone/#Model-attributes
var filters = new Filters();
filters.attributes.facetKey.push( [...] );
OR
filters.set('facetKey', ( filters.get('facetKey') || []).concat([...]));
Anyway, here is your transformed function which may or may not work:
function setFilters() {
for (var facet in facets) {
for (var facetKey in facets[facet]) {
var f = [ facets[facet][facetKey], "equals" ];
if( filterExists(facetKey)) {
// OR: if( filters.attributes[ facetKey ]){
filters.attributes[ facetKey ].push( f );
}else{
filters.attributes[ facetKey ] = [ f ];
}
}
}
// trigger change event for all attributes
filters.set( filters.attributes );
}
Bonus:
(filters.attributes[ facetKey ] = filters.attributes[ facetKey ] || [] ).push(f);