Return object comparing input with nested objects - javascript

I am having an object that looks like this:
const obj = {
"cat1" : {
id: "1",
name: "Category1",
tiles: [{ tileName: "abc", searchable: true}, { tileName: "def", searchable: true}]
},
"cat2" : {
id: "2",
name: "Category2",
tiles: [{ tileName: "ab", searchable: true}, { tileName: "lmn", searchable: true}]
},
"cat3" : {
id: "3",
name: "Category3",
tiles: [{ tileName: "pqr", searchable: true}, { tileName: "", searchable: false}]
}
}
Based on the search input , I need to check if the search item is included in each object basically inside two fields. One is name and the other is tileName inside tile array ( should be searched only if that object has searchable true ). It should be searched across name and tiles array
When search is "ab", the output should be
const obj = {
"cat1" : {
id: "1",
name: "Category1",
tiles: [{ tileName: "abc", searchable: true}]
},
"cat2" : {
id: "2",
name: "Category2",
tiles: [{ tileName: "ab", searchable: true}]
},
}
Code that I tried
function handleSearch(search)
{
return Object.values(obj).map((item) => {
if(item["name"].toLowerCase().includes(item.toLowerCase()))
return item;
})
})
}

I would personally create a generic filterMap() helper that combines the functionalities of both filter() and map()
// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
// filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
// //=> [1,9,25]
//
function filterMap(iterable, fn) {
const filterMapped = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
With the above helper defined you can get the desired functionality with relative ease
function search(searchString, object) {
const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());
return Object.fromEntries(
filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
// If the category name matches, return the whole category as is,
// without filtering the tiles.
if (searchIn(category.name, searchString)) {
return [key, { ...category, tiles }];
}
// If the category name did not match, filter the tiles.
const matchingTiles = tiles.filter((tile) => (
tile.searchable && searchIn(tile.tileName, searchString)
));
// If there are one or more matching tiles found, return the
// category with only the matching tiles.
if (matchingTiles.length) {
return [key, { ...category, tiles: matchingTiles }];
}
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
})
);
}
function search(searchString, object) {
const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());
return Object.fromEntries(
filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
// If the category name matches, return the whole category as is,
// without filtering the tiles.
if (searchIn(category.name, searchString)) {
return [key, { ...category, tiles }];
}
// If the category name did not match, filter the tiles.
const matchingTiles = tiles.filter((tile) => (
tile.searchable && searchIn(tile.tileName, searchString)
));
// If there are one or more matching tiles found, return the
// category with only the matching tiles.
if (matchingTiles.length) {
return [key, { ...category, tiles: matchingTiles }];
}
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
})
);
}
const obj = {
"cat1": {
id: "1",
name: "Category1",
tiles: [
{ tileName: "abc", searchable: true },
{ tileName: "def", searchable: true },
],
},
"cat2": {
id: "2",
name: "Category2",
tiles: [
{ tileName: "ab", searchable: true },
{ tileName: "lmn", searchable: true },
],
},
"cat3": {
id: "3",
name: "Category3",
tiles: [
{ tileName: "pqr", searchable: true },
{ tileName: "", searchable: false },
],
},
};
console.log('search "ab"', search("ab", obj));
console.log('search "3"', search("3", obj));
// helper
// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
// filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
// //=> [1,9,25]
//
function filterMap(iterable, fn) {
const filterMapped = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
In the above code we use Object.entries() to convert the object to an array. The array is then transformed using our newly defined filterMap() method. Finally the resulting array is transformed back into an object using Object.fromEntries().
The code makes use of destructuring, the spread syntax in object literals, and the property definition shorthand.
For those using TypeScript, filterMap() should be defined like:
function filterMap<A, B>(iterable: Iterable<A>, fn: (item: A) => undefined | B): B[] {
const filterMapped: B[] = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
The answer might still leave you with:
Not all code paths return a value. (7030)
In which case you must either explicitly return from the callback function.
// ...
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
return; // <- explicit return at the end
})
Or alternatively set "noImplicitReturns": false in your TypeScript settings, to allow implicit returns.

Related

Transform dot notation to a tree data form

