Related
I have a json object as below:
[
{
name: 'data 1',
users: [
{
username: 'user 1',
full_name: 'name 1',
sources: [
{ type: 'type 1', name: 'source name 1' },
{ type: 'type 2', name: 'source name 2' },
],
},
{
username: 'user 2',
full_name: 'name 2',
sources: [
{ type: 'type 3', name: 'source name 3' },
{ type: 'type 4', name: 'source name 4' },
],
},
],
},
{
name: 'data 2',
users: [
{ username: 'user 3', full_name: 'name 3' },
{
username: 'user 4',
full_name: 'name 4',
sources: [
{ type: 'type 5', name: 'source name 3' },
{ type: 'type 6', name: 'source name 5' },
],
},
],
},
{
name: 'data 3',
users: [
{ username: 'user 5', full_name: 'name 5' },
{
username: 'user 6',
full_name: 'name 6',
sources: [
{ type: 'type 5', name: 'source name 6' },
{ type: 'type 6', name: 'source name 7' },
],
},
],
},
];
I need a function that filter data recursively by its all values.
For example when I type "data 1" it should return an array such as
[
{
name: 'data 1',
users: [
{
username: 'user 1',
full_name: 'name 1',
sources: [
{ type: 'type 1', name: 'source name 1' },
{ type: 'type 2', name: 'source name 2' },
],
},
{
username: 'user 2',
full_name: 'name 2',
sources: [
{ type: 'type 3', name: 'source name 3' },
{ type: 'type 4', name: 'source name 4' },
],
},
],
},
];
or if I type "source name 3" it should return both "data 1" and "data 2" objects as an array.
The json object may have more arrays or objects in its values. I have tried something like this but it returns all data instead of filtered data.
function search(data) {
return data.filter((data) => {
Object.values(data).some((value) =>
value.constructor.name === 'Array'
? search(value)
: value.constructor.name === 'Object'
? Object.values(value).some((innerValue) =>
innerValue.toString().toLowerCase().includes(searchValue)
)
: value.toString().toLowerCase().includes(searchValue)
);
});
}
If I had to do that by some specific key it would be easy but I have to filter dynamically by all keys. What should I do to filter a json object like this?
You could take an recursive approach. By having an object it iterates the values as well.
const
has = value => object => Object
.values(object)
.some(v => v === value || v && typeof v === 'object' && has(value)(v)),
filter = (array, value) => array.filter(has(value)),
data = [{ name: 'data 1', users: [{ username: 'user 1', full_name: 'name 1', sources: [{ type: 'type 1', name: 'source name 1' }, { type: 'type 2', name: 'source name 2' }] }, { username: 'user 2', full_name: 'name 2', sources: [{ type: 'type 3', name: 'source name 3' }, { type: 'type 4', name: 'source name 4' }] }] }, { name: 'data 2', users: [{ username: 'user 3', full_name: 'name 3' }, { username: 'user 4', full_name: 'name 4', sources: [{ type: 'type 5', name: 'source name 3' }, { type: 'type 6', name: 'source name 5' }] }] }, { name: 'data 3', users: [{ username: 'user 5', full_name: 'name 5' }, { username: 'user 6', full_name: 'name 6', sources: [ { type: 'type 5', name: 'source name 6' }, { type: 'type 6', name: 'source name 7' }] }] }];
console.log(filter(data, 'data 1'));
console.log(filter(data, 'source name 3'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
The problem:
I'm trying to write a TypeScript function suitable for a previously written JEST test. There are 5 tests in total. The function I wrote passes all the tests, but unfortunately, I was asked to write again in the review. How to write this more efficiently?
Example case:
Imagine an array that contains folders. These folders can have files in it. move function moves a file to another folder and returns the new state of given list.
Example list:
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
]
If I run move(list, '4', '6') then I expect file with id 4 moved to the folder which has id 6. Function should return the new state below;
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
]
move.ts
type File = {
id: string;
name: string;
};
type Folder = {
id: string;
name: string;
files: File[];
};
type List = Folder[];
export default function move(list: List, source: string, destination: string): List {
let sourceFileIndex = -1;
let sourceFolderIndex = -1;
let destinationFolderIndex = -1;
for (let i = 0; i < list.length; i += 1) {
const { id, files } = list[i];
if (id === source) {
throw new Error('You cannot move a folder');
}
if (id === destination) {
destinationFolderIndex = i;
}
if (destinationFolderIndex > -1 && sourceFileIndex > -1) break;
for (let j = 0; j < files.length; j += 1) {
if (files[j].id === destination) {
throw new Error('You cannot specify a file as the destination');
}
if (files[j].id === source) {
sourceFileIndex = j;
sourceFolderIndex = i;
break;
}
}
}
if (destinationFolderIndex === -1) {
throw new Error('Given destination id did not match any folder');
}
if (sourceFolderIndex === -1) {
throw new Error('Given source id of file was not found in any folder');
}
list[destinationFolderIndex].files.push(
...list[sourceFolderIndex].files.splice(sourceFileIndex, 1),
);
return list;
}
move.spec.ts
import move from './move';
describe('move', () => {
it('moves given file to another folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
];
expect(move(list, '4', '6')).toStrictEqual(result);
});
it('throws error if given source is not a file', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [] },
];
expect(() => move(list, '3', '1')).toThrow('You cannot move a folder');
});
it('throws error if given destination is not a folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [{ id: '4', name: 'File 2' }] },
];
expect(() => move(list, '2', '4')).toThrow('You cannot specify a file as the destination');
});
it('throws error if given source was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '9', '6')).toThrow(
'Given source id of file was not found in any folder',
);
});
it('throws error if given destination was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '4', '9')).toThrow('Given destination id did not match any folder');
});
});
As I said, it passed 5 tests but was not accepted. What is the problem?
I want to convert an object from one format to another. So far my attempts at doing this recursively failed; either I'm getting a maximum stack exception or I'm unable to iterate over all paths.
Let's assume we have an object that lists questions and their answers. There may be N questions and M answers.
Object at start:
var before = {
item: 'Question 1',
id: '1',
type: 'Question',
items: [
{
item: 'Answer 1',
id: '1.1',
type: 'Answer',
items:
[
{
item: 'Question 2',
id: '1.1.1',
type: 'Question',
items: [
{
item: 'Answer 2.1',
id: '1.1.1.1',
type: 'Answer'
},
{
item: 'Answer 2.2',
id: '1.1.1.2',
type: 'Answer'
}
]
}
// ...
]
}, {
item: 'Answer 1',
id: '1.2',
type: 'Answer',
items:
[
{
item: 'Question 3',
id: '1.2.1',
type: 'Question',
items: [
{
item: 'Answer 3.1',
id: '1.2.1.1',
type: 'Answer'
},
{
item: 'Answer 3.2',
id: '1.2.1.2',
type: 'Answer'
}
]
}
// ...
]
}
// ...
]
}
Object how it should look like (wrap all in 'items' array; change key names 'item' to 'title', 'id' to 'key', remove 'type', add 'color' depending on 'type'):
var after = {
items: [
{
title: 'Question 1',
key: '1',
color: 'Red',
items: [
{
title: 'Answer 1',
key: '1.1',
color: 'Blue',
items:
[
{
title: 'Question 2',
key: '1.1.1',
color: 'Red',
items: [
{
title: 'Answer 2.1',
key: '1.1.1.1',
color: 'Blue'
},
{
title: 'Answer 2.2',
key: '1.1.1.2',
color: 'Blue'
}
]
}
// ...
]
}, {
title: 'Answer 1',
key: '1.2',
color: 'Blue',
items:
[
{
title: 'Question 3',
key: '1.2.1',
color: 'Red',
items: [
{
title: 'Answer 3.1',
key: '1.2.1.1',
color: 'Blue'
},
{
title: 'Answer 3.2',
key: '1.2.1.2',
color: 'Blue'
}
]
}
// ...
]
}
// ...
]
}
]
}
It seems easy enough, but I can't get it to work. This is how I tried to iterate:
function iter(o) {
for(let k in o) {
if (!(['item', 'items', 'id'].includes(k))) // My object contains a few more keys I don't want to go down further into
continue
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k]); // Max stack exception
return;
}
}
};
Thank you very much!
You could map the objects and rename the keys and map nested items.
const
iter = ({ item: title, id: key, type, items, ...o }) => ({
title,
key,
color: 'color' + key,
...o,
...(items && { items: items.map(iter) })
}),
before = { item: 'Question 1', id: '1', type: 'Question', items: [{ item: 'Answer 1', id: '1.1', type: 'Answer', items: [{ item: 'Question 2', id: '1.1.1', type: 'Question', items: [{ item: 'Answer 2.1', id: '1.1.1.1', type: 'Answer' }, { item: 'Answer 2.2', id: '1.1.1.2', type: 'Answer' }] }] }, { item: 'Answer 1', id: '1.2', type: 'Answer', items: [{ item: 'Question 3', id: '1.2.1', type: 'Question', items: [{ item: 'Answer 3.1', id: '1.2.1.1', type: 'Answer' }, { item: 'Answer 3.2', id: '1.2.1.2', type: 'Answer' }] }] }] },
result = [before].map(iter);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can achieve this using map, I wrote an simple test to show my point here
this is the important part of the code
function rename(item: any) {
return {
title: item.item,
key: item.id,
color: item.type === 'Question' ? 'red' : 'blue',
items: item.items?.map(rename)
}
}
console.log(items.map(rename))
Of course if you're using typescript, change any to the appropriate type and pay attention that I'm using ? operator which will not work with javascript, so you could do something like
...
items: item.items ? item.items.map(rename) : undefined
...
so I am trying to build my own discord bot with discord.js. There is one really important thing that I want to achieve.
I want to reassign a variable through a bot command. My current code looks like this:
client.on("message", (msg) => {
if (msg.content.startsWith(config.prefix + "boosting")) {
const embed = new Discord.MessageEmbed()
.setColor("0xd08d11")
.setTitle("**Random Title**")
.setDescription(Some description)
.addFields(
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: "Person 0", value: 'Personvalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Price', value: 'Pricevalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Person 1', value: '40k', inline: true },
{ name: 'Person 2', value: '40k', inline: true },
{ name: 'Person 3', value: '40k', inline: true },
{ name: 'Person 4', value: '40k', inline: true },
)
.setThumbnail(logo)
.setTimestamp()
.setFooter('created by me or something like that');
client.channels.cache.get(`here_is_my_channel_id`).send(embed);
}
});
client.login(config.token)
Okay, so right now I can type .boosting to get an embed message from my bot, but the thing is, that i want to type .boosting Variable1 Variable2 and so on to give new values to the attributes of the embed message. And with attributes I mean something like the description or the fields name or values. I tried something like this:
let Variable = "Test";
client.on("message", (msg) => {
if (msg.content.startsWith(config.prefix + "boosting" + " " + Variable)) {
const embed = new Discord.MessageEmbed()
.setColor("0xd08d11")
.setTitle("**Random Title**")
.setDescription(Variable)
.addFields(
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: "Person 0", value: 'Personvalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Price', value: 'Pricevalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Person 1', value: '40k', inline: true },
{ name: 'Person 2', value: '40k', inline: true },
{ name: 'Person 3', value: '40k', inline: true },
{ name: 'Person 4', value: '40k', inline: true },
)
.setThumbnail(logo)
.setTimestamp()
.setFooter('created by me or something like that');
client.channels.cache.get(`here_is_my_channel_id`).send(embed);
}
});
client.login(config.token)
Now I can write .boosting Test and the description of this embed message will have this as it's value.
I know that I need to define the variable, but is there any possibility to reassign it with a bot command? So it can change from the value "Test" to "something" for example. Appreciate every help!
You can split your the message into an array of words:
const args = message.content.slice(config.prefix.length).split(/ +/).slice(1)
// example: `.boosting hello people of the world`
// returns: ['hello', 'people', 'of', 'the', 'world']
So, if you'd like to get the first argument (word), you can use args[0]. For the second, args[1], and so on.
You updated code:
client.on("message", (msg) => {
if (msg.content.startsWith(config.prefix + "boosting" + " " + Variable)) {
const args = message.content.slice(config.prefix.length).split(/ +/).slice(1)
const embed = new Discord.MessageEmbed()
.setColor("0xd08d11")
.setTitle("**Random Title**")
.setDescription(args[0])
.addFields(
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: "Person 0", value: 'Personvalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Price', value: 'Pricevalue', inline: true },
{ name: '\u200B', value: '\u200B' }, //spacer
{ name: 'Person 1', value: '40k', inline: true },
{ name: 'Person 2', value: '40k', inline: true },
{ name: 'Person 3', value: '40k', inline: true },
{ name: 'Person 4', value: '40k', inline: true },
)
.setThumbnail(logo)
.setTimestamp()
.setFooter('created by me or something like that');
client.channels.cache.get(`here_is_my_channel_id`).send(embed);
}
});
client.login(config.token)
I'm trying to understand if it's possible to place a select input on the right of a radio input in a custom bootbox dialog. Example:
bootbox.prompt({
title: "This is a prompt with a set of radio inputs!",
message: '<p>Please select an option below:</p>',
inputType: 'radio',
inputOptions: [
{
text: 'Choice One',
value: '1',
},
{
text: 'Choice Two',
value: '2',
},
{
text: 'Choice Three',
value: '3',
}
],
callback: function (result) {
console.log(result);
}
});
This will show three radio items.
I want to place on the right of, say, 'Choice Two' a select:
inputType: 'select',
inputOptions: [
{
text: 'Choose one...',
value: '',
},
{
text: 'Choice One',
value: '1',
}]
But I don't understand if this is possible with bootbox.