Build multidimensional object from a URL - javascript

I like to create a object that looks like this for the following URL:
faq/jamie/hutber/faq.json
faq/jamie/hutber/faq_sales.json
sales/people/faq_refunds.json
{
faq: {
jamie: {
hutber:[
"faq.json",
"faq_sales.json"
]
}
},
sales: {
people: [
faq_refunds.json
]
}
}
I feel confident to be able to build we'll need some kind of recursion... which I am lacking in.
const data = {}
const list = 'faq/jamie/hutber/faq.json'.split('/').reverse();
list.forEach((cur, index) => {
if(cur.includes('.json')){
data[cur];
} else if(poo[cur]) {
data[cur] = {}
}else{
data[cur] = {}
}
});

var a = ["faq/jamie/hutber/faq.json",
"faq/jamie/hutber/faq_sales.json",
"sales/people/faq_refunds.json"]; //your URLs
var jsonObj = {}; //this json object will store your result
function urlToJson(array, index, jsonObj){ //function that implements your logic
if(index == array.length - 2){
jsonObj[ array[index] ] = jsonObj[ array[index] ] || [];
jsonObj[ array[index] ].push(array[index + 1]);
return;
}
jsonObj[ array[index] ] = jsonObj[ array[index] ] || {};
urlToJson(array, index + 1, jsonObj[ array[index] ]);
}
for(var key in a){
var array = a[key].split("/");
urlToJson(array, 0, jsonObj);
}
console.log(jsonObj);

You can do this in a loop. Note that you won't support folders that contain both folders and files in your current format.
Here's an example that loops over all paths and adds object to the tree. It's a bit ugly, but it should help you write your own function.
const paths = [
"faq/jamie/hutber/faq.json",
"faq/jamie/hutber/faq_sales.json",
"sales/people/faq_refunds.json"
];
const makeTree = (paths, tree = {}) =>
paths.reduce(
(tree, path) => {
const parts = path.split("/");
const folders = parts.slice(0, -2);
const container = parts[parts.length - 2];
const file = parts[parts.length - 1];
let loc = tree;
folders.forEach(f => {
loc[f] = loc[f] || {};
loc = loc[f];
});
loc[container] = loc[container] || [];
loc[container].push(file);
return tree;
},
tree
);
console.log(makeTree(paths));

Related

loop through this Object that contain Array as values and combine all values, sort them, reindex and update values

I have to loop through the object values and sort them in such a way that I remove number 1 & 2 (lets call it index) from Self and Spouse. Then reindex Child3 and Child4 to Child1 and Child2.
Though the Object got from API response looks like below it makes more sense to reindex them as I'll be displaying this information on the screen.
Object looks like below: This is just a sample data. This object is
dynamically created based on User household information
eligibilityMap: {
"CHIP": [
"CHILD3" // should be Child1
],
"APTC/CSR": [
"SELF1", //should be Self
"SPOUSE2", //should be Spouse
"CHILD4" //should be Child2
]
}
My question is how should I loop through this Object and just sort the child and reindex the same?
Expected Result:
newMapping: {
"CHIP": ["Child1"],
"APTC/CSR": ["Self, Spouse and Child2"]
}
CODE:
var sortedMember = Object.keys(eligibilityMap).reduce((acc, key) => {
//fetch the array for the given key
var array = eligibilityMap[key];
console.log('array', array);
var sortedArray = array.sort( function (firstValue, secondValue) {
if (firstValue.indexOf("SELF") >= 0) return -1;
if (secondValue.indexOf("SELF") >= 0) return 1;
if (firstValue.indexOf("SPOUSE") >= 0) return -1;
if (secondValue.indexOf("SPOUSE") >= 0) return 1;
return 0;
});
console.log("sortedArray", sortedArray)
acc[key] = sortedArray;
return acc;
}, {})
$scope.memberString = Object.keys(sortedMember).reduce(function (acc, key) {
var array = sortedMember[key]
var formattedString = array.reduce(function (memberAcc, member, index) {
if (member.indexOf("SELF") >= 0) {
return "Applicant"
}
if (member.indexOf("SPOUSE") >= 0) {
var delimiter = index > 0 ? ", " : "";
return memberAcc + delimiter + "Spouse"
}
if(index === 0) return member;
var delimiter = index === array.length - 1 ? " and " : ", ";
return memberAcc + delimiter + member //CHILD1
}, "");
console.log("STRING", key, formattedString)
acc[key] = formattedString;
return acc;
}, {});
RESULT: (But still not what I wanted)
MEMBERS STRING {CHIP: "CHILD4", APTC/CSR: "Applicant, Spouse, CHILD3 and CHILD5"}
You can check this approach. I've created a dataMapper to map the specific data with their new values.
var obj = {
"CHIP": [
"CHILD3", // should be Child1
],
"APTC/CSR": [
"SELF1", //should be Self
"SPOUSE2", //should be Spouse
"CHILD4" //should be Child2
],
"TEST": [
"SELF1",
"CHILD3"
]
};
// Code to support IE9
var i = 0;
var updatedObj = {};
var data = Object.keys(obj).map(function(key) {
var values = obj[key];
var memberArr = new Array();
var dataObj = values.reduce(function(store, value) {
var storeKey = value.replace(/[0-9]/g, '');
var number = Number(value.replace(/^\D+/g, ''));
if(storeKey !== 'SELF' && storeKey !== 'SPOUSE') {
var arr = new Array();
for(i = 0; i < number; i++) {
arr.push(storeKey);
memberArr.push(storeKey);
}
store[storeKey] = arr;
} else {
var anotherArr = new Array(storeKey);
memberArr.push(storeKey);
store[storeKey] = anotherArr;
}
return store;
}, {});
memberArr.sort(function(a, b) {
return b - a;
})
var sortedMemberArr = memberArr.map(function(member, index) {
return member+''+(index+1);
})
updatedObj[key] = sortedMemberArr;
})
console.log(updatedObj)
/*
// Code Using Modern Syntax
const data = Object.entries(obj).map(([key, value]) => {
const memberCount = value.reduce((store, val) => {
const key = val.replace(/[0-9]/g, '');
const number = +val.replace(/^\D+/g, '');
if(!['SELF', 'SPOUSE'].includes(key)) {
store[key] = Array(number).fill(key);
} else {
store[key] = [key];
}
return store;
}, {})
const sortedMembers = Object.values(memberCount).flat().sort((a, b) => b - a);
const sortedMembersWithNumber = sortedMembers.map((member, index) => `${member}${index+1}`)
return [key, sortedMembersWithNumber]
})
const updatedObj = Object.fromEntries(data);
console.log(updatedObj)
*/

