-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[snipsonian-core] added some extra object utility functions
- Loading branch information
Ben Verbist
committed
Sep 27, 2022
1 parent
e264c3c
commit eda48bd
Showing
16 changed files
with
331 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v14.18.2 | ||
16.15.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
packages/snipsonian-core/src/object/filtering/getPossiblyNestedObjectPropValue.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import getPossiblyNestedObjectPropValue from './getPossiblyNestedObjectPropValue'; | ||
|
||
describe('getPossiblyNestedObjectPropValue()', () => { | ||
const TEST_OBJECT = { | ||
someField: 'someValue', | ||
levelOne: { | ||
levelTwo: { | ||
levelTwoField: false, | ||
levelThree: { | ||
deepField: 'qwerty', | ||
otherDeepField: 123, | ||
}, | ||
}, | ||
levelOneField: 'qed', | ||
levelTwoArray: [{ | ||
fieldInArray: 'abc', | ||
}], | ||
}, | ||
}; | ||
|
||
it('returns a - possibly nested - field value that matches the input path part(s)', () => { | ||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'someField', | ||
)).toEqual('someValue'); | ||
|
||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelOneField', | ||
)).toEqual('qed'); | ||
|
||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelTwo', 'levelTwoField', | ||
)).toEqual(false); | ||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelTwo', 'levelThree', 'deepField', | ||
)).toEqual('qwerty'); | ||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelTwo', 'levelThree', 'otherDeepField', | ||
)).toEqual(123); | ||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelTwoArray', '0', 'fieldInArray', | ||
)).toEqual('abc'); | ||
|
||
expect(getPossiblyNestedObjectPropValue( | ||
TEST_OBJECT, | ||
'levelOne', 'levelTwo', 'levelThree', | ||
)).toEqual({ | ||
deepField: 'qwerty', | ||
otherDeepField: 123, | ||
}); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
packages/snipsonian-core/src/object/filtering/getPossiblyNestedObjectPropValue.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { TAnyObject } from '../../typings/object'; | ||
import isArray from '../../is/isArray'; | ||
import isObjectPure from '../../is/isObjectPure'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export default function getPossiblyNestedObjectPropValue(obj: TAnyObject, ...pathParts: string[]): any { | ||
if (!obj || (!isArray(obj) && !isObjectPure(obj))) { | ||
return null; | ||
} | ||
|
||
if (pathParts.length === 1) { | ||
return obj[pathParts[0]]; | ||
} | ||
|
||
const [firstPathPart, ...deeperPathParts] = pathParts; | ||
|
||
return getPossiblyNestedObjectPropValue( | ||
obj[firstPathPart] as TAnyObject, | ||
...deeperPathParts, | ||
); | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/snipsonian-core/src/object/keyVals/flipObjectKeyVals.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* E.g. input object { abc: 'def' } will result in { def: 'abc' } | ||
*/ | ||
export default function flipObjectKeyVals<Key extends string = string, Val extends string = string>( | ||
obj: Record<Key, Val>, | ||
): Record<Val, Key> { | ||
return Object.entries<Val>(obj).reduce( | ||
(accumulator, [key, val]) => { | ||
accumulator[val] = key as Key; | ||
return accumulator; | ||
}, | ||
{} as Record<Val, Key>, | ||
); | ||
} |
10 changes: 10 additions & 0 deletions
10
packages/snipsonian-core/src/object/keyVals/getObjectKeyVals.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { TAnyObject } from '../../typings/object'; | ||
import { IKeyValuePair } from '../../typings/patterns'; | ||
|
||
export default function getObjectKeyVals<Value = unknown>(obj: TAnyObject<Value>): IKeyValuePair<Value>[] { | ||
return Object.keys(obj) | ||
.map((key) => ({ | ||
key, | ||
value: obj[key], | ||
})); | ||
} |
74 changes: 74 additions & 0 deletions
74
packages/snipsonian-core/src/object/manipulation/updateObjectField.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import updateObjectField from './updateObjectField'; | ||
import cloneObjectDataProps from '../cloneObjectDataProps'; | ||
|
||
describe('updateObjectField()', () => { | ||
const COMPLEX_OBJ = { | ||
name: 'Doe', | ||
firstName: 'John', | ||
meta: { | ||
version: 3, | ||
languages: ['nl', 'en'], | ||
job: { | ||
category: 'IT', | ||
location: 'Leuven', | ||
}, | ||
children: [{ name: 'Kimberly', age: 10 }, { name: 'Kelly', age: 8 }], | ||
pets: { | ||
0: { name: 'barkly', type: 'dog' }, | ||
1: { name: 'kitkat', type: 'cat' }, | ||
}, | ||
}, | ||
}; | ||
|
||
it('updates the specified field when it is just a top field', () => { | ||
const obj = cloneObjectDataProps(COMPLEX_OBJ); | ||
const expectedObj = cloneObjectDataProps(COMPLEX_OBJ); | ||
|
||
const actual = updateObjectField({ obj, fieldToUpdateRef: 'firstName', val: 'Jane' }); | ||
|
||
expectedObj.firstName = 'Jane'; | ||
expect(actual).toEqual(expectedObj); | ||
}); | ||
|
||
it('updates nested object properties', () => { | ||
const obj = cloneObjectDataProps(COMPLEX_OBJ); | ||
const expectedObj = cloneObjectDataProps(COMPLEX_OBJ); | ||
|
||
const actual = updateObjectField({ obj, fieldToUpdateRef: 'meta.version', val: 44 }); | ||
|
||
expectedObj.meta.version = 44; | ||
expect(actual).toEqual(expectedObj); | ||
}); | ||
|
||
it('updates nested array values', () => { | ||
const obj = cloneObjectDataProps(COMPLEX_OBJ); | ||
const expectedObj = cloneObjectDataProps(COMPLEX_OBJ); | ||
|
||
const actual = updateObjectField({ obj, fieldToUpdateRef: 'meta.languages[1]', val: 'fr' }); | ||
|
||
expectedObj.meta.languages = ['nl', 'fr']; | ||
expect(actual).toEqual(expectedObj); | ||
}); | ||
|
||
it('updates nested arraylike object values', () => { | ||
const obj = cloneObjectDataProps(COMPLEX_OBJ); | ||
const expectedObj = cloneObjectDataProps(COMPLEX_OBJ); | ||
|
||
const actual = updateObjectField({ obj, fieldToUpdateRef: 'meta.pets.1.name', val: 'twix' }); | ||
|
||
expectedObj.meta.pets[1] = { name: 'twix', type: 'cat' }; | ||
expect(actual).toEqual(expectedObj); | ||
}); | ||
|
||
it('updates deeply nested values', () => { | ||
const obj = cloneObjectDataProps(COMPLEX_OBJ); | ||
const expectedObj = cloneObjectDataProps(COMPLEX_OBJ); | ||
|
||
const actualTemp = updateObjectField({ obj, fieldToUpdateRef: 'meta.job.category', val: 'HR' }); | ||
const actual = updateObjectField({ obj: actualTemp, fieldToUpdateRef: 'meta.children[1].age', val: 21 }); | ||
|
||
expectedObj.meta.job.category = 'HR'; | ||
expectedObj.meta.children[1].age = 21; | ||
expect(actual).toEqual(expectedObj); | ||
}); | ||
}); |
47 changes: 47 additions & 0 deletions
47
packages/snipsonian-core/src/object/manipulation/updateObjectField.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import isObject from '../../is/isObject'; | ||
import { TAnyObject } from '../../typings/object'; | ||
import getPossiblyNestedObjectPropValue from '../filtering/getPossiblyNestedObjectPropValue'; | ||
|
||
/** | ||
* 'fieldToUpdateRef' can be something like e.g. "parentField[0].childField". | ||
* This function would then update the 'childField' property of the first element of a | ||
* 'parentField' array (which should be a property of the input 'obj'). | ||
*/ | ||
export default function updateObjectField({ | ||
obj, | ||
fieldToUpdateRef, | ||
val, | ||
}: { | ||
obj: TAnyObject; | ||
fieldToUpdateRef: string; | ||
val: unknown; | ||
}): TAnyObject { | ||
if (!isObject(obj)) { | ||
return obj; | ||
} | ||
|
||
const lastArraySeparator = fieldToUpdateRef.lastIndexOf('['); | ||
const lastObjSeparator = fieldToUpdateRef.lastIndexOf('.'); | ||
|
||
const splitIndex = Math.max(lastArraySeparator, lastObjSeparator); | ||
|
||
if (splitIndex === -1) { | ||
// eslint-disable-next-line no-param-reassign | ||
obj[fieldToUpdateRef] = val; | ||
return obj; | ||
} | ||
|
||
const parentRef = fieldToUpdateRef.substring(0, splitIndex); | ||
const remainingRef = fieldToUpdateRef.substring(splitIndex + 1); | ||
const childKey = (splitIndex === lastArraySeparator) | ||
? remainingRef.substring(0, remainingRef.indexOf(']')) | ||
: remainingRef; | ||
|
||
const getFieldsFromParentRefRegex = /([^.[\]]+)/g; | ||
const pathParts = parentRef.match(getFieldsFromParentRefRegex); | ||
const parent = getPossiblyNestedObjectPropValue(obj, ...pathParts) as TAnyObject; | ||
|
||
parent[childKey] = val; | ||
|
||
return obj; | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/snipsonian-core/src/object/verification/doesAnyObjectValueMatchFilter.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import doesAnyObjectValueMatchFilter from './doesAnyObjectValueMatchFilter'; | ||
|
||
describe('doesAnyObjectValueMatchFilter()', () => { | ||
const testObject = { | ||
id: 3, | ||
name: 'testObject', | ||
description: 'test', | ||
hiddenKey: 'neverShareThisKey', | ||
}; | ||
|
||
it('returns true if any property of the input object matches the input string filter', () => { | ||
expect(doesAnyObjectValueMatchFilter(testObject, '3')).toBeTruthy(); | ||
expect(doesAnyObjectValueMatchFilter(testObject, 'test')).toBeTruthy(); | ||
expect(doesAnyObjectValueMatchFilter(testObject, 'wrong')).toBeFalsy(); | ||
}); | ||
|
||
it('allows to ignore some object properties so that they are not matched against the filter', () => { | ||
const filter = 'neverShare'; | ||
|
||
expect(doesAnyObjectValueMatchFilter( | ||
testObject, | ||
filter, | ||
)).toBeTruthy(); | ||
|
||
expect(doesAnyObjectValueMatchFilter( | ||
testObject, | ||
filter, | ||
{ fieldsToIgnore: ['hiddenKey'] }, | ||
)).toBeFalsy(); | ||
}); | ||
}); |
29 changes: 29 additions & 0 deletions
29
packages/snipsonian-core/src/object/verification/doesAnyObjectValueMatchFilter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import isString from '../../is/isString'; | ||
import isSetString from '../../string/isSetString'; | ||
import { TAnyObject } from '../../typings/object'; | ||
import getObjectKeyVals from '../keyVals/getObjectKeyVals'; | ||
import escapeSpecialCharsForRegex from '../../regex/escapeSpecialCharsForRegex'; | ||
|
||
export default function doesAnyObjectValueMatchFilter( | ||
obj: TAnyObject, | ||
filterValue: string, | ||
options: { fieldsToIgnore?: string[] } = {}, | ||
): boolean { | ||
if (!isSetString(filterValue)) { | ||
/* no filter set, so object matches */ | ||
return true; | ||
} | ||
|
||
const filterRegex = new RegExp(escapeSpecialCharsForRegex(filterValue), 'i'); | ||
const { fieldsToIgnore } = options; | ||
|
||
return getObjectKeyVals(obj) | ||
.filter(({ key }) => !fieldsToIgnore || fieldsToIgnore.indexOf(key) === -1) | ||
.some(({ value = '' }) => { | ||
const fieldValue = isString(value) | ||
? value | ||
: value.toString(); | ||
|
||
return fieldValue.search(filterRegex) > -1; | ||
}); | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/snipsonian-core/src/object/verification/isObjectWithProps.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import isObjectWithProps from './isObjectWithProps'; | ||
|
||
describe('isObjectWithProps()', () => { | ||
it('returns only true if the input is an object with at least 1 property', () => { | ||
expect(isObjectWithProps({ someProp: false })).toEqual(true); | ||
expect(isObjectWithProps({ a: 1, b: 'xyz' })).toEqual(true); | ||
|
||
expect(isObjectWithProps({})).toEqual(false); | ||
expect(isObjectWithProps(undefined)).toEqual(false); | ||
expect(isObjectWithProps(null)).toEqual(false); | ||
expect(isObjectWithProps([])).toEqual(false); | ||
expect(isObjectWithProps('str')).toEqual(false); | ||
}); | ||
}); |
6 changes: 6 additions & 0 deletions
6
packages/snipsonian-core/src/object/verification/isObjectWithProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import isObjectPure from '../../is/isObjectPure'; | ||
import isEmptyObject from './isEmptyObject'; | ||
|
||
export default function isObjectWithProps(val: unknown): boolean { | ||
return isObjectPure(val) && !isEmptyObject(val); | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/snipsonian-core/src/object/verification/isSetObject.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import isSet from '../../is/isSet'; | ||
import isObject from '../../is/isObject'; | ||
|
||
export default function isSetObject(val: unknown) { | ||
return isSet(val) && isObject(val); | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/snipsonian-core/src/regex/escapeSpecialCharsForRegex.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* To be used when e.g. a user-input-string has to be turned into a regex for client-side-searching | ||
*/ | ||
export default function escapeSpecialCharsForRegex(input: string) { | ||
if (!input) { | ||
return input; | ||
} | ||
|
||
return input | ||
.replaceAll('.', '\\.') | ||
.replaceAll('+', '\\+') | ||
.replaceAll('*', '\\*') | ||
.replaceAll('?', '\\?'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export interface IKeyValuePair<Key = string, Value = string> { | ||
export interface IKeyValuePair<Value = string, Key = string> { | ||
key: Key; | ||
value: Value; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters