Match
A **true** pattern matching library for JavaScript/ TypeScript
Install / Use
/learn @jsr-core/MatchREADME
[ GitHub tani/ts-match ] [ JSR @core/match ]
A pattern matching library for JavaScript/ TypeScript
EcmaScript has a structured binding. It is a very useful notation for extracting only the necessary parts from a complex structure. However, this structured binding is incomplete for use as pattern matching. Because, To do a structured binding, the pre-assigned value must match the pattern, If it does not match the pattern, an exception is thrown.
const { a } = JSON.parse("null"); // ERROR!
Therefore, to do structured binding, TypeScript either guarantees that the structure matches at compile time, or it does not, validation libraries such as zod or unknownutil checks at runtime that the structure matches. The former is impotent for data whose structure is not determined at compile time, such as JSON data. The latter required writing two structured binding patterns and two validation patterns.
This library can perform structured binding and validation simultaneously, while preserving compile-time type information. It is a library that enables true pattern matching and brings EcmaScript's structured binding to perfection.
Again, this is not just for TypeScript, it is also useful in JavaScript.
Usage
This library is published in JSR and can be used in Deno with jsr:@core/match.
There are only two functions that users need to remember: _ and match.
import { match, placeholder as _ } from "jsr:@core/match";
-
_is a function for creating structured bound patterns. If you already use_for other library, you can use other name like__.const pattern = { name: _("name"), // this value will be captured as unknown value address: { country: _("country"), // you can write the placeholder anywhere,. state: "NY", // without place holder, matcher will compares the values using === }, age: _("age", isNumber), // you can specify the type of placeholder with the type guard, favorites: ["baseball", _("favorite")], // you can put the placeholder in an array others: [_(1), _(Symbol.other)], // you can declare the placeholder with number or symbol message: _`Hello, ${_("nickname")}` // you can put the placeholder in the template string. }; -
matchis a function for performing structured binding. If you execute a structured binding based on the above pattern, the value corresponding to the followingMatchtype is:- an object whose key is the name declared as placeholder, and
- the placeholder given the type guard will be of that type, and
- if the structure does not match or the type guard fails,
undefinedis returned.
type Match = { [1]: unknown, [Symbol.other]: unknown name: unknown, country: unknown, age: number, favorite: unknown, nickname: string } | undefined;. const result: Match = match(pattern, value);
How to declare type guards.
In TypeScript, a type guard is a function with type (v: unknown) => v is T, It
can be declared as follows. There is also a collection of generic type guards,
such as unknownutil.
function isNumber(v: unknown): v is number {
return typeof v === "number";
}
License.
This library is licensed under the MIT License. Please feel free to use it as long as you comply with the licence.
Scenarios for using this library
First, load the library.
import { match, placeholder as _ } from "./mod.ts";
import { assertEquals } from "jsr:@std/assert";
This library can be used to check if an object matches a specific pattern. The following example demonstrates how to match an object with a simple string value. If the object matches the pattern, the result will be an empty object, since the pattern does not contain any placeholders yet.
Deno.test("match object with primitive string value", () => {
const pattern = "hello";
const value = "hello";
const result = match(pattern, value);
assertEquals(result, {});
});
You can also use numeric values as patterns.
Deno.test("match object with primitive number value", () => {
const pattern = 123;
const value = 123;
const result = match(pattern, value);
assertEquals(result, {});
});
Boolean values also can be used as patterns.
Deno.test("match object with primitive boolean value", () => {
const pattern = true;
const value = true;
const result = match(pattern, value);
assertEquals(result, {});
});
Null values can be used as patterns as well. Note that the match function returns an empty object even if both the pattern and the value are null.
Deno.test("match object with primitive null value", () => {
const pattern = null;
const value = null;
const result = match(pattern, value);
assertEquals(result, {});
});
Undefined values can also be used as patterns. Note that the match function returns an empty object even if both the pattern and the value are undefined.
Deno.test("match object with primitive undefined value", () => {
const pattern = undefined;
const value = undefined;
const result = match(pattern, value);
assertEquals(result, {});
});
Symbol values can be used as patterns as well.
Deno.test("match object with primitive symbol value", () => {
const symbol = Symbol("hello");
const pattern = symbol;
const value = symbol;
const result = match(pattern, value);
assertEquals(result, {});
});
You can also use compound objects as patterns. If the object matches the pattern, the result will still be an empty object, because there are no placeholders in the pattern.
Deno.test("match object with compound object value", () => {
const pattern = { name: "hello", age: 1 };
const value = { name: "hello", age: 1 };
const result = match(pattern, value);
assertEquals(result, {});
});
Arrays can be used as patterns too. If the object matches the pattern, the result will still be an empty object, because there are no placeholders in the pattern.
Deno.test("match object with array value", () => {
const pattern = ["hello", 123];
const value = ["hello", 123];
const result = match(pattern, value);
assertEquals(result, {});
});
If the object does not match the pattern, the match function returns undefined. In the following pattern, the value of the object does not match the pattern because the value is not object.
Deno.test("match object with primitive value (not equal)", () => {
const pattern = { a: 1 };
const value = 123;
const result = match(pattern, value);
assertEquals(result, undefined);
});
Now, let's use a placeholder. The first step is to declare a single placeholder.
The placeholder is declared using the _ function, and the name of the
placeholder is passed as an argument. The placeholder holds the name's string
value and the type guard function test. The following placeholder does not
have a type guard, making it the simplest form of a placeholder.
Deno.test("declare single placeholder", () => {
const pattern = _("a");
assertEquals(pattern.name, "a");
assertEquals(pattern.test, undefined);
});
To use the placeholder, apply the match function. The match function returns an
object containing the key-value pair of the placeholder's name and the
associated object's value. If the object does not match the pattern, the match
function returns undefined. Note that the resulting object has an a key, the
value of the object is hello, and its type is unknown in TypeScript because
the placeholder has no type guard.
Deno.test("match object with single placeholder", () => {
const pattern = _("a");
const value = "hello";
const result = match(pattern, value);
assertEquals(result, { a: "hello" });
});
To provide a type guard for the placeholder, declare the type guard function as
the second argument of the _ function.
Deno.test("match object with single placeholder and type guard", () => {
const pattern = _("a", (v: unknown): v is string => typeof v === "string");
const value = "hello";
const result = match(pattern, value);
assertEquals(result, { a: "hello" });
});
If the type guard fails, the match function returns undefined.
Deno.test("match object with single placeholder and type guard", () => {
const pattern = _("a", (v: unknown): v is string => typeof v === "string");
const value = 123;
const result = match(pattern, value);
assertEquals(result, undefined);
});
Placeholders can also be used in compound objects. The following pattern has two
placeholders, a and b. Note that the resulting object has a and b keys,
and the values of the object are hello and 123 respectively. Furthermore,
the type of the a value is unknown, and the type of the b value is
unknown in TypeScript.
Deno.test("match object with compound object and placeholders", () => {
const pattern = { name: _("a"), age: _("b") };
const value = { name: "hello", age: 1 };
const result = match(pattern, value);
assertEquals(result, { a: "hello", b: 1 });
});
Similarly, you can pass the type guard to the placeholder. In this case, the
type of the a value is string, while the type of the b value is number
in TypeScript.
Deno.test("match object with compound object and placeholders (type guard)", () => {
const pattern = {
name: _("a", (v: unknown): v is string => typeof v === "string"),
age: _("b", (v: unknown): v is number => typeof v === "number"),
};
const value = { name: "hello", age: 1 };
const result = match(pattern, value);
assertEquals(result, { a: "hello", b: 1 });
});
It is expected that the match function returns undefined if the object does not match the pattern. In the following
