This is a simple TypeScript-oriented library developed for use in Salesforce TypeScript libraries, applications, and plugins consisting of two parts:
See the API documentation for more details on each of the utilities that ts-types
provides.
We were interested in developing with strict compiler settings in TypeScript. Among the sub-settings that comprise strict mode are strictNullChecks
, strictPropertyInitialization
, and noImplicitAny
. tslint
provides additional rules that further improve code quality and type correctness by discouraging explicit any
usages and other unsafe constructs such as some classes of unwarranted type assertions. Together, these settings have the potential to increase code quality substantially, reducing the frequency of certain classes of runtime errors typical in classical JavaScript applications. They also encourage the writing of clearer, more accurately typed code, which helps teams work together more effectively and more rapidly onboard new hires.
Of course, stricter compiler settings require developers to write more type-safe code -- or to work around the compiler's insistence on type-safety. Often this stricter style leads to more verbose code in the way of type declarations and type guards, and can require new and occasionally unfamiliar patterns to accomplish without subverting the compiler's enforcement of the type system (typically via the use of type assertions).
TypeScript provides both syntax and built-in types designed to help write well-typed code, but we can be more terse and concise by employing additional types and type-narrowing utility functions by leveraging language features like type predicates backed by sound runtime validation, simplified undefined
and null
checking, etc. That's where this library comes in.
This library has its roots in solving the problem of how to handle untyped JSON data in a type-safe way. It was born when we added some basic type declarations to replace the unsafe any
type as a stand-in for JSON data with a type that could capture the range of data available in any JSON construct. This yielded the AnyJson
type, which is effectively a union of all primitive and collection JSON values. The type alone was not enough to make for convenient, type-guarded handling of JSON data, however. TypeScript supports a very elegant system of control flow analysis that will narrow the type of a variable in code after the compiler can prove the set of possible types of the variable has been reduced. Using type guards in your code improves its runtime type safety characteristics, makes it more readable, and provides richer typing information for IDEs. Type guards are implemented as conditional statements, however, and can quickly become noisy and make what was once terse JavaScript code expand into several lines of type checking. This library aimed to simplify the experience of reducing the amount of type guards needed to process a typed-JSON data structure by providing several convenience functions that help extract well-typed data from such JSON structures.
For example, look at the following typical untyped JSON processing in JavaScript:
// concise, but not at all null-safe or type-safe; often made to be at least null-safe using lodash fns
JSON.parse(response.body).results.forEach(item => db.save(item.id, item));
Then a safe version in bare TypeScript using type guards:
const json = JSON.parse(response.body);
// type of json -> `any`, but will not be undefined or JSON.parse would throw
if (json === null && typeof json !== 'object') throw new Error('Unexpected json data type');
let results = json.results;
// type of results -> `any`
if (!Array.isArray(results)) results = [];
// type of results -> `any[]`
results.forEach(item => {
// type of item -> `any`
const id = item.id;
// type of id -> `any`
if (typeof id !== 'string') throw new Error('Unexpected item id data type');
// type of id -> `string`
db.save(id, item);
});
While that's pretty safe, it's also a mess to read and write. That's why this library is here to help!
const json = ensureJsonMap(JSON.parse(response.body));
// type of json -> `JsonMap` or raises an error
const results = asJsonArray(json.results, []);
// type of results -> `JsonArray` or uses the default of `[]`
results.forEach(item => {
// type of item -> `AnyJson`
record = ensureJsonMap(record);
db.save(ensureString(record.id), record);
});
Removing the comments, we can shorten the above somewhat to achieve something not much more complex than the original example, but with robust type and null checking implemented:
asJsonArray(ensureJsonMap(JSON.parse(response.body)).results, []).forEach(item => {
const record = ensureJsonMap(item);
db.save(ensureString(record.id), record);
});
The ensure*
functions are used in this example since they will raise an error when the value being checked either does not exist or does not match the expected type. Additionally, and perhaps more importantly, the generic any
and AnyJson
types get progressively narrowed when using these functions to more specific types. Of course, you don't always want to raise an error when these conditions are not met, so alternative forms exist for each of the JSON data types that allow the types to be tested and narrowed -- see the is*
and as*
variants in the API documentation for testing and narrowing capabilities without additionally raising errors.
After a few iterations of working on the JSON support types and utilities, it became apparent that we needed other non-JSON types and functions that provide similar capabilities. Rather than create a new library for those, we instead grew the scope of this one to contain all of our commonly used types and narrowing functions.
A small library of types is included to help write more concise TypeScript code. These types are in part designed to augment the standard types included with the TypeScript library. Please see the generated API documentation for the complete set of provided types. Here are a few of the most commonly used types:
T | undefined
.undefined
from a type T
, when T
includes undefined
as a union member.Optional<T | null>
, or T | null | undefined
. NonNullable
is a TypeScript built-in that subtracts both null
and undefined
from a type T
with either as a union member.string
-indexed object
of the form { [key: string]: Optional<T> }
.Extract<keyof T, string>
.JsonPrimitive | JsonCollection
.null | string | number| boolean
.Dictionary<AnyJson>
.Array<AnyJson>
.JsonMap | JsonArray
.This library provides several categories of functions to help with safely narrowing variables of broadly typed variables, like unknown
or object
, to more specific types.
The is*
suite of functions accept a variable of a broad type such as unknown
or object
and returns a boolean
type-predicate useful for narrowing the type in conditional scopes.
// type of value -> string | boolean
if (isString(value)) {
// type of value -> string
}
// type of value -> boolean
The as*
suite of functions accept a variable of a broad type such as unknown
or object
and optionally returns a narrowed type after validating it with a runtime test. If the test is negative or if the value was not defined (i.e. undefined
or null
), undefined
is returned instead.
// some function that takes a string or undefined
function upperFirst(s: Optional<string>): Optional<string> {
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
}
// type of value -> unknown
const name = upperFirst(asString(value));
// type of name -> Optional<string>
The ensure*
suite of functions narrow values' types to a definite value of the designated type, or raises an error if the value is undefined
or of an incompatible type.
// type of value -> unknown
try {
const s = ensureString(value);
// type of s -> string
} catch (err) {
// s was undefined, null, or not of type string
}
The has*
suite of functions both tests for the existence and type-compatibility of a given value and, if the runtime value check succeeds, narrows the type to a view of the original value's type intersected with the tested property (e.g. T & { [_ in K]: V }
where K
is the test property key and V
is the test property value type).
// type of value -> unknown
if (hasString(value, 'name')) {
// type of value -> { name: string }
// value can be further narrowed with additional checks
if (hasArray(value, 'results')) {
// type of value -> { name: string } & { results: Array<unknown> }
} else if (hasInstance(value, 'error', Error)) {
// type of value -> { name: string } & { error: Error }
}
}
The get*
suite of functions search an unknown
target value for a given path. Search paths follow the same syntax as lodash
's get
, set
, at
, etc. These functions are more strictly typed, however, increasingly the likelihood that well-typed code stays well-typed as a function's control flow advances.
// imagine response json retrieved from a remote query
const response = {
start: 0,
length: 2,
results: [{ name: 'first' }, { name: 'second' }]
};
const nameOfFirst = getString(response, 'results[0].name');
// type of nameOfFirst = string
The coerce
suite of functions accept values of general types and narrow their types to JSON-specific values. They are named with the coerce
prefix to indicate that they do not perform an exhaustive runtime check of the entire data structure -- only shallow type checks are performed. As a result, only use these functions when you are confident that the broadly typed subject being coerced was derived from a JSON-compatible value. If you are unsure of an object's origins or contents but want to avoid runtime errors handling elements, see the to*
set of functions.
const response = coerceJsonMap(JSON.parse(await http.get('http://example.com/data.json').body));
// type of response -> JsonMap
The to*
suite of functions is a fully type-safe version of the coerce*
functions for JSON narrowing, but at the expense of some runtime performance. Under the hood, the to*
functions perform a JSON-clone of their subject arguments, ensuring that the entire data structure is JSON compatible before returning the narrowed type.
const obj = {
name: 'example',
parse: function(s) {
return s.split(':');
}
};
const json = toJsonMap(obj);
// type of json -> JsonMap
// json = { name: 'example' }
// notice that the parse function has been omitted to ensure JSON-compatibility!
This suite of functions are used to iterate the keys, entries, and values of objects with some typing conveniences applied that are not present in their built-in counterparts (i.e. Object.keys
, Object.entries
, and Object.values
), but come with some caveats noted in their documentation. Typical uses include iterating over the properties of an object with more useful keyof
typings applied during the iterator bodies, and/or filtering out undefined
or null
values before invoking the iterator functions.
const pets: Dictionary<string> = {
fido: 'dog',
bill: 'cat',
fred: undefined
};
// note that the array is typed as [string, string] rather than [string, string | undefined]
function logPet([name, type]: [string, string]) {
console.log('%s is a %s', name, type);
}
definiteEntriesOf(pets).forEach(logPet);
// fido is a dog
// bill is a cat
Another Salesforce TypeScript library, @salesforce/kit, builds on this library to add additional utilities. It includes additional JSON support, a lightweight replacement for some lodash
functions, and growing support for patterns used in other Salesforce CLI libraries and applications.
Any function
returning type T
. T
defaults to unknown
when not explicitly supplied.
Any valid JSON value.
Any valid JSON collection value.
Any valid JSON primitive value.
An alias for the commonly needed Extract<keyof T, string>
.
An alias for a tuple of type [string, T]' for a given generic type
T.
Tdefaults to
unknown` if not otherwise
defined.
Extracts literally defined property names from a type T
as a union of key name strings, minus
any index signatures.
Creates a new Record
type from the literal properties of a type T
, assigning their values
the to the type U
.
This can be useful for creating interfaces from the keys of an enum
so that the keys are
available at runtime for meta-programming purposes, while both tying the properties of the
generated type to the enum keys and remaining as DRY as possible.
enum QUERY_KEY { id, name, created, updated }
// type of QUERY_KEY -> {
// [x: number]: number;
// readonly id: number;
// readonly name: number;
// readonly created: number;
// readonly updated: number;
// }
interface QueryRecord extends LiteralsRecord<typeof QUERY_KEY, string> { }
// type of QueryRecord -> {
// readonly id: string;
// readonly name: string;
// readonly created: string;
// readonly updated: string;
// }
// And for an interface with writable properties, use the following:
interface QueryRecord extends ReadWrite<LiteralsRecord<typeof QUERY_KEY, string>> { }
// type of QueryRecord -> {
// id: string;
// name: string;
// created: string;
// updated: string;
// }
A union type for either the parameterized type T
or an array of T
.
Subtracts undefined
from any union type T
. This is the opposite of Optional.
A union type for either the parameterized type T
, null
, or undefined
-- the opposite of
the NonNullable
builtin conditional type.
Creates a new type that omits keys in union type K
of a target type T
.
A union type for either the parameterized type T
or undefined
-- the opposite of NonOptional.
Returns a new type consisting of all properties declared for an input type T2
overlaid on the
properties of type T1
. Any definitions in T2
replace those previously defined in T1
. This can
be useful for redefining the types of properties on T1
with values from an inline type T2
, perhaps to
change their type or to make them optional.
type NameAndStringValue = { name: string, value: string }
type NameAndOptionalNumericValue = Overwrite<NameAndValue, { value?: number }>
// type of NameAndOptionalNumericValue -> { name: string } & { value?: number | undefined }
Converts readonly properties of a type T
to writable properties. This is the opposite of the
Readonly<T>
builtin mapped type.
Converts a type T
that may have optional, nullable properties into a new type with only required
properties, while also subtracting null
from all possible property values.
type Foo = { bar?: string | undefined | null };
type RequiredNonNullableFoo = RequiredNonNullable<Foo>;
// RequiredNonNullableFoo -> { bar: string };
Converts a type T
that may have optional properties into a type T
with only required
properties (e.g. undefined
values are not allowed). Explicit null
s in value unions
will still be possible. This is similar to the Required
builtin mapped type, but also
subtracts undefined
from value union types as well as the optional property declaration.
type Foo = { bar?: string | undefined | null };
type RequiredNonOptionalFoo = RequiredNonOptional<Foo>;
// RequiredNonOptionalFoo -> { bar: string | null };
A view over an object
with constrainable properties.
Narrows an unknown
value to an Array
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to an object
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to a boolean
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to a boolean
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to an AnyFunction
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to an object
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to an instance of constructor type T
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to an object
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an AnyJson
value to a JsonArray
if it is type-compatible, or returns undefined
otherwise.
Narrows an AnyJson
value to a JsonArray
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if the value was undefined or of the incorrect type.
Narrows an AnyJson
value to a JsonMap
if it is type-compatible, or returns undefined
otherwise.
Narrows an AnyJson
value to a JsonMap
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to a number
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to a number
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to an object
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to an object
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to a plain object
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to an object
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to a string
if it is type-compatible, or returns undefined
otherwise.
The value to test.
Narrows an unknown
value to a string
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an unknown
value to an AnyJson
if it is type-compatible*, or returns undefined
otherwise.
* This is not a 100% safe operation -- it will not deeply validate plain object or array structures
to ensure that they contain only AnyJson
values. When type-narrowing potential objects or arrays with this
function, it's the responsibility of the caller to understand the risks of making such a shallow type assertion
over the value
data.
The value to test.
Narrows an unknown
value to an AnyJson
if it is type-compatible, or returns the provided default otherwise.
The value to test.
The default to return if value
was undefined or of the incorrect type.
Narrows an array of type T
to a JsonArray
using a shallow type-compatibility check. Use this when the source of
the array is known to be JSON-compatible and you want simple type coercion to a JsonArray
. Use toJsonArray
instead when the value
array cannot be guaranteed to be JSON-compatible and you want an assurance of runtime
type safety. This is a shortcut for writing asJsonArray(coerceAnyJson(value))
.
The array to coerce.
Narrows an array of type T
to a JsonArray
using a shallow type-compatibility check. Use this when the source of
the array is known to be JSON-compatible and you want simple type coercion to a JsonArray
. Use toJsonArray
instead when the value
array cannot be guaranteed to be JSON-compatible and you want an assurance of runtime
type safety. This is a shortcut for writing asJsonArray(coerceAnyJson(value)) || defaultValue
.
The array to coerce.
The default to return if value
was not defined.
Narrows an object of type T
to a JsonMap
using a shallow type-compatibility check. Use this when the source of
the object is known to be JSON-compatible and you want simple type coercion to a JsonMap
. Use toJsonMap
instead when the value
object cannot be guaranteed to be JSON-compatible and you want an assurance of runtime
type safety. This is a shortcut for writing asJsonMap(coerceAnyJson(value))
.
The object to coerce.
Narrows an object of type T
to a JsonMap
using a shallow type-compatibility check. Use this when the source of
the object is known to be JSON-compatible and you want simple type coercion to a JsonMap
. Use toJsonMap
instead when the value
object cannot be guaranteed to be JSON-compatible and you want an assurance of runtime
type safety. This is a shortcut for writing asJsonMap(coerceAnyJson(value)) || defaultValue
.
The object to coerce.
The default to return if value
was not defined.
Returns an array of all entry tuples of type [K, NonNullable<T[K]>]
in an object T
whose values are neither
null
nor undefined
. This can be convenient for enumerating the entries of unknown objects with optional properties
(including Dictionary
s) without worrying about performing checks against possibly undefined
or null
values.
See also caveats outlined in entriesOf.
The object of interest.
Returns an array of all string
keys in an object of type T
whose values are neither null
nor undefined
.
This can be convenient for enumerating the keys of definitely assigned properties in an object or Dictionary
.
See also caveats outlined in keysOf.
The object of interest.
Returns an array of all values of type T
in an object T
for values that are neither null
nor undefined
.
This can be convenient for enumerating the values of unknown objects with optional properties (including
Dictionary
s) without worrying about performing checks against possibly undefined
or null
values.
The object of interest.
Narrows a type Nullable<T>
to a T
or raises an error.
The value to test.
The error message to use if value
is undefined
or null
.
Narrows an unknown
value to an Array
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to a boolean
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to an AnyFunction
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to instance of constructor type T
if it is type-compatible, or raises an error
otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to a number
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to an object
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to an object
if it is type-compatible and tests positively with isPlainObject,
or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Narrows an unknown
value to a string
if it is type-compatible, or raises an error otherwise.
The value to test.
The error message to use if value
is not type-compatible.
Returns the entries of an object of type T
. This is like Object.entries
except the return type
captures the known keys and value types of T
.
Note that it is the responsibility of the caller to use this wisely -- there are cases where
the runtime set of entries returned may be broader than the type checked set at compile time,
so there's potential for this to be abused in ways that are not inherently type safe. For
example, given base class Animal
, subclass Fish
, and const animal: Animal = new Fish();
then entriesOf(animal)
will not type-check the entire set of keys of the object animal
since
it is actually an instance of type Fish
, which has an extended property set.
In general, it should be both convenient and type-safe to use this when enumerating the entries of simple data objects with known properties.
interface Point { x: number; y: number; }
const point: Point = { x: 1, y: 2 };
// type of entries -> ['x' | 'y', number][]
const entries = entriesOf(point);
for (const entry of entries) {
console.log(entry[0], entry[1]);
}
// x 1
// y 2
The object of interest.
Given a deep-search query path, returns an object property or array value of an object or array.
const obj = { foo: { bar: ['baz'] } };
const value = get(obj, 'foo.bar[0]');
// type of value -> unknown; value === 'baz'
const value = get(obj, 'foo.bar.nothing', 'default');
// type of value -> unknown; value === 'default'
const value = get(obj, 'foo["bar"][0]');
// type of value -> unknown; value === 'baz'
const arr = [obj];
const value = get(arr, '[0].foo.bar[0]');
// type of value -> unknown; value === 'baz'
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of a JsonCollection as an
AnyJson, or undefined
if a value was not found or was not type-compatible.
See coerceAnyJson for caveats regarding shallow type detection of AnyJson
values from untyped sources.
const obj = { foo: { bar: [{ a: 'b' }] } };
const value = getAnyJson(obj, 'foo.bar[0]');
// type of value -> AnyJson; value -> { a: 'b' }
Given a deep-search query path, returns an object property or array value of a JsonCollection as an AnyJson, or the given default if a value was not found or was not type-compatible.
const obj = { foo: { bar: [{ a: 'b' }] } };
const value = getAnyJson(obj, 'foo.bar[1]', { c: 'd' });
// type of value -> AnyJson; value -> { c: 'd' }
The JSON value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as an
AnyArray, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1, 2, 3] } };
const value = getArray(obj, 'foo.bar');
// type of value -> AnyArray; value -> [1, 2, 3]
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as an
AnyArray, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1, 2, 3] } };
const value = getArray(obj, 'foo.baz', [4, 5, 6]);
// type of value -> AnyArray; value -> [4, 5, 6]
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as a boolean
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [true] } };
const value = getBoolean(obj, 'foo.bar[0]');
// type of value -> boolean; value -> true
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as a boolean
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [true] } };
const value = getBoolean(obj, 'foo.bar[1]', false);
// type of value -> boolean; value -> false
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as an
AnyFunction, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [(arg: string) => `Hi, ${arg}`] } };
const value = getFunction(obj, 'foo.bar[0]');
// type of value -> AnyArray; value -> (arg: string) => `Hi, ${arg}`
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as an
AnyFunction, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [(arg: string) => `Hi, ${arg}`] } };
const value = getFunction(obj, 'foo.bar[1]', (arg: string) => `Bye, ${arg}`);
// type of value -> AnyArray; value -> (arg: string) => `Bye, ${arg}`)
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as an instance of
class type C
, or undefined
if a value was not found or was not type-compatible.
class Example { ... }
const obj = { foo: { bar: [new Example()] } };
const value = getInstance(obj, 'foo.bar[0]', Example);
// type of value -> Example
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as an instance of
class type C
, or undefined
if a value was not found or was not type-compatible.
class Example { ... }
const obj = { foo: { bar: [new Example()] } };
const value = getInstance(obj, 'foo.bar[0]', Example);
// type of value -> Example; value -> new Example()
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value from an AnyJson as a
JsonArray, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1, 2, 3] } };
const value = getJsonArray(obj, 'foo.bar');
// type of value -> JsonArray; value -> [1, 2, 3]
Given a deep-search query path, returns an object property or array value from an AnyJson as a JsonArray, or the given default if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1, 2, 3] } };
const value = getJsonArray(obj, 'foo.baz', [4, 5, 6]);
// type of value -> JsonArray; value -> [4, 5, 6]
The JSON value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value from an AnyJson as a
JsonMap, or undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [{ a: 'b' }] } };
const value = getJsonMap(obj, 'foo.bar[0]');
// type of value -> JsonMap; value -> { a: 'b' }
Given a deep-search query path, returns an object property or array value from an AnyJson as a JsonMap, or the given default if a value was not found or was not type-compatible.
const obj = { foo: { bar: [{ a: 'b' }] } };
const value = getJsonMap(obj, 'foo.bar[1]', { c: 'd' });
// type of value -> JsonMap; value -> { c: 'd' }
The JSON value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as a number
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1] } };
const value = getNumber(obj, 'foo.bar[0]');
// type of value -> number; value -> 1
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as a number
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [1] } };
const value = getNumber(obj, 'foo.bar[1]', 2);
// type of value -> number; value -> 2
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as an object
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [{ name: 'baz' }] } };
const value = getObject(obj, 'foo.bar[0]');
// type of value -> object; value -> { name: 'baz' }
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as an object
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: [{ name: 'baz' }] } };
const value = getObject(obj, 'foo.bar[1]', { name: 'buzz' });
// type of value -> object; value -> { name: 'buzz' }
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as an object
, or
undefined
if a value was not found or was not type-compatible. This differs from getObject by way of
testing for the property value type compatibility using isPlainObject instead of isObject.
const obj = { foo: { bar: [{ name: 'baz' }] } };
const value = getPlainObject(obj, 'foo.bar[0]');
// type of value -> object; value -> { name: 'baz' }
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as an object
, or
undefined
if a value was not found or was not type-compatible. This differs from getObject by way of
testing for the property value type compatibility using isPlainObject instead of isObject.
const obj = { foo: { bar: [{ name: 'baz' }] } };
const value = getPlainObject(obj, 'foo.bar[1]', { name: 'buzz' });
// type of value -> object; value -> { name: 'buzz' }
Any value to query.
The query path.
The default to return if the query result was not defined.
Given a deep-search query path, returns an object property or array value of an object or array as a string
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: ['baz'] } };
const value = getString(obj, 'foo.bar[0]');
// type of value -> string; value -> 'baz'
Any value to query.
The query path.
Given a deep-search query path, returns an object property or array value of an object or array as a string
, or
undefined
if a value was not found or was not type-compatible.
const obj = { foo: { bar: ['baz'] } };
const value = getString(obj, 'foo.bar[1]', 'default');
// type of value -> string; value -> 'default'
Any value to query.
The query path.
The default to return if the query result was not defined.
Tests whether a value of type T
contains one or more property keys
. If so, the type of the tested value is
narrowed to reflect the existence of those keys for convenient access in the same scope. Returns false if the
property key does not exist on the target type, which must be an object. Returns true if the property key exists,
even if the associated value is undefined
or null
.
// type of obj -> unknown
if (has(obj, 'name')) {
// type of obj -> { name: unknown }
if (has(obj, 'data')) {
// type of obj -> { name: unknown } & { data: unknown }
} else if (has(obj, ['error', 'status'])) {
// type of obj -> { name: unknown } & { error: unknown, status: unknown }
}
}
The value to test.
One or more string
keys to check for existence.
Tests whether a value of type T
contains a property key
of type AnyJson, using a shallow test for
AnyJson
compatibility (see isAnyJson for more information). If so, the type of the
tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns
false
if the property key does not exist on the object or the value stored by that key is not of type
AnyJson.
// type of obj -> unknown
if (hasAnyJson(obj, 'body')) {
// type of obj -> { body: AnyJson }
}
The value to test.
Tests whether a value of type T
contains a property key
of type AnyArray. If so, the type of the tested
value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if
the property key does not exist on the object or the value stored by that key is not of type AnyArray.
// type of obj -> unknown
if (hasNumber(obj, 'offset')) {
// type of obj -> { offset: number }
if (hasNumber(obj, 'page') && hasArray(obj, 'items')) {
// type of obj -> { offset: number } & { page: number } & { items: AnyArray }
}
}
The value to test.
Tests whether a value of type T
contains a property key
of type boolean
. If so, the type of the tested value is
narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if the
property key does not exist on the object or the value stored by that key is not of type boolean
.
// type of obj -> unknown
if (hasBoolean(obj, 'enabled')) {
// type of obj -> { enabled: boolean }
if (hasBoolean(obj, 'hidden')) {
// type of obj -> { enabled: boolean } & { hidden: boolean }
}
}
The value to test.
Tests whether a value of type T
contains a property key
of type AnyFunction. If so, the type of the
tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns
false
if the property key does not exist on the object or the value stored by that key is not of type
AnyFunction.
// type of obj -> unknown
if (hasFunction(obj, 'callback')) {
// type of obj -> { callback: AnyFunction }
obj.callback(response);
}
The value to test.
Tests whether a value of type T
contains a property key
whose type tests positively when tested with
isInstance when compared with the given constructor type C
. If so, the type of the tested value is
narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if the
property key does not exist on the object or the value stored by that key is not an instance of C
.
class ServerResponse { ... }
// type of obj -> unknown
if (hasNumber(obj, 'status')) {
// type of obj -> { status: number }
if (hasInstance(obj, 'data', ServerResponse)) {
// type of obj -> { status: number } & { data: ServerResponse }
} else if (hasString('error')) {
// type of obj -> { status: number } & { error: string }
}
}
The value to test.
Tests whether a value of type T extends AnyJson
contains a property key
of type JsonArray. If so, the
type of the tested value is narrowed to reflect the existence of that key for convenient access in the same scope.
Returns false
if the property key does not exist on the object or the value stored by that key is not of type
JsonArray.
// type of obj -> unknown
if (hasJsonArray(obj, 'body')) {
// type of obj -> { body: JsonArray }
}
The value to test.
Tests whether a value of type T extends AnyJson
contains a property key
of type JsonMap. If so, the type
of the tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns
false
if the property key does not exist on the object or the value stored by that key is not of type
JsonMap.
// type of obj -> unknown
if (hasJsonMap(obj, 'body')) {
// type of obj -> { body: JsonMap }
}
The value to test.
Tests whether a value of type T
contains a property key
of type number
. If so, the type of the tested value is
narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if the
property key does not exist on the object or the value stored by that key is not of type number
.
// type of obj -> unknown
if (hasNumber(obj, 'offset')) {
// type of obj -> { offset: number }
if (hasNumber(obj, 'page') && hasArray(obj, 'items')) {
// type of obj -> { offset: number } & { page: number } & { items: Array<unknown> }
}
}
The value to test.
Tests whether a value of type T
contains a property key
of type object
. If so, the type of the tested value is
narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if the
property key does not exist on the object or the value stored by that key is not of type object
.
// type of obj -> unknown
if (hasNumber(obj, 'status')) {
// type of obj -> { status: number }
if (hasObject(obj, 'data')) {
// type of obj -> { status: number } & { data: object }
} else if (hasString('error')) {
// type of obj -> { status: number } & { error: string }
}
}
The value to test.
Tests whether a value of type T
contains a property key
whose type tests positively when tested with
isPlainObject. If so, the type of the tested value is narrowed to reflect the existence of that key for
convenient access in the same scope. Returns false
if the property key does not exist on the object or the value
stored by that key is not of type object
.
// type of obj -> unknown
if (hasNumber(obj, 'status')) {
// type of obj -> { status: number }
if (hasPlainObject(obj, 'data')) {
// type of obj -> { status: number } & { data: object }
} else if (hasString('error')) {
// type of obj -> { status: number } & { error: string }
}
}
The value to test.
Tests whether a value of type T
contains a property key
of type string
. If so, the type of the tested value is
narrowed to reflect the existence of that key for convenient access in the same scope. Returns false
if the
property key does not exist on the object or the value stored by that key is not of type string
.
// type of obj -> unknown
if (hasString(obj, 'name')) {
// type of obj -> { name: string }
if (hasString(obj, 'message')) {
// type of obj -> { name: string } & { message: string }
}
}
The value to test.
Tests whether unknown
value is a valid JSON type. Note that objects and arrays are only checked using a shallow
test. To be sure that a given value is JSON-compatible at runtime, see toAnyJson.
The value to test.
Tests whether an unknown
value is an Array
.
The value to test.
Tests whether an unknown
value conforms to AnyArrayLike.
The value to test.
Tests whether an unknown
value is a boolean
.
The value to test.
Tests whether an unknown
value is a class constructor that is either equal to or extends another class
constructor.
The value to test.
The class to test against.
Tests whether an unknown
value is a function
.
The value to test.
Tests whether an unknown
value is a function
.
The value to test.
Tests whether or not a key
string is a key of the given object type T
.
The target object to check the key in.
The string to test as a key of the target object.
Tests whether an unknown
value is a number
.
The value to test.
Tests whether an unknown
value is an Object
subtype (e.g., arrays, functions, objects, regexes,
new Number(0), new String(''), and new Boolean(true)). Tests that wish to distinguish objects that
were created from literals or that otherwise were not created via a non-Object
constructor and do
not have a prototype chain should instead use isPlainObject.
The value to test.
Tests whether or not an unknown
value is a plain JavaScript object. That is, if it is an object created
by the Object constructor or one with a null prototype
.
The value to test.
Tests whether an unknown
value is a string
.
The value to test.
Returns the keys of an object of type T
. This is like Object.keys
except the return type
captures the known keys of T
.
Note that it is the responsibility of the caller to use this wisely -- there are cases where
the runtime set of keys returned may be broader than the type checked set at compile time,
so there's potential for this to be abused in ways that are not inherently type safe. For
example, given base class Animal
, subclass Fish
, and const animal: Animal = new Fish();
then keysOf(animal)
will not type-check the entire set of keys of the object animal
since
it is actually an instance of type Fish
, which has an extended property set.
In general, it should be both convenient and type-safe to use this when enumerating the keys of simple data objects with known properties.
interface Point { x: number; y: number; }
const point: Point = { x: 1, y: 2 };
const keys = keysOf(point);
// type of keys -> ('a' | 'b')[]
for (const key of keys) {
console.log(key, point[key]);
}
// x 1
// y 2
The object of interest.
Narrows an object of type T
to an AnyJson
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceAnyJson(unknown)
to type-narrow an arbitrary value to an AnyJson
when the value's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Use coerceAnyJson(unknown)
when the value
object can be guaranteed to be JSON-compatible
and only needs type coercion.
The value to convert.
Narrows an object of type T
to an AnyJson
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceAnyJson(unknown)
to type-narrow an arbitrary value to an AnyJson
when the value's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Use coerceAnyJson(unknown)
when the value
object can be guaranteed to be JSON-compatible
and only needs type coercion.
The value to convert.
The default to return if value
was not defined.
Narrows an array of type T
to a JsonArray
following a deep, brute-force conversion of the array's data to
only consist of JSON-compatible values by performing a basic JSON clone on the array. This is preferable to
using the weaker coerceJsonArray(array)
to type-narrow an arbitrary array to a JsonArray
when the array's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Non-JSON entries will be converted to null
s. Use coerceJsonArray(array)
when the value
object can be guaranteed to be JSON-compatible and only needs type coercion.
The array to convert.
Narrows an array of type T
to a JsonArray
following a deep, brute-force conversion of the array's data to
only consist of JSON-compatible values by performing a basic JSON clone on the array. This is preferable to
using the weaker coerceJsonArray(array)
to type-narrow an arbitrary array to a JsonArray
when the array's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Non-JSON entries will be converted to null
s. Use coerceJsonArray(array)
when the value
object can be guaranteed to be JSON-compatible and only needs type coercion.
The array to convert.
Narrows an object of type T
to a JsonMap
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceJsonMap(object)
to type-narrow an arbitrary array to a JsonMap
when the object's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Non-JSON entries will be converted to null
s. Use coerceJsonArray(array)
when the value
object can be guaranteed to be JSON-compatible and only needs type coercion.
The array to convert.
The default to return if the value was undefined or of the incorrect type.
Narrows an object of type T
to a JsonMap
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceJsonMap(object)
to type-narrow an arbitrary object to a JsonMap
when the object's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Use coerceJsonMap(object)
when the value
object can be guaranteed to be JSON-compatible
and only needs type coercion.
The object to convert.
Narrows an object of type T
to a JsonMap
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceJsonMap(object)
to type-narrow an arbitrary object to a JsonMap
when the object's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Use coerceJsonMap(object)
when the value
object can be guaranteed to be JSON-compatible
and only needs type coercion.
The object to convert.
Narrows an object of type T
to a JsonMap
following a deep, brute-force conversion of the object's data to
only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to
using the weaker coerceJsonMap(object)
to type-narrow an arbitrary object to a JsonMap
when the object's source
is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type
safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting
JSON data structure. Use coerceJsonMap(object)
when the value
object can be guaranteed to be JSON-compatible
and only needs type coercion.
The object to convert.
The default to return if value
was not defined.
Returns the values of an object of type T
. This is like Object.values
except the return type
captures the possible value types of T
.
Note that it is the responsibility of the caller to use this wisely -- there are cases where
the runtime set of values returned may be broader than the type checked set at compile time,
so there's potential for this to be abused in ways that are not inherently type safe. For
example, given base class Animal
, subclass Fish
, and const animal: Animal = new Fish();
then valuesOf(animal)
will not type-check the entire set of values of the object animal
since
it is actually an instance of type Fish
, which has an extended property set.
In general, it should be both convenient and type-safe to use this when enumerating the values of simple data objects with known properties.
interface Point { x: number; y: number; }
const point: Point = { x: 1, y: 2 };
const values = valuesOf(point);
// type of values -> number[]
for (const value of values) {
console.log(value);
}
// 1
// 2
The object of interest.
A constructor for any type
T
.T
defaults toobject
when not explicitly supplied.