I have object oriented data in the form:
var alist = [
'foo',
'foo.lol1',
'foo.lol2',
'bar.lol1',
'bar.barbar.kk',
...
]
which I would like to transform into a tree structure, to be able to serve them with a tree component (https://github.com/vinz3872/vuejs-tree in particular). The require form is the following:
var ok = [
{
text: "foo",
state: { expanded: false },
nodes: [
{
id: 1,
path: "foo.lol1",
text: "lol1",
checkable: true,
state: { checked: false },
},
{
id: 2,
path: "foo.lol2",
text: "lol2",
checkable: true,
state: { checked: false },
},
]
},
{
text: "bar",
state: { expanded: false },
nodes: [
{
id: 3,
path: "bar.lol1",
text: "lol1",
checkable: true,
state: { checked: false },
},
]
},
{
text: "bar",
state: { expanded: false },
nodes: [
{
id: 3,
path: "bar.lol1",
text: "lol1",
checkable: true,
state: { checked: false },
},
{
text: "barbar",
state: { expanded: false },
nodes: [
{
id: 4,
path: "bar.barbar.kk",
text: "kk",
checkable: true,
state: { checked: false },
},
]
},
]
}
]
I am aware that I should use recursion and I have tried all relevan posts in stackoverflow, i.e. How to build a JSON tree structure using object dot notation.
My main problem is that I have to somehow preserve the information of the full path to the leaves of the tree. As a newbie in js I lost myself in counters and callback for days without any luck.
I would appreciate your help.
Thank you in advance
Basically you could use forEach then split each string into array and then use reduce on that. Then you build nested object where the keys are current paths and also ad to result array.
var alist = [
'foo',
'foo.lol1',
'foo.lol2',
'bar.lol1',
'bar.barbar.kk',
]
const result = []
const levels = {
result
}
let prev = ''
let id = 1
alist.forEach(str => {
str.split('.').reduce((r, text, i, arr) => {
const path = prev += (prev.length ? '.' : '') + text
if (!r[path]) {
r[path] = {result: []}
const obj = {
id: id++,
text,
}
if (i === 0) {
obj.state = {expanded: false}
} else {
obj.state = {checked: false}
obj.checkable = true
obj.path = path
}
obj.nodes = r[path].result
r.result.push(obj)
}
if (i === arr.length - 1) {
prev = ''
}
return r[path]
}, levels)
})
console.log(result)
I found that it was easiest to do this transformation in two steps. The first converts your input into this format:
{
foo: {
lol1: {},
lol2: {}
},
bar: {
barbar: {
kk: {}
},
lol1: {}
},
}
The second uses just this format to create your desired structure. This has two advantages. First, I have tools lying around that make it easy to create this structure from your input. Second, this structure embeds enough information to create your output, with only one branching construct: whether the value at a path is an empty object or has properties. This makes the generation code relatively simple:
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const reformat = (o, path = [], nextId = ((id) => () => String (++ id)) (0)) =>
Object .entries (o) .map (([k, v]) => Object .entries (v) .length > 0
? {text: k, state: {exapanded: false}, nodes: reformat (v, [...path, k], nextId)}
: {id: nextId (), path: [...path, k] .join('.'), text: k, checkable: false, state: {checked: false}}
)
const transform = (pathTokens) =>
reformat (pathTokens
.map (s => s .split ('.'))
.reduce ((a, path) => setPath (path) ({}) (a), {})
)
const alist = ['foo', 'foo.lol1', 'foo.lol2', 'bar.lol1', 'bar.barbar.kk']
console .log (transform (alist))
.as-console-wrapper {max-height: 100% !important; top: 0}
We start with setPath, which takes a path, in a format such as ['bar', 'barbar', 'kk'], the value to set at that path, and an object to shallow clone with this new property along that path. Thus setPath (['foo', 'bar', 'baz']) (42) ({foo: {qux: 7}, corge: 6}) yields {foo: {qux: 7, bar: {baz: 42}}, corge: 6}. (There's a little more in this reusable function to also handle array indices instead of string object paths, but we can't reach that from this input format.)
Then we have reformat, which does the format conversion. It simply builds a different input object based upon whether the input value is an empty object.
Finally, transform maps a splitting function over your input array to get the path structure needed for setPath, folds the results into an initially empty object by setting every path value to an empty object, yielding our intermediate format, which we then pas to reformat.
There is one thing I really don't like here, and that is the nextId function, which is a stateful function. We could just have easily used a generator function, but whatever we do here, we're using state to build this output and that bothers me. If someone has a cleaner suggestion for this, I'd love to hear it.

How to map or assign an entry to an array-item based on some of this item's conditions?

I have array of objects,
if the name is xx then push xitems to that object and
if the name is yy then push yitems to that object
Below is the code tried , and also should not use spread operator
const result = [];
var ss=arrobj.forEach(function(e){
if(e.name === 'xx'){
result.push({id: e.id, name: e.name, country:e.country, others: xitems})
}
if(e.name === 'yy'){
result.push({id: e.id, name: e.name, country:e.country, others: yitems})
}
return result;
});
var arrobj =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
]
xitems =[
{title: "Finance", valid: true}
]
yitems =[
{title: "Sales", valid: true}
]
Expected Output
[
{id:1, name: "xx", country: "IN",
others:[
{title: "Finance", valid: true}
]
},
{id:2, name: "yy", country: "MY",
others: [
{title: "Sales", valid: true}
]
},
]
You should use .map for this.
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj.map((item) => {
if (item.name === "xx") {
item.others = xitems;
} else if (item.name === "yy") {
item.others = yitems;
}
return item;
});
console.log(result);
Your code works, the only issue that I identified are.
There is no need to assign var ss with arrobj.forEach. Because Array.forEach donot return a value.
No need of return result; inside Array.forEach.
Also as an improvement you can simply assign the object with key others like Object.assign({}, e, { others: xitems }), rather than returning individual key value.
Working Fiddle
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
]
const xitems = [
{ title: "Finance", valid: true }
]
const yitems = [
{ title: "Sales", valid: true }
]
const result = [];
arrobj.forEach(function (e) {
if (e.name === 'xx') {
result.push(Object.assign({}, e, { others: xitems }))
}
if (e.name === 'yy') {
result.push(Object.assign({}, e, { others: yitems }))
}
});
console.log(result)
Variables are references to an object that has a value, variables do not store values. It is pointless to try to use a variable in that manner unless you have specific parameters. If you insist on a condition then you need to identify xitems and yitems by the objects values and/or properties or by the order they came in. If you have dynamic data how would you know what xitems or yitems really is?
The example below has been made reusable as long as you meet these requirements:
Must have an array of objects as a primary parameter.
Must have at least one array of objects for each object in the primary array. If there's more the rest will be ignored.
The secondary array of objects must be in the order you want then to end up as.
The second parameter is a rest parameter (not a spread operator, although I have no idea why OP does not want to use it). This will allow us to stuff in as many object arrays as we want.
const distOther = (main, ...oAs) => {...
Next we create an array of pairs from all of the secondary arrays
let others = oAs.map(sub => ['others', sub]);
// [['others', [{...}]], [['others', [{...}]], ...]
Then we turn our attention to the primary array. We'll work our way from the inside out. .map() each object as an array of pairs by Object.entries():
main.map((obj, idx) =>
// ...
Object.entries(obj)
// ...
// [{A: 1, B: 2}, {...}] => [[['A', 1], ['B', 2]], [[...], [...]]]
Then .concat() (a spead operator would be more succinct) each array of pairs with that of the secondary array of pairs corresponding to the current index (you'll need to wrap each secondary array in another array, so the return will level off correctly):
// main.map((obj, idx) =>
// ...
// Object.entries(obj)
.concat([others[idx]])));
// [[['A', 1], ['B', 2], ['others', [{...}]], [[...], [...], ['others', [{...}]]]
Finally we'll use Object.fromEntries() to convert each array of pairs into an object.
// main.map((obj, idx) =>
Object.fromEntries(
// Object.entries(obj)
// .concat([others[idx]])));
// [{'A': 1, 'B': 2, 'others': [{...}]},...]
const objArr =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
];
const x =[
{title: "Finance", valid: true}
]
const y =[
{title: "Sales", valid: true}
]
const distOther = (main, ...oAs) => {
let others = oAs.map(sub => ['others', sub]);
return main.map((obj, idx) =>
Object.fromEntries(
Object.entries(obj)
.concat([others[idx]])));
};
console.log(distOther(objArr, x, y));
I would choose a map based approach as well but without the if clauses which explicitly check for expected values of the mapped item's name property.
The approach instead utilizes map's 2nd thisArg parameter which gets applied as the mapper functions this context. Such an additional object can be provided as a map/index of custom key value pairs where key equals a mapped item's name.
Thus the mapper implementation features generic code, and due to the this binding it will be provided as function statement which makes it also re-usable and, if properly named, readable / comprehensible / maintainable too.
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item,
{ others: index[item.name] ?? [] },
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
As one can see, the above implementation via Object.assign creates a new object from each mapped arrobj item. Thus the original item-references remains untouched / non mutated. It does not apply for the items of xitems and yitems since both array references are directly assigned each to its newly created others property. The above log does reflect this.
In case the goal was an entirely reference free data structure one needs to slightly change the Object.assign part of assignBoundNamedValueAsOthers ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item, {
others: (index[item.name] ?? [])
// dereference the `others` items as well.
.map(othersItem =>
Object.assign({}, othersItem)
)
},
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the OP does not need to care about immutability, the entire process then changes from a map task to a forEach task, where assignBoundNamedValueAsOthers does directly change/mutate each currently processed item of arrobj, thus forEach does not return any data but always the undefined value ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// mutate the original reference of the currently
// processed `item` by directly assigning, according
// to `item.name`, the bound named value as `others`.
Object.assign(
item,
{ others: index[item.name] ?? [] },
);
// no explicit return value due to
// going to be used as a `forEach` task.
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
// mutates each item of `arrobj`.
arrobj.forEach(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

How can I detect duplicated items in an array of objects?

I want to find duplicated objects and add hasDuplicate: true property, but not the first one. Methods should be run after one element.
The example array
items: [
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
},
{
checked: false,
desc: "",
id: "230",
value: "Lorem"
},
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
hasDuplicate: true
},
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
hasDuplicate: true
},
{
checked: false,
desc: "",
id: "230",
value: "Lorem",
hasDuplicate: true
},
]
What is an efficient way to detect duplicate items in an array with ES6?
Use Array.prototype.map() to traverse your array and check whether the hash (Object.entries() concatenated) is already seen:
const src = [{checked:false,desc:"",id:"396",value:"Lorem",},{checked:false,desc:"",id:"230",value:"Lorem"},{checked:false,desc:"",id:"396",value:"Lorem"},{checked:false,desc:"",id:"396",value:"Lorem"},{desc:"",id:"230",checked:false,value:"Lorem"}],
dedupe = (a, hashMap=[]) => a.map(o => {
const hash = Object
.entries(o)
.sort(([a],[b]) =>
a.localeCompare(b))
.flat()
.join('\ud8ff')
return !hashMap.includes(hash) ?
(hashMap.push(hash), o) :
{...o, hasDuplicate: true}
})
console.log(dedupe(src))
.as-console-wrapper{min-height:100%;}
isIdentical() method will compare two objects and returns true if they are identical.
const isIdentical = (obj1, obj2) => {
let flag = true;
Object.keys(obj1).forEach(key => {
if(obj1[key] !== obj2[key]){
flag = false;
return false;
}
});
return flag;
};
Now reduce method will help us loop through the array.
items.reduce((unique, item) => {
const index = unique.findIndex( u => isIdentical(u, item));
if(index < 0){
return [...unique, item];
} else {
item.hasDuplicate = true;
return unique;
}
}, []);
console.log(items);
This sets the hasDuplicate property of every duplicate item except the first one based on their id
items.forEach((d, i) => {
if(i == items.map(d => d.id).indexOf(d.id)) {
d.hasDuplicate = true;
}
})

