I want to check if there is a value in a certain variable I have. Below I put an example of a logic that I want to achieve.
No matter how hard I tried, I was able to write a very sketchy code as a result of 3 hours of work and research, but it has nothing to do with what I want to achieve.
My Code:
const Files = [
{
Name: 'System',
Type: 'directory',
Value: [
{
Name: 'Main',
Type: 'directory',
Value: [
{
Name: 'Drivers',
Type: 'directory',
Value: [
{
Name: 'Startup',
Type: 'file',
Value: new FileSystem.File('Startup', 0x1, 'test blah blah'),
},
],
},
],
},
],
},
];
BlahBlah.has(Files, 'System->Main->Drivers');
// [File]
BlahBlah.has(Files, 'System->Main->Drivers->Startup');
// File
BlahBlah.has(Files, 'System->Main->Drivers->AnyWhere');
// undefined
BlahBlah.has(Files, 'System->Main->AnyRandomDirectory');
// NaN
My Function:
function text2Binary(str: string, spliter: string = ' '): string {
return str
.split('')
.map(function (char) {
return char.charCodeAt(0).toString(2);
})
.join(spliter);
}
export function FileTypeFromNumber(e: number) {
if (typeof e != 'number')
try {
e = Number(e);
} catch (_) {
return null;
}
return {
0x1: {
Name: 'Executable File',
Extension: 'exe',
},
0x2: {
Name: 'Text Document',
Extension: 'txt',
},
}[e];
}
export type FileTypes =
| 0x1
| 0x2
| 0x3
| 0x4
| 0x5
| 0x6
| 0x7
| 0x8
| 0x9
| null;
export class File {
Name: string;
Type: {
Name: string;
Extension: string;
};
Content: string;
Size: number;
constructor(name: string, type: FileTypes, content: string) {
this.Name = name;
this.Type = FileTypeFromNumber(type);
this.Content = content;
this.Size = text2Binary(content, '').length;
}
}
export class Directory {
public Name: string;
public Files: (File | Directory)[] = [];
constructor(name: string) {
this.Name = name;
}
addFile(file: File | Directory) {
this.Files.push(file);
}
getFile(name: string): null | (File | Directory)[] {
if (typeof name != 'string')
try {
name = String(name);
} catch (_) {
return null;
}
const Result = this.Files.filter((e) => e.Name == name);
return Result.length == 0 ? null : Result;
}
getSize() {
return this.Files.map((e) =>
e instanceof Directory ? e.getSize() : e.Size
).reduce((a, b) => a + b, 0);
}
has(name) {
return this.Files.some((e) => e.Name == name);
}
getJSON() {
return this.Files.map((e) => ({ ...e }));
}
}
interface x {
Content: string;
Name: string;
Size: number;
Type: string;
}
export function ConvertFromJSONtoDirectory(json: any[]) {
return json.map((value) => {
const isDirectory = value.Type == 'directory';
if (!isDirectory) {
return value.Value;
}
const self = new Directory(value.Name);
ConvertFromJSONtoDirectory(value.Value).map((e) => self.addFile(e));
return self;
});
}
export default class DirectorySystem {
Memory: Map<any, any>;
Current: string | null;
constructor(Current = null) {
this.Memory = new Map();
this.Current = Current;
}
addDirectory(directory: Directory): null | true {
if (!(directory instanceof Directory)) return null;
if (this.Memory.has(directory.Name)) return null;
this.Memory.set(directory.Name, directory);
return true;
}
getDirectory(DirectoryName: string): boolean | Directory {
if (typeof DirectoryName != 'string')
try {
DirectoryName = String(DirectoryName);
} catch (_) {
return null;
}
const Result = this.Memory.has(DirectoryName);
return Result ? this.Memory.get(DirectoryName) : Result;
}
getDirectoryCurrent() {
if (this.Current == null) return this;
}
changeDirectory(by: -1 | 1, value: string) {
if (by == -1) {
if (this.Current == null) return null;
if (this.Current.includes('->')) {
this.Current = this.Current.split('->').slice(0, -1).join('->');
} else {
this.Current = null;
}
return this.Current;
} else if (by == 1) {
let Position = [this.Current, value].join('->');
if (this.Current == null) {
Position = Position.split('->').slice(1).join('->');
}
let Result = this.has(Position);
console.log(Result);
}
}
has(query: string) {
try {
return query.split('->').reduce((a, b) => {
if (Array.isArray(a)) {
const f = a.filter((e) => e['Name'] == b);
if (a.length > 0) {
return f['Files'];
} else {
return a;
}
}
return a['Files'];
}, this.getJSON());
} catch (_) {
return false;
}
}
getJSON(): x[][] {
return [...this.Memory.values()].reduce((a, b) => {
a[b.Name] = b.getJSON();
return a;
}, {});
}
}
Result: (Thanks Michael M. and chill 389cc for helping me understand the error)
has(
query: string,
overwrite = null
) {
// If overwrite argument is not null, we are going use it.
let files = overwrite == null ? this.getJSON() : overwrite;
// Split string for getting more usable type with converting string to Array.
const QueryParams = query.split('->').filter(String);
// If we dont have no query, we can return current status.
if (QueryParams.length == 0) return overwrite;
if (Array.isArray(files)) {
const SearchFor = QueryParams.shift();
const Result = files.filter((e) => {
if (e instanceof Directory) {
const x = e.Name == SearchFor;
return x ? e : false;
}
return e.Name == SearchFor;
})[0];
// If we cant find any indexing result
if (!Result) return false;
// We found a file and if we dont have any query this is mean we found it!
if (Result instanceof File) return QueryParams.length == 0;
// We found a Directory and we doesnt have any Query now, so we can return true.
if (Result instanceof Directory && QueryParams.length == 0) return true;
if (
Result.Name != SearchFor ||
(QueryParams.length != 0 && Result.Files.length == 0)
)
// If name not suits or still we has Query and not enough file for indexing.
return false;
// If nothing happens on upper section, return rescyned version of this function.
return this.has(QueryParams.join('->'), Result.Files);
} else {
// If value is Object, Try Search param in object, and return it.
const Result = files[QueryParams.shift()];
return !Result ? false : this.has(QueryParams.join('->'), Result);
}
}
I can't replicate all of your code, but does this help?
interface Entry {
Name: string,
Type: string,
Value: Array<Entry> | any,
};
const Files = [
{
Name: "System",
Type: "directory",
Value: [
{
Name: "Main",
Type: "directory",
Value: [
{
Name: "Drivers",
Type: "directory",
Value: [
{
Name: "Startup",
Type: "file",
Value: "test", // change this to anything
},
],
},
],
},
],
},
];
function getEl(files: Array<Entry>, path: String) {
let path_walk = path.split("->");
let obj = files;
for (let part of path_walk) {
let found = false;
for (let entry of obj) {
if (entry.Name == part) {
obj = entry.Value;
found = true;
}
}
if (!found) return undefined;
}
return obj;
}
console.log(getEl(Files, "System->Main->Drivers")); // => [ { Name: 'Startup', Type: 'file', Value: 'test' } ]
console.log(getEl(Files, "System->Main->Drivers->Startup")); // => "test"
console.log(getEl(Files, "System->Main->Drivers->AnyWhere")); // => undefined
console.log(getEl(Files, "System->Main->AnyRandomDirectory")); // => undefined
There are some obvious problems, such as the fact that your example shows .has() being called with two arguments but it is defined in the class to only take in one. That being said, here is a function that, given a string query as you have and an array of objects like you have, would read the query and return if the array works for that query.
function has(fileSystem, query) {
const arrayOfArgs = query.split('->')
if (Array.isArray(fileSystem)) {
for (let i = 0; i < fileSystem.length; i++) {
if (fileSystem[i]['Name'] === arrayOfArgs[0]) {
if (arrayOfArgs.length === 1) {
// element found
return true; // replace this to return an actual value if that is desired.
}
if (fileSystem[i]['Type'] === 'directory') {
// if not, recurse in if it is a directory
return has(fileSystem[i]['Value'], arrayOfArgs.slice(1).join('->'));
} else {
// if it isn't a directory, don't try to recurse in
return false;
}
}
}
}
return false;
}
console.log(has(Files, 'System->Main->Drivers')); // true
console.log(has(Files, 'System->Main->Drivers->Startup')); // true
console.log(has(Files, 'System->Main->Drivers->AnyWhere')); // false
console.log(has(Files, 'System->Main->AnyRandomDirectory')); // false
You'll have to add your own types to get it back to TypeScript and obviously I pulled it out of the class for easier testing but it should be pretty easy to re-implement.
Related
I would like to use a BehaviorSubject to store an Array of objects and have a way to easily update (next?) a single item of that array without having to update the whole array.
I would also like for an easy way to subscribe to changes to an specific item of that array. I know it could be done with filter, but an easier way would be nice...
Is that possible?
I am currently using this version I created (which I don't know if it is the best way or not) that also persists its contents to localstorage:
export class LocalStorageBehaviorSubject<T, Y = T> {
private _data: BehaviorSubject<T>;
public asObservable() {
return this._data.asObservable();
}
public next(data: T) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
localStorage.setItem(this.key, JSON.stringify(data));
this._data.next(data);
}
public nextItem(item: Y) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
if (dados.some(r => r[this.id] === item[this.id])) {
dados = dados.map(r => r[this.id] === item[this.id] ? item : r);
} else {
dados.push(item);
}
if(this.expirationFn !== null) {
dados = this.expirationFn(dados);
}
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeItem(id) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
dados = dados.filter(r => r[this.id] !== id);
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeExpiredData(){
let data = this.loadFromStorage();
if (data) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
this._data.next(data);
}
}
public getValue() {
this.removeExpiredData();
return this._data.getValue();
}
public getItem(id): Y {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
this.removeExpiredData();
return (<any>this._data.getValue()).slice().find(t => t[this.id] == id);
}
constructor(private key: string, private id: string, defaultValue: any = null, private expirationFn: (dados: T) => T = null) {
this._data = new BehaviorSubject<T>(defaultValue);
this.removeExpiredData();
}
private loadFromStorage(): T {
let dadosStr = localStorage.getItem(this.key);
if (dadosStr) {
return JSON.parse(dadosStr);
}
return null;
}
}
I hoped that would be an simpler way...
Thanks
I would also like for an easy way to subscribe to changes to an
specific item of that array. I know it could be done with filter, but
an easier way would be nice...
You can use map operator and inside lambda array.find
Example
const mockStorage = {
values: {},
setItem(key, value) {
this.values[key] = value;
},
getItem(key) {
return this.values[key]
},
clearItem(key) {
this.values[key] = undefined;
}
}
class LocalStorageBehaviorSubject {
constructor(key, defaultValue) {
this.key = key;
this._data = new rxjs.BehaviorSubject(defaultValue);
}
nextItem(item) {
const list = this._data.value;
const itemIndex = list.findIndex(pr => pr.id === item.id);
this._data.next([
...list.slice(0, itemIndex),
{
...(list[itemIndex] || {}),
...item
},
...list.slice(itemIndex + 1)
]);
}
removeItem(id) {
this._data.next(this._data.value.filter(pr => pr.id !== id));
}
getItem(id) {
return this.asObservable()
.pipe(
rxjs.operators.map(values => values.find(pr => pr.id === id) || null),
rxjs.operators.distinctUntilChanged());
}
asObservable() {
return this._data.asObservable().pipe(
rxjs.operators.tap(values => {
if (values && values.length) {
mockStorage.setItem(this.key, JSON.stringify(values));
}
else {
mockStorage.clearItem(this.key);
}
}))
}
}
const localStorageBehaviorSubject = new LocalStorageBehaviorSubject('items', []);
localStorageBehaviorSubject
.getItem(1)
.subscribe(item => {
console.log(item);
})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test'})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test1'})
localStorageBehaviorSubject.nextItem({id: 2, value: 'test2'})
localStorageBehaviorSubject.nextItem({id: 3, value: 'test3'})
localStorageBehaviorSubject.removeItem(2);
localStorageBehaviorSubject.removeItem(1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I have been trying to write a function that would return an another in line property key.
Lets say we have this JSON:
'Test123': {
'Another Test': {},
'Test some more': {
'Still testing?': {
'Yeah...': {}
},
'Never ending story': {}
}
},
For example, if we pass 'Still testing?' key into the function it should return 'Yeah...', but if we pass 'Yeah...' it should return 'Never ending story'. Doest someone knows who to do it? I have been trying to write a function doing that but my brain doesn't support it...
let obj = {
'Test123': {
'Another Test': {},
'Test some more': {
'Still testing?': {
'Yeah...': {}
},
'Never ending story': {}
}
}
}
function nextKey(keyStr, object) {
if (!keyStr || !object || typeof object != 'object' || object.constructor.name != 'Object') {
console.log("Improper Parameter !!!")
return
}
let found = false
let answer = {}
findKey(object)
return (Object.keys(answer).length) ? answer : "";
function findKey(obj) {
for (let key of Object.keys(obj)) {
if(Object.keys(answer).length) return;
let value = obj[key]
if(key == keyStr)found = true;
else if(found) {answer.key = key; answer.value = value; return}
if (Object.keys(value).length) findKey(value)
}
}
}
console.log(nextKey('Still testing?', obj)) //{ key: 'Yeah...', value: {} }
console.log(nextKey('Yeah...', obj)) //{ key: 'Never ending story', value: {} }
I have the following function that performs the searches.
public static containsDeep = (text: string) => (value?: any): any => {
if (!value) {
return false;
}
const valueType = typeof value;
if (valueType === 'string') {
return value.toLowerCase().indexOf(text.toLowerCase()) > -1;
}
if (Array.isArray(value)) {
return value.some(VimboUtils.containsDeep(text));
}
if (valueType === 'object') {
return Object.values(value).some(VimboUtils.containsDeep(text));
}
return false;
// tslint:disable-next-line
};
public static searchDeepInArray(array: Array<any>, text: string): any {
if (!array || !text) {
return null;
}
return array.filter(VimboUtils.containsDeep(text));
}
example of an array I get that I search for:
const value = [
{
'config_vimbo': [
{
_id: '1',
title: 'Estrutura FrontEnd - Códigos',
path: '/apps/general-settings/frontend-structure-code',
hidden: 'this._rolesService.hide.isNotVimbo()'
},
{
_id: '2',
title: 'Unidade de medidas CTE',
path: '/apps/general-settings/units-measurement-cte',
hidden: 'this._rolesService.hide.isNotVimbo()'
}
]
},
{
'Sua equipe': [
{
_id: '1',
title: 'Gerencie usuários e permissões',
path: '/apps/general-settings/user-business',
hidden: '!this._rolesService.modules.canView("configuracao")'
}
]
},
{
'Ajustes da sua conta': [
{
_id: '1',
title: 'Unidades de medidas',
path: '/apps/general-settings/units-measurement',
hidden: '!this._rolesService.modules.canView("configuracao")'
}
]
}]
I call the function as follows:
searchDeepInArray(value,'argument');
what is happening that it is filtering, but only if I type the first whole word.
If I type a word that is in the middle of the sentence, it does not search correctly.
I couldn't identify where the error is.
Edit 1
I could reproduce the error better. What happens, when it has more than one item per position, when you type if the word has something that matches, it returns all the items of that possession where the word was found. And should return the position with only the corresponding items. see the example on this link, if I type recebimento comes the position with all items(2) https://i.imgur.com/Uj7MlvK.gif
Solution found, if someone has a better solution, you can leave your comment :)
public static searchDeepInArray(
array: Array<any>,
text: string,
field: string
): any {
if (!array || !text) {
return null;
}
return array.filter(this.containsDeep(text)).map(element => {
const idx = Object.keys(element).length
? Object.keys(element)[0]
: null;
if (!idx) {
return element;
}
return Object.assign({}, element, {
[idx]: element[idx].filter(
subElement =>
subElement[field]
.toLowerCase()
.indexOf(text.toLowerCase()) > -1
)
});
});
}
I have a function like so:
type Entry = PageBasic | PageHome | PostCollection | PostProduct;
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
if (contentType ==='pageBasic') {
return { slug: entry.fields.slug };
}
if (contentType === 'pageCollection') {
return { collection: entry.fields.slug };
}
if (contentType === 'postProduct') return {
collection: entry.fields.collection.fields.slug,
product: entry.fields.slug,
};
return {};
};
My types for PageBasic, PostCollection and PostProduct are all different. Some have the fields.slug and fields.collection data and some don't. My if statements confirm to me which entry type I'm dealing with e.g. if the contentType === 'pageCollection' then I'm dealing with PostCollection type. However Typescript doesn't know that. Is there a way for me to tell it if my condition is true, then the type of the entry param is PostCollection or something?
The only other way I know to do this is adding lots more conditional statements to check if the keys exist, which seems pointless when I already know they will based on my conditional statements. e.g:
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
if (contentType === CT_PAGE_BASIC) {
if ('slug' in entry.fields) return { slug: entry.fields.slug };
}
if (contentType === CT_POST_COLLECTION) {
if ('slug' in entry.fields) return { collection: entry.fields.slug };
}
if (contentType === CT_POST_PRODUCT) return {
...'collection' in entry.fields && { collection: entry.fields.collection.fields.slug },
...'slug' in entry.fields && { product: entry.fields.slug },
};
return {};
};
Yes you can - use a type assertion:
if (contentType ==='pageBasic') {
return { slug: <PageBasic>(entry).fields.slug };
}
You could also return an object with conditionals:
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
return {
...(contentType ==='pageBasic' ? { slug: (<PageBasic>entry).fields.slug } : 0),
...(contentType === 'pageCollection' ? { collection: (<PostCollection>entry).fields.slug } : 0),
...(contentType === 'postProduct' ? { collection: (<PostProduct>entry).fields.collection.fields.slug, product: (<PostProduct>entry).fields.slug } : 0)
};
};
I have a nested object which looks like this :
let obj = {
_id:{}
person:{
$search:{fname:true}
_id:{},
fname:{}
},
code:{},
vnvEmpName:{}
}
I have to search for a $search keyword in this and get the key which is inside it that is fname in this case, it can contain multiple keys as well and I have to retrieve all of it.
I tried something like this :
function findById(obj, id) {
var result;
for (var p in obj) {
if (obj.id === id) {
return obj;
} else {
if (typeof obj[p] === 'object') {
result = findById(obj[p], id);
if (result) {
return result;
}
}
}
}
return result;
}
If the object is in this way :
let obj = {
_id: {},
person: {
$search: {
lname: true
},
_id: {},
fname: {},
something:{
$search: {
fname: true
},
}
},
code: {},
$search: {
mname: true
},
vnvEmpName: {}
}
I want to retrieve all the attributes inside the $search of every block.
but I don't know how to get the keys inside a particular key as I am so new to the javascript.
To just get the keys you can simply do it using Object.keys(yourObject) MDN Object.keys
You can also use lodash to obtain the same result
Need to recursively search through the object
let obj = {
_id: {},
person: {
$search: {
fname: true
},
_id: {},
fname: {}
},
code: {},
vnvEmpName: {}
}
function findById(obj, id) {
var result = "";
// iterate the object using for..in
for (var keys in obj) {
// check if the object has any property by that name
if (obj.hasOwnProperty(keys) && typeof obj[keys] === 'object') {
// if the key is not undefined get it's value
if (obj[keys][id] !== undefined) {
result = (obj[keys][id])
} else {
// else again call the same function using the new obj value
findById(obj[keys], id)
}
}
}
return result;
}
console.log(findById(obj, 'fname'))
You can use the following function:
const objectifier = function (splits, create, context) {
let result = context;
for (let i = 0, key; result && (key = splits[i]); i += 1) {
if (key in result) { result = result[key]; } else {
result = create
? result[key] = {}
: undefined;
}
}
return result;
};
Have a look at the example below:
let obj = {
'_id': {aa: 'aa'},
'person': {
'$search': {
'fname': true
},
'_id': {'bb': 'bb'},
'fname': {'cc': 'cc'}
},
'code': {'dd': 'dd'},
'vnvEmpName': {'name': 'sdsdd'}
}
const objectifier = function (splits, create, context) {
let result = context;
for (let i = 0, key; result && (key = splits[i]); i += 1) {
if (key in result) { result = result[key]; } else {
result = create
? result[key] = {}
: undefined;
}
}
return result;
};
console.log(objectifier('person.$search'.split('.'), false, obj));
// { fname: true }