Objects
properties
Objects definitions can include any combination of required, optional, defaultable named properties and index signatures.
required
optional
Optional properties cannot be present with the value undefined
In TypeScript, there is a setting called exactOptionalPropertyTypes
that can be set to true
to enforce the distinction between properties that are missing and properties that are present with the value undefined
.
ArkType mirrors this behavior by default, so if you want to allow undefined
, you'll need to add it to your value's definition. If you're interested in a builtin configuration option for this setting, we'd love feedback or contributions on this issue.
See an example
defaultable
Optional and default only work within objects and tuples!
Unlike e.g. number.array()
, number.optional()
and number.default(0)
don't return a new Type
, but rather a tuple definition like [Type<number>, "?"]
or [Type<number>, "=", 0]
.
This reflects the fact that in ArkType's type system, optionality and defaultability are only meaningful in reference to a property. Attempting to create an optional or defaultable value outside an object like type("string?")
will result in a ParseError
.
To create a Type
accepting string
or undefined
, use a union like type("string | undefined")
.
To have it transform undefined
to an empty string, use an explicit morph like:
index
undeclared
TypeScript's structural type system explicitly allows assigning objects with additional keys so long as all declared constraints are satisfied. ArkType mirrors this behavior by default because generally...
- Existing objects can be reused more often.
- Validation is much more efficient if you don't need to check for undeclared keys.
- Extra properties don't usually matter as long as those you've declared are satisfied.
However, sometimes the way you're using the object would make undeclared properties problematic. Even though they can't be reflected by TypeScript (yet- please +1 the issue!), ArkType does support rejection or deletion of undeclared keys. This behavior can be defined for individual objects using the syntax below or via configuration if you want to change the default across all objects.
spread
The spread operator is great for merging sets of properties. When applied to two distinct (i.e. non-overlapping) sets of properties, it is equivalent to intersection. However, if a key appears in both the base and merged objects, the base value will be discarded in favor of the merged rather than recursively intersected.
Spreading bypasses a lot of the behavioral complexity and computational overhead of an intersection and should be the preferred method of combining property sets.
A base object definition can be spread if "..."
is the first key specified in an object literal. Subsequent properties will be merged into those from the base object, just like the ...
operator in JS.
The spread operator is semantically equivalent to the generic Merge
keyword, which can be instantiated via a dedicated method on Type
in addition to the standard keyword syntax.
keyof
Like in TypeScript, the keyof
operator extracts the keys of an object as a union:
Also like in TypeScript, if an object includes an index signature like [string]
alongside named properties, the union from keyof
will reduce to string
:
ArkType's `keyof` will never include `number`
Though TypeScript's keyof
operator can yield a number
, the concept of
numeric keys does not exist in JavaScript at runtime. This leads to confusing
and inconsistent behavior. In ArkType, keyof
will always return a string
or symbol
in accordance with the construction of a JavaScript object.
Learn more about our motivation for diverging from TypeScript on this issue
In JavaScript, you can use a number literal to define a key, but the constructed value has no way to represent a numeric key, so it is coerced to a string.
For a set-based type system to be correct, any two types representing the same set of underlying values must share a single representation. TypeScript's decision to have distinct numeric and string representations for the same underlying key has led to some if its most confusing inference pitfalls:
This sort of inconsistency is inevitable for a type system that has to reconcile multiple representations
for identical sets of underlying values. Therefore, numeric keys are one of a handful of cases where ArkType intentionally diverges from TypeScript. ArkType will never return a number
from keyof
. Keys will always be normalized to a string
or symbol
, the two distinct property types that can be uniquely attached to a JavaScript object.
get
Like an index access expression in TypeScript (e.g. User["name"]
), the get
operator extracts the Type of a value based on a specified key definition from an object:
Expressions like `get` and `omit` that extract a portion of an exising Type can be an antipattern!
Before using get
to extract the type of a property you've defined, consider
whether you may be able to define the property value directly as a standalone
Type that can be easily referenced and composed as needed.
Usually, composing Types from the bottom up is clearer and more efficient than trying to rip the part you need out of an existing Type.
Though cases like this are quite straightforward, there are number of more nuanced behaviors to consider when accessing an arbitrary key that could be a union, literal, or index signature on an object Type that could also be a union including optional keys or index signatures.
If you're interested in a deeper dive into this (or anything else in ArkType), our unit tests are the closest thing we have to a comprehensive spec.
Not your cup of tea? No worries- the inferred types and errors you'll see in editor will always be guiding you in the right direction ðŸ§
Support for TypeScript's index access syntax is planned!
Leave a comment on the issue letting us know if you're interested in using- or even helping implement- type-level parsing for string-embedded index access 🤓
arrays
lengths
Constrain an array with an inclusive or exclusive min or max length.
Range expressions allow you to specify both a min and max length and use the same syntax for exclusivity.
tuples
Like objects, tuples are structures whose values are nested definitions. Like TypeScript, ArkType supports prefix, optional, variadic, and postfix elements, with the same restrictions about combining them.
prefix
defaultable
Defaultable elements are optional elements that will be assigned their specified default if not present in the tuple's input.
A tuple may include zero or more defaultable elements following its prefix elements and preceding its non-defaultable optional elements.
Like optional elements, defaultable elements are mutually exclusive with postfix elements.
optional
Optional elements are tuple elements that may or may not be present in the input that do not have a default value.
A tuple may include zero or more optional elements following its prefix and defaultable elements and preceding either a variadic element or the end of the tuple.
Like in TypeScript, optional elements are mutually exclusive with postfix elements.
variadic
Like in TypeScript, variadic elements allow zero or more consecutive values of a given type and may occur at most once in a tuple.
They are specified with a "..."
operator preceding an array element.
postfix
Postfix elements are required elements following a variadic element.
They are mutually exclusive with optional elements.
dates
literals
Date literals represent a Date instance with an exact value.
They're primarily useful in ranges.
ranges
Constrain a Date with an inclusive or exclusive min or max.
Bounds can be expressed as either a number representing its corresponding Unix epoch value or a Date literal.
Range expressions allow you to specify both a min and max and use the same syntax for exclusivity.
instanceof
Most builtin instance types like Array
and Date
are available directly as keywords, but instanceof
can be useful for constraining a type to one of your own classes.
keywords
A list of instanceof keywords can be found here alongside the base and subtype keywords for Array and FormData.