texts in array converts to objects

I am using node to convert an array to object, I have an array looks like this
[
'items[0].book=Book1',
'items[0].color=Red',
'items[0].bookCode=#1',
'items[1].book=Book2',
'items[1].color=Yellow',
'items[1].bookCode=#2',
'items[2].book=Book3',
'items[2].color=Blue',
'items[2].bookCode=#3',
...
]
I am trying to convert it to be objets in one array
items:[
{
book: "Book1",
color: "Red",
bookCode: "#1"
},
{
book: "Book2",
color: "Yellow",
bookCode: "#2"
},
...
]
I found it is easy to conver it uses a 3rd party lib like setKeypath/set,
const obj = {};
const arr = [items......(like above)]
arr.forEach((val => {
if (val.startsWith('items[')) {
const splitWord = item.split('=');
setKeypath(obj, splitWord[0], splitWord[1]);
}
});
I am seeking a way if it can be done the same output with es6, so I don't really need a library. Thanks
const items = [
"items[0].book=Book1",
"items[0].color=Red",
"items[0].bookCode=#1",
"items[1].book=Book2",
"items[1].color=Yellow",
"items[1].bookCode=#2",
"items[2].book=Book3",
"items[2].color=Blue",
"items[2].bookCode=#3"
];
let res = [];
let currId = "";
let currItem = null;
for (let i = 0; i < items.length; i++) {
let parts = items[i].split(".");
if (currId!==parts[0] && currItem) { //new item
res.push(currItem)
currId = parts[0];
}
if (!currItem)
currItem = {};
let keyValue = parts[1].split("=");
currItem[keyValue[0]] = keyValue[1]
}
console.log({items: res})
You may first find all values by regex, and insert the attribute to each corresponding element one by one. This approach works for whatever ordering the array is, and whatever attributes there are, as long as each element follow the same pattern.
let items = [
"items[1].bookCode=#2",
"items[0].book=Book1",
"items[0].bookCode=#1",
"items[1].book=Book2",
"items[2].bookCode=#3",
"items[1].color=Yellow",
"items[2].book=Book3",
"items[2].color=Blue",
"items[0].color=Red",
"items[4].test=test!"
];
let indexPattern = /\[(\d*)\]/;
let attrPattern = /\.(.*)=/;
let valuePattern = /=(.*)/;
let obj = Object.values(
items.reduce((obj, element) => {
let index = element.match(indexPattern)[1];
let attr = element.match(attrPattern)[1];
let value = element.match(valuePattern)[1];
if (!obj.hasOwnProperty(index)) obj[index] = {};
obj[index][attr] = value;
return obj;
}, {})
);
console.log(obj);
[
'items[0].book=Book1',
'items[0].color=Red',
'items[0].bookCode=#1',
'items[1].book=Book2',
'items[1].color=Yellow',
'items[1].bookCode=#2',
'items[2].book=Book3',
'items[2].color=Blue',
'items[2].bookCode=#3',
].reduce((acc, str) => {
const index = Number(str.slice(str.indexOf('[') + 1, str.indexOf(']')));
if (!acc[index]) {
acc[index] = {};
}
const entry = [str.slice(str.indexOf('.') + 1, str.indexOf('=')), str.slice(str.indexOf('=') + 1)];
acc[index][entry[0]] = entry[1];
return acc;
}, []);
Here I pick apart the string you're given based on the consistent format, grab the index, key, and value, and then just use Array#reduce to do the work of putting the array together.
Documentation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
I think a smattering of regex would do the trick:
const ar = [
'items[0].book=Book1',
'items[0].color=Red',
'items[0].bookCode=#1',
'items[1].book=Book2',
'items[1].color=Yellow',
'items[1].bookCode=#2',
'items[2].book=Book3',
'items[2].color=Blue',
'items[2].bookCode=#3'
]
const result = [];
ar.forEach(item => {
const index = parseInt(item.match(/\[([0-9]+)\]/)[1]);
const params = item.split(".")[1].split("=");
if(!result[index])
result[index] = {}
result[index][params[0]] = params[1];
})
console.log(result)
Note that item.match(/\[([0-9]+)\]/) matches the number inside your brackets. match returns an array where 1 is the index of the actual value between the brackets.

Create object tree from URLs

I have a array of URLs like this:
const urls = [
'src/components/index.js',
'src/components/style.css',
'readme.txt',
'photos/something/somwhere/why.jpg',
'places/something/home,
];
And I want to have output like this:
{
'src': {
'components': {
'index.js': true,
'style.css': true
}
},
'readme.txt': true,
'photos': {
'something': {
'somewhere': {
'why.jpg'
}
'home': {}
}
}
Where the files in the end should have value true and the empty folders should have an empty object as a value.
So far I tried to do it with nested loops but it always fails. The best option that I had this far was this but it's not complete.
const result = {};
const urls = [
'src/components/index.js',
'src/components/style.css',
'readme.txt',
'photos/something/somwhere/why.jpg',
'places/something/home',
];
const urlArr = [];
urls.forEach(url => {
const items = url.split('/');
urlArr.push(items);
});
for (let i = 0; i < urlArr.length; i++) {
for (let j = 0; j < urlArr[i].length; j++) {
let item = urlArr[i][j];
}
}
console.log(urlArr);
If it's possible to do with outer way it would be great too (say recursion or other stuff).
Checks for '.' to decide if file. Note that this is not a reliable check. This looks like part of a build pipeline, so there should already be a way to determine files from folders.
You don't need a recursive function. The path can be iterated over without branching:
splits the urls by '/' to get the paths to traverse (and gets the base filename)
Traverses into the object and sets the values, creating objects if path doesn't exist yet.
const urls = [
'src/components/index.js',
'src/components/style.css',
'readme.txt',
'photos/something/somwhere/why.jpg',
'places/something/home',
];
const root = {}
for(const url of urls) {
let ptr = root
const stack = url.split('/'), basename = stack.pop()
for(const p of stack) ptr = ptr[p] = ptr[p] || {}
ptr[basename] = /\./.test(basename) || {}
}
console.log(root)
You can do this in a lot of different ways, however this is how I would do it.
H & T stands for Head & Tail and are standard names for when designing recursive function that operate over lists.
const urls = [
'src/components/index.js',
'src/components/style.css',
'readme.txt',
'photos/something/somwhere/why.jpg',
'places/something/home',
];
const recursiveAssign = ([H, ...T], target) => {
if (H.indexOf('.') !== -1) {
target[H] = true;
return;
}
target[H] = target[H] || {};
if (T.length > 0) recursiveAssign(T, target[H]);
}
const createTree = (paths) => {
const acc = {};
for (let path of paths) {
recursiveAssign(path.split('/'), acc);
}
return acc;
}
console.log(createTree(urls));
Or if you prefer a less verbose way of write the same solution.
const urls = [
'src/components/index.js',
'src/components/style.css',
'readme.txt',
'photos/something/somwhere/why.jpg',
'places/something/home',
]
const recursiveAssign = ([H, ...T], target) =>
H.indexOf('.') !== -1
? target[H] = true
: T.length > 0
? recursiveAssign(T, target[H] = target[H] || {})
: target[H] = {}
const createTree = ([H, ...T], acc = {}) => (
recursiveAssign(H.split('/'), acc),
T.length > 0
? createTree(T, acc)
: acc
)
console.log(createTree(urls))

Remove Duplicate Object from JSON Array

I am trying to remove duplicate JSON Objects from the array in ServiceNow.
Tried below code but it does not remove the duplicate. I want to compare both name & city.
var arr1 = '[{"name":"Pune","city":"India"},{"name":"Pune","city":"India"}]';
var splitlen = JSON.parse(arr1);
alert(splitlen.length);
var uniqueArray = [];
var uniqueJson = {};
for(i=0;i<splitlen.length;i++)
{
if(uniqueArray.indexOf(splitlen[i].name)==-1)
{
uniqueArray.push(splitlen[i]);
}
}
alert(JSON.stringify(uniqueArray));
Expected output :
[{"name":"Pune","city":"India"}]
uniqueArray.indexOf doesn't work because you're comparing objects against strings (splitlen[i].name). Try to use .find() instead:
var arr1 = '[{"name":"Pune","city":"India"},{"name":"Pune","city":"India"}]';
var splitlen = JSON.parse(arr1);
var uniqueArray = [];
var uniqueJson = {};
for(i=0;i<splitlen.length;i++)
{
if(!uniqueArray.find(x => x.name === splitlen[i].name))
{
uniqueArray.push(splitlen[i]);
}
}
console.log(uniqueArray);
or
var arr1 = '[{"name":"Pune","city":"India"},{"name":"Pune","city":"India"}]';
var splitlen = JSON.parse(arr1);
function compare(x){
return x.name === splitlen[i].name;
}
var uniqueArray = [];
var uniqueJson = {};
for(i=0;i<splitlen.length;i++)
{
if(!uniqueArray.find(compare))
{
uniqueArray.push(splitlen[i]);
}
}
console.log(uniqueArray);
you can try this. Also one more thing your array declaration is not right, remove single quotes from array.
var arr1 = [{"name":"Pune","city":"India"},{"name":"Pune","city":"India"}];
function getUniqueListByKey(arr, key) {
return [...new Map(arr.map(item => [item[key], item])).values()]
}
var arr2 = getUniqueListByKey(arr1, "name")
console.log(arr2);
Please try the following example
const arr1 = '[{"name":"Pune","city":"India"},{"name":"Pune","city":"India"}]';
const splitlen = JSON.parse(arr1);
const output = splitlen.reduce((previousValue, currentValue) => {
const { name, city } = currentValue;
const index = previousValue.findIndex(
(entry) => entry.name === name && entry.city === city
);
if (index === -1) {
return [...previousValue, currentValue];
}
return previousValue;
}, []);
console.log(output);
See
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
Put the records in a hashset. If there is collision in the hashset, there is duplicate. This approach is O(n) while comparing all pairs is $O(n^2)$.
I'm trying to get an answer, here's my idea:
Create a function to compare two objects then create a function to get the unique value
function isEquals(obj1, obj2) {
const aProps = Object.getOwnPropertyNames(obj1);
const bProps = Object.getOwnPropertyNames(obj2);
if (aProps.length !== bProps.length) {
return false;
}
for (let j = 0; j < aProps.length; j++) {
const propName = aProps[j];
if (JSON.stringify(obj1[propName]) !== JSON.stringify(obj2[propName])) {
return false;
}
} return true;
}
function getUnique(arr) {
var uniqueArray = [];
for (var item of arr) {
const uniqueItems = arr.filter(i => isEquals(item, i));
if (uniqueItems.length !== 0) {
uniqueArray.push(Object.assign({}, uniqueItems.shift()));
}
arr = arr.filter(i => !isEquals(item, i));
}
return uniqueArray;
}
Hope it helps!

Combine elements of an array into a sub array if they are the same

Trying to find an algorithm that will do the following:
let input = [ 'kooty', 'dlnnoo', 'emor', 'dlnnoo', 'kooty', 'aiprs' ]
function combine(input){
// you should return
[ ['kooty', 'kooty'], ['dlnnoo','dlnnoo'], ['emor'], ['aiprs'] ]
}
I got the answer by using Lodash but i was wondering if there was a way without
function combine(input){
let sortedCity = [];
let finalArr = [];
for(let city in input){
sortedCity.push(input[city].toLowerCase().split('').sort().join(''));
}
let a = lodash.groupBy(sortedCity)
return Object.values(a)
}
combine(input)
let input = [ 'kooty', 'dlnnoo', 'emor', 'dlnnoo', 'kooty', 'aiprs' ]
function combine (input) {
let sorted = input.map(cur => cur.toLowerCase().split('').sort().join(''))
let cache = {} // cache value to index of result array
return sorted.reduce((sum, cur) => {
let index = cache[cur]
if (index !== undefined) {
sum[index].push(cur)
} else {
sum.push([cur])
cache[cur] = sum.length - 1
}
return sum
}, [])
}
combine(input)

Categories