typedots
v0.3.1
Published
A simple way to get and set object properties using paths (aka dot notation) with TypeScript support
Maintainers
Readme
Features
Runtime
- [x] Get object property value from path:
get(obj, 'prop.subprop') - [x] Set object property value from path:
set(obj, 'prop.subprop', 'value') - [x] Check if object has property from path:
has(obj, 'prop.subprop') - [x] Supports property names containing dots:
{'my.property': true}
Type system (while developing)
- [x] Path argument is autocompleted based on the object you pass.
- [x] Ability to filter out suggested paths based on a given type.
Install
# Using npm
npm i typedots
# Using Yarn
yarn add typedots
# Using pnpm
pnpm add typedotsUsage
There are two ways of consuming typedots depending on your needs and preferences:
Directly use base methods
import { get, set, has } from 'typedots';Advanced use through class
The class implements the exact same base methods.
import Typedots from 'typedots';
const td = new Typedots(); // td.get, td.set, td.hasIt is mainly used to get finer control over the way typedots' type system behaves as further explored in Advanced usage.
Methods
get
Error handling
When handleErrors is set to "throw", Typedots will throw a InvalidPathError whenever it encounters an unresolvable path instead of returning undefined. Typedots will only console.warn and continue runtime execution. Unresolvable paths include non-existing properties and intermediate properties that are not objects.
set
Error handling
When handleErrors is set to "throw", Typedots will throw a InvalidPathError whenever it encounters an untraversable path instead of just ignoring it. If force is set to true, Typedots will create nested object structure as long as it doesn't encounter a defined path segment that is not an object.
has
Error handling
When handleErrors is set to "throw", Typedots will throw a InvalidPathError whenever it encounters an unresolvable path instead of returning false. Typedots will only console.warn and continue runtime execution. Unresolvable paths include non-existing properties and intermediate properties that are not objects.
Note When one of the properties in the path contains a dot, such property should be wrapped with parentheses so that it does not conflict with typedots inner workings. e.g.
"sites.(my.host.com).ip"
Advanced usage
By default, using the base methods does not enforce the path arguments to actually strictly match one of the values provided by autocompletion. Those are just bare suggestions and you may pass any other string without TypeScript yelling at you.
Additionally, when drawing up the list of suggestions, all paths resolving to non-object properties are automatically picked.
Both behaviours may be tweaked by making use of the Typedots class instead of the base methods. It provides a generic type which is constrained to the following interface:
export interface TypedotsParams = {
expectedType?: any;
preventDistribution?: boolean;
}Filter out suggestions by type
You may want to add some constraints to the list of suggested paths. To achieve this, you can use typedots' class and set the expectedType parameter in its generic type. Once set, only properties matching the type you passed in expectedType will be suggested in the path arguments. Here's a quick example which should only suggest paths resolving to functions:
const obj = {
myProperty: 'value of my property',
myMethod: (message) => `received ${message}`,
helpers: {
maxLength: 255,
count(item) {
return item.length;
},
},
};
const typedots = new Typedots<{ expectedType: (...args: any) => any }>();
const method = typedots.get(obj, ''); // <-- should only suggest "myMethod" and "helpers.count"Please note that typedots relies on TypeScript's own type inference mechanism. This means its behaviour may be influenced by TypeScript's configuration.
False, undefined and null values
For example, strictNullChecks changes the way types are infered:
/** > When `strictNullChecks: false`: */
// infered as { prop, string un: any }
const obj = { prop: 'some string', un: undefined };
new Typedots<{ expectedType: string }>(); // suggests "prop" | "un"
/** > When `strictNullChecks: true`: */
// infered as { prop: string, un: undefined }
const obj = { prop: 'some string', un: undefined };
new Typedots<{ expectedType: string }>(); // suggests "prop"Boolean distributivity
Because conditional types in generic parameters are distributive, when specifically expecting true or false types, paths resolving to boolean are also suggested since they match (boolean = true | false).
// infered as { one: boolean, two: boolean, tree: boolean }
const obj = { one: false, two: true, three: true as boolean };
new Typedots<{ expectedType: true }>(); // suggests "one" | "two" | "three"
// infered as { one: false, two: true, tree: boolean }
const obj = { one: false, two: true, three: true as boolean } as const;
new Typedots<{ expectedType: true }>(); // suggests "two" | "three"You may want to go even stricter by preventing boolean to be distributed when expecting false or true. To achieve this, you can use typedots' class and set the preventDistribution parameter in its generic type:
// infered as { one: boolean, two: boolean, tree: boolean }
const obj = { one: false, two: true, three: true as boolean };
new Typedots<{ expectedType: true; preventDistribution: true }>(); // no suggestion
// infered as { one: false, two: true, tree: boolean }
const obj = { one: false, two: true, three: true as boolean } as const;
new Typedots<{ expectedType: true; preventDistribution: true }>(); // suggests "two"Exported type
The type which powers the suggestion system is exported as ExtractObjectPaths, you may be interested in using it even if you're not actually using the runtime methods. It takes three generic parameters:
BaseObject, which extends any non-null object.ExpectedType, see Filter out suggestions by typePreventDistribution, see Boolean distributivity
Examples
import { get, set, has } from 'typedots';
const variableName = 'content';
const baseObject = {
'prop1': true,
'prop2': false,
'prop3': {
subprop1: 'string',
subprop2: ['first', 2_000, { third: undefined }],
subprop3: { one: true, two: true, three: false },
subprop4: undefined,
},
[variableName]: {},
'prop.5': { 'nested': 'string', 'another.sub.prop': {} },
};
get(baseObject, 'prop1'); // true
get(baseObject, 'prop3.subprop1'); // "string"
get(baseObject, 'prop3.subprop3.three'); // false
get(baseObject, '(prop.5).nested'); // 'string'
get(baseObject, 'content'); // {}
get(baseObject, variableName); // {}
set(baseObject, 'prop1', value); // true
set(baseObject, 'prop100', value, false); // false
set(baseObject, 'prop100', value); // true
set(baseObject, 'prop2.child', value, false); // false
set(baseObject, 'prop2.child', value); // true
has(baseObject, 'prop1'); // true
has(baseObject, 'prop3.NOOP'); // false
has(baseObject, 'NOPE'); // false
has(baseObject, 'prop3.subprop4'); // true
has(baseObject, '(prop.5).(another.sub.prop)'); // trueUntypedots
You may want to benefit from the runtime methods provided by Typedots without enforcing type safety. This can be useful in scenarios where you are dealing with dynamic objects or or encountering the following error:
Type instantiation is excessively deep and possibly infinite.
This usually happens when passing any which makes it impossible for TypeScript to infer any type and for Typedots to provide any type suggestions. Typedots does not handle this on purpose because it could silence relevant type errors that you would otherwise want to be aware of, which would defeat one of the core purposes of this library.
It shares the same API as the regular Typedots class, but doesn't take any type parameters.
import { Untypedots } from 'typedots';
new Untypedots();
// .has(…)
// .get(…)
// .set(…)