Set new Sequence in Object from Array after deletion of specific object

My Goal:
I need to have a continuous sequence of numbers in the sequenceIndex which is a value in my object.
So when I remove a specific object the sequence index of the other objects is of course not continuous anymore. The object I remove is being checked against a specific value to see whether there are other objects in the array which share the same value (second if-statement). If so then there should be a new value set which is continuous.
The output is that the iterator in the if-statement is always the same for all objects manipulated.
From this:
const objectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}
];
By removing id 1 and 2 I would like to come to this (see continuous sequenceIndex):
const desiredObjectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "1",
documentType: "letter"
}
]
}
];
My code so far:
case ActionType.RemoveDocumentInSpecificFolder:
return state.map(file => {
// if in the correct folder remove the object with the delivered id
if (file.folder=== folder) {
remove(file.documents, {
id: action.payload.documents[0].id
});
// create newObjArray from objects which share a specific value and replace the sequence index by new value
const newObjArray = file.documents.map((obj: any) => {
// if the object has the specific value create new object with new sequenceIndex
if (obj.documentType === action.payload.documents[0].documentType) {
//poor attempt to create a sequence
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return {
...obj
};
});
return {
...file,
documents: newObjArray
};
}
return file;
});
I hope someone can guide me in the right direction. I would also always appreciate a suggestion of best practice :)
Best regards
You can use filter and map something like this
const arr = [{folder: "folderName",documents: [{id: 0,sequenceIndex: "0",documentType: "letter"},{id: 1,sequenceIndex: "1",documentType: "letter"},{id: 2,sequenceIndex: "2",documentType: "letter"},{id: 3,sequenceIndex: "3",documentType: "letter"}]}];
let getInSequence = (filterId) => {
return arr[0].documents.filter(({ id }) => !filterId.includes(id))
.map((v, i) => ({ ...v, sequenceIndex: i }))
}
console.log(getInSequence([1, 2]))
As commented:
This is the classic case where .filter().map() will be useful. filter the data and then use .map((o, i) => ({ ...obj, sequenceIndex: i+1 }) )
Following is the sample:
const objectsArray = [{
folder: "folderName",
documents: [{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}];
const ignoreIds = [1, 2]
const updatedDocs = objectsArray[0].documents
.filter(({
id
}) => !ignoreIds.includes(id))
.map((doc, index) => ({ ...doc,
sequenceIndex: index
}));
console.log(updatedDocs)
Now lets cover your attempt
const newObjArray = file.documents.map((obj: any) => {
// For all the unmatching objects, you will have undefined as object as you are using `.map`
// This will make you `newObjArray: Array<IDocument | undefined>` which can break your code.
if (obj.documentType === action.payload.documents[0].documentType) {
// This will set it as 0 in every iteration making i as 0 always.
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return { ...obj };
});
An alternate with single loop:
Idea:
Create a loop using Array.reduce and pass it a blank array as list.
Add a check and inside it, push value to this list.
For sequenceIndex, fetch last element and fetch its sequenceIndex. Add one and set it again.
const newObjArray = file.documents.reduce((acc: Array<IDocument>, obj: any) => {
if (obj.documentType === action.payload.documents[0].documentType) {
const sequenceIndex: number = (!!acc[acc.length - 1] ? acc[acc.length - 1].sequenceIndex : 1) + 1;
acc.push({ ...obj, sequenceIndex });
}
return acc;
});
The solution I used now to this problem was:
let count = 0;
const newObject = file.documents.map(obj => {
if (obj.documentType === firstDocument.documentType) {
count++;
return { ...obj, sequenceIndex: count - 1 };
}
return obj;
});
Both of the provided answers were not able to handle objects which were out of interest because of the different documentType so they dropped the object. with this solution, I am checking against the last element and increasing the count if the last element was the same documentType.

How to create/merge object from splitted string array in TypeScript?

I have an array of objects like below;
const arr1 = [
{"name": "System.Level" },
{"name": "System.Status" },
{"name": "System.Status:*" },
{"name": "System.Status:Rejected" },
{"name": "System.Status:Updated" }
]
I am trying to split name property and create an object. At the end I would like to create an object like;
{
"System.Level": true,
"System.Status": {
"*": true,
"Rejected": true,
"Updated": true
}
}
What I have done so far;
transform(element){
const transformed = element.split(/:/).reduce((previousValue, currentValue) => {
previousValue[currentValue] = true;
}, {});
console.log(transofrmed);
}
const transofrmed = arr1.foreEach(element => this.transform(element));
The output is;
{System.Level: true}
{System.Status: true}
{System.Status: true, *: true}
{System.Status: true, Rejected: true}
{System.Status: true, Updated: true}
It is close what I want to do but I should merge and give a key. How can I give first value as key in reduce method? Is it possible to merge objects have same key?
You could reduce the splitted keys adn check if the last level is reached, then assign true, otherwise take an existent object or a new one.
const
array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }],
object = array.reduce((r, { name }) => {
var path = name.split(':');
last = path.pop();
path.reduce((o, k) => o[k] = typeof o[k] === 'object' ? o[k] : {}, r)[last] = true;
return r;
}, {});
console.log(object);
Use Array.reduce() on the list of properties. After splitting the path by :, check if there is second part. If there is a second part assign an object. Use object spread on the previous values, because undefined or true values would be ignored, while object properties would be added. If there isn't a second part, assign true as value:
const array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }];
const createObject = (arr) =>
arr.reduce((r, { name }) => {
const [first, second] = name.split(':');
r[first] = second ? { ...r[first], [second]: true } : true;
return r;
}, {});
console.log(createObject(array));

Categories

Resources