Schema Transformations
Transformations are important when working with schemas. They allow you to change data from one type to another. For example, you might parse a string into a number or convert a date string into a Date object.
The Schema.transform and Schema.transformOrFail functions help you connect two schemas so you can convert data between them.
transform
Schema.transform creates a new schema by taking the output of one schema (the “source”) and making it the input of another schema (the “target”). Use this when you know the transformation will always succeed. If it might fail, use Schema.transformOrFail instead.
Understanding Input and Output
“Output” and “input” depend on what you are doing (decoding or encoding):
When decoding:
- The source schema
Schema<SourceType, SourceEncoded>produces aSourceType. - The target schema
Schema<TargetType, TargetEncoded>expects aTargetEncoded. - The decoding path looks like this:
SourceEncoded→TargetType.
If SourceType and TargetEncoded differ, you can provide a decode function to convert the source schema’s output into the target schema’s input.
When encoding:
- The target schema
Schema<TargetType, TargetEncoded>produces aTargetEncoded. - The source schema
Schema<SourceType, SourceEncoded>expects aSourceType. - The encoding path looks like this:
TargetType→SourceEncoded.
If TargetEncoded and SourceType differ, you can provide an encode function to convert the target schema’s output into the source schema’s input.
Combining Two Primitive Schemas
In this example, we start with a schema that accepts "on" or "off" and transform it into a boolean schema. The decode function turns "on" into true and "off" into false. The encode function does the reverse. This gives us a Schema<boolean, "on" | "off">.
Example (Converting a String to a Boolean)
import { Schema } from "effect"
// Convert "on"/"off" to boolean and backconst BooleanFromString = Schema.transform( // Source schema: "on" or "off" Schema.Literal("on", "off"), // Target schema: boolean Schema.Boolean, { // optional but you get better error messages from TypeScript strict: true, // Transformation to convert the output of the // source schema ("on" | "off") into the input of the // target schema (boolean) decode: (literal) => literal === "on", // Always succeeds here // Reverse transformation encode: (bool) => (bool ? "on" : "off"), },)
// ┌─── "on" | "off"// ▼type Encoded = typeof BooleanFromString.Encoded
// ┌─── boolean// ▼type Type = typeof BooleanFromString.Type
console.log(Schema.decodeUnknownSync(BooleanFromString)("on"))// Output: trueThe decode function above never fails by itself. However, the full decoding process can still fail if the input does not fit the source schema. For example, if you provide "wrong" instead of "on" or "off", the source schema will fail before calling decode.
Example (Handling Invalid Input)
import { Schema } from "effect"
// Convert "on"/"off" to boolean and back9 collapsed lines
const BooleanFromString = Schema.transform(Schema.Literal("on", "off"), Schema.Boolean, { strict: true, decode: (s) => s === "on", encode: (bool) => (bool ? "on" : "off"),})
// Providing input not allowed by the source schemaSchema.decodeUnknownSync(BooleanFromString)("wrong")/*throws:ParseError: ("on" | "off" <-> boolean)└─ Encoded side transformation failure └─ "on" | "off" ├─ Expected "on", actual "wrong" └─ Expected "off", actual "wrong"*/Combining Two Transformation Schemas
Below is an example where both the source and target schemas transform their data:
- The source schema is
Schema.NumberFromString, which isSchema<number, string>. - The target schema is
BooleanFromString(defined above), which isSchema<boolean, "on" | "off">.
This example involves four types and requires two conversions:
- When decoding, convert a
numberinto"on" | "off". For example, treat any positive number as"on". - When encoding, convert
"on" | "off"back into anumber. For example, treat"on"as1and"off"as-1.
By composing these transformations, we get a schema that decodes a string into a boolean and encodes a boolean back into a string. The resulting schema is Schema<boolean, string>.
Example (Combining Two Transformation Schemas)
import { Schema } from "effect"
// Convert "on"/"off" to boolean and back9 collapsed lines
const BooleanFromString = Schema.transform(Schema.Literal("on", "off"), Schema.Boolean, { strict: true, decode: (s) => s === "on", encode: (bool) => (bool ? "on" : "off"),})
const BooleanFromNumericString = Schema.transform( // Source schema: Convert string -> number Schema.NumberFromString, // Target schema: Convert "on"/"off" -> boolean BooleanFromString, { strict: true, // If number is positive, use "on", otherwise "off" decode: (n) => (n > 0 ? "on" : "off"), // If boolean is "on", use 1, otherwise -1 encode: (bool) => (bool === "on" ? 1 : -1), },)
// ┌─── string// ▼type Encoded = typeof BooleanFromNumericString.Encoded
// ┌─── boolean// ▼type Type = typeof BooleanFromNumericString.Type
console.log(Schema.decodeUnknownSync(BooleanFromNumericString)("100"))// Output: trueExample (Converting an array to a ReadonlySet)
In this example, we convert an array into a ReadonlySet. The decode function takes an array and creates a new ReadonlySet. The encode function converts the set back into an array. We also provide the schema of the array items so they are properly validated.
import { Schema } from "effect"
// This function builds a schema that converts between a readonly array// and a readonly set of itemsconst ReadonlySetFromArray = <A, I, R>( itemSchema: Schema.Schema<A, I, R>,): Schema.Schema<ReadonlySet<A>, ReadonlyArray<I>, R> => Schema.transform( // Source schema: array of items Schema.Array(itemSchema), // Target schema: readonly set of items // **IMPORTANT** We use `Schema.typeSchema` here to obtain the schema // of the items to avoid decoding the elements twice Schema.ReadonlySetFromSelf(Schema.typeSchema(itemSchema)), { strict: true, decode: (items) => new Set(items), encode: (set) => Array.from(set.values()), }, )
const schema = ReadonlySetFromArray(Schema.String)
// ┌─── readonly string[]// ▼type Encoded = typeof schema.Encoded
// ┌─── ReadonlySet<string>// ▼type Type = typeof schema.Type
console.log(Schema.decodeUnknownSync(schema)(["a", "b", "c"]))// Output: Set(3) { 'a', 'b', 'c' }
console.log(Schema.encodeSync(schema)(new Set(["a", "b", "c"])))// Output: [ 'a', 'b', 'c' ]Non-strict option
In some cases, strict type checking can create issues during data transformations, especially when the types might slightly differ in specific transformations. To address these scenarios, Schema.transform offers the option strict: false, which relaxes type constraints and allows more flexible transformations.
Example (Creating a Clamping Constructor)
Let’s consider the scenario where you need to define a constructor clamp that ensures a number falls within a specific range. This function returns a schema that “clamps” a number to a specified minimum and maximum range:
import { Schema, Number } from "effect"
const clamp = (minimum: number, maximum: number) => <A extends number, I, R>(self: Schema.Schema<A, I, R>) => Schema.transform( // Source schema self, // Target schema: filter based on min/max range self.pipe( Schema.typeSchema, Schema.filter((a) => a <= minimum || a >= maximum), ), // @errors: 2345 { strict: true, // Clamp the number within the specified range decode: (a) => Number.clamp(a, { minimum, maximum }), encode: (a) => a, }, )In this example, Number.clamp returns a number that might not be recognized as the specific A type, which leads to a type mismatch under strict checking.
There are two ways to resolve this issue:
-
Using Type Assertion: Adding a type cast can enforce the return type to be treated as type
A:decode: (a) => Number.clamp(a, { minimum, maximum }) as A -
Using the Non-Strict Option: Setting
strict: falsein the transformation options allows the schema to bypass some of TypeScript’s type-checking rules, accommodating the type discrepancy:import { Schema, Number } from "effect"const clamp =(minimum: number, maximum: number) =><A extends number, I, R>(self: Schema.Schema<A, I, R>) =>Schema.transform(self,self.pipe(Schema.typeSchema,Schema.filter((a) => a >= minimum && a <= maximum),),{strict: false,decode: (a) => Number.clamp(a, { minimum, maximum }),encode: (a) => a,},)
transformOrFail
While the Schema.transform function is suitable for error-free transformations,
the Schema.transformOrFail function is designed for more complex scenarios where transformations
can fail during the decoding or encoding stages.
This function enables decoding/encoding functions to return either a successful result or an error, making it particularly useful for validating and processing data that might not always conform to expected formats.
Error Handling
The Schema.transformOrFail function utilizes the ParseResult module to manage potential errors:
| Constructor | Description |
|---|---|
ParseResult.succeed |
Indicates a successful transformation, where no errors occurred. |
ParseResult.fail |
Signals a failed transformation, creating a new ParseError based on the provided ParseIssue. |
Additionally, the ParseResult module provides constructors for dealing with various types of parse issues, such as:
| Parse Issue Type | Description |
|---|---|
Type |
Indicates a type mismatch error. |
Missing |
Used when a required field is missing. |
Unexpected |
Used for unexpected fields that are not allowed in the schema. |
Forbidden |
Flags the decoding or encoding operation being forbidden by the schema. |
Pointer |
Points to a specific location in the data where an issue occurred. |
Refinement |
Used when a value does not meet a specific refinement or constraint. |
Transformation |
Flags issues that occur during transformation from one type to another. |
Composite |
Represents a composite error, combining multiple issues into one, helpful for grouped errors. |
These tools allow for detailed and specific error handling, enhancing the reliability of data processing operations.
Example (Converting a String to a Number)
A common use case for Schema.transformOrFail is converting string representations of numbers into actual numeric types. This scenario is typical when dealing with user inputs or data from external sources.
import { ParseResult, Schema } from "effect"
export const NumberFromString = Schema.transformOrFail( // Source schema: accepts any string Schema.String, // Target schema: expects a number Schema.Number, { // optional but you get better error messages from TypeScript strict: true, decode: (input, options, ast) => { const parsed = parseFloat(input) // If parsing fails (NaN), return a ParseError with a custom error if (isNaN(parsed)) { return ParseResult.fail( // Create a Type Mismatch error new ParseResult.Type( // Provide the schema's abstract syntax tree for context ast, // Include the problematic input input, // Optional custom error message "Failed to convert string to number", ), ) } return ParseResult.succeed(parsed) }, encode: (input, options, ast) => ParseResult.succeed(input.toString()), },)
// ┌─── string// ▼type Encoded = typeof NumberFromString.Encoded
// ┌─── number// ▼type Type = typeof NumberFromString.Type
console.log(Schema.decodeUnknownSync(NumberFromString)("123"))// Output: 123
console.log(Schema.decodeUnknownSync(NumberFromString)("-"))/*throws:ParseError: (string <-> number)└─ Transformation process failure └─ Failed to convert string to number*/Both decode and encode functions not only receive the value to transform (input), but also the parse options that the user sets when using the resulting schema, and the ast, which represents the low level definition of the schema you’re transforming.
Async Transformations
In modern applications, especially those interacting with external APIs, you might need to transform data asynchronously. Schema.transformOrFail supports asynchronous transformations by allowing you to return an Effect.
Example (Validating Data with an API Call)
Consider a scenario where you need to validate a person’s ID by making an API call. Here’s how you can implement it:
import { Effect, Schema, ParseResult } from "effect"
// Define a function to make API requestsconst get = (url: string): Effect.Effect<unknown, Error> => Effect.tryPromise({ try: () => fetch(url).then((res) => { if (res.ok) { return res.json() as Promise<unknown> } throw new Error(String(res.status)) }), catch: (e) => new Error(String(e)), })
// Create a branded schema for a person's IDconst PeopleId = Schema.String.pipe(Schema.brand("PeopleId"))
// Define a schema with async transformationconst PeopleIdFromString = Schema.transformOrFail(Schema.String, PeopleId, { strict: true, decode: (s, _, ast) => // Make an API call to validate the ID Effect.mapBoth(get(`https://swapi.dev/api/people/${s}`), { // Error handling for failed API call onFailure: (e) => new ParseResult.Type(ast, s, e.message), // Return the ID if the API call succeeds onSuccess: () => s, }), encode: ParseResult.succeed,})
// ┌─── string// ▼type Encoded = typeof PeopleIdFromString.Encoded
// ┌─── string & Brand<"PeopleId">// ▼type Type = typeof PeopleIdFromString.Type
// ┌─── never// ▼type Context = typeof PeopleIdFromString.Context
// Run a successful decode operationEffect.runPromiseExit(Schema.decodeUnknown(PeopleIdFromString)("1")).then(console.log)/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/
// Run a decode operation that will failEffect.runPromiseExit(Schema.decodeUnknown(PeopleIdFromString)("fail")).then(console.log)/*Output:{ _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _id: 'ParseError', message: '(string <-> string & Brand<"PeopleId">)\n' + '└─ Transformation process failure\n' + ' └─ Error: 404' } }}*/Declaring Dependencies
In cases where your transformation depends on external services, you can inject these services in the decode or encode functions. These dependencies are then tracked in the Requirements channel of the schema:
Schema<Type, Encoded, Requirements>Example (Validating Data with a Service)
import { Context, Effect, Schema, ParseResult, Layer } from "effect"
// Define a Validation service for dependency injectionclass Validation extends Context.Tag("Validation")< Validation, { readonly validatePeopleid: (s: string) => Effect.Effect<void, Error> }>() {}
// Create a branded schema for a person's IDconst PeopleId = Schema.String.pipe(Schema.brand("PeopleId"))
// Transform a string into a validated PeopleId,// using an external validation serviceconst PeopleIdFromString = Schema.transformOrFail(Schema.String, PeopleId, { strict: true, decode: (s, _, ast) => // Asynchronously validate the ID using the injected service Effect.gen(function* () { // Access the validation service const validator = yield* Validation // Use service to validate ID yield* validator.validatePeopleid(s) return s }).pipe(Effect.mapError((e) => new ParseResult.Type(ast, s, e.message))), encode: ParseResult.succeed, // Encode by simply returning the string})
// ┌─── string// ▼type Encoded = typeof PeopleIdFromString.Encoded
// ┌─── string & Brand<"PeopleId">// ▼type Type = typeof PeopleIdFromString.Type
// ┌─── Validation// ▼type Context = typeof PeopleIdFromString.Context
// Layer to provide a successful validation serviceconst SuccessTest = Layer.succeed(Validation, { validatePeopleid: (_) => Effect.void,})
// Run a successful decode operationEffect.runPromiseExit( Schema.decodeUnknown(PeopleIdFromString)("1").pipe(Effect.provide(SuccessTest)),).then(console.log)/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/
// Layer to provide a failing validation serviceconst FailureTest = Layer.succeed(Validation, { validatePeopleid: (_) => Effect.fail(new Error("404")),})
// Run a decode operation that will failEffect.runPromiseExit( Schema.decodeUnknown(PeopleIdFromString)("fail").pipe(Effect.provide(FailureTest)),).then(console.log)/*Output:{ _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _id: 'ParseError', message: '(string <-> string & Brand<"PeopleId">)\n' + '└─ Transformation process failure\n' + ' └─ Error: 404' } }}*/One-Way Transformations with Forbidden Encoding
In some cases, encoding a value back to its original form may not make sense or may be undesirable. You can use Schema.transformOrFail to define a one-way transformation and explicitly return a Forbidden parse error during the encoding process. This ensures that once a value is transformed, it cannot be reverted to its original form.
Example (Password Hashing with Forbidden Encoding)
Consider a scenario where you need to hash a user’s plain text password for secure storage. It is important that the hashed password cannot be reversed back to plain text. By using Schema.transformOrFail, you can enforce this restriction, ensuring a one-way transformation from plain text to a hashed password.
import { Schema, ParseResult, Redacted } from "effect"import { createHash } from "node:crypto"
// Define a schema for plain text passwords// with a minimum length requirementconst PlainPassword = Schema.String.pipe( Schema.minLength(6), Schema.brand("PlainPassword", { identifier: "PlainPassword" }),)
// Define a schema for hashed passwords as a separate branded typeconst HashedPassword = Schema.String.pipe( Schema.brand("HashedPassword", { identifier: "HashedPassword" }),)
// Define a one-way transformation from plain passwords to hashed passwordsexport const PasswordHashing = Schema.transformOrFail( PlainPassword, // Wrap the output in Redacted for added safety Schema.RedactedFromSelf(HashedPassword), { strict: true, // Decode: Transform a plain password into a hashed password decode: (plainPassword) => { const hash = createHash("sha256").update(plainPassword).digest("hex") // Wrap the hash in Redacted return ParseResult.succeed(Redacted.make(hash)) }, // Encode: Forbid reversing the hashed password back to plain text encode: (hashedPassword, _, ast) => ParseResult.fail( new ParseResult.Forbidden( ast, hashedPassword, "Encoding hashed passwords back to plain text is forbidden.", ), ), },)
// ┌─── string// ▼type Encoded = typeof PasswordHashing.Encoded
// ┌─── Redacted<string & Brand<"HashedPassword">>// ▼type Type = typeof PasswordHashing.Type
// Example: Decoding a plain password into a hashed passwordconsole.log(Schema.decodeUnknownSync(PasswordHashing)("myPlainPassword123"))// Output: <redacted>
// Example: Attempting to encode a hashed password back to plain textconsole.log(Schema.encodeUnknownSync(PasswordHashing)(Redacted.make("2ef2b7...")))/*throws:ParseError: (PlainPassword <-> Redacted(<redacted>))└─ Transformation process failure └─ (PlainPassword <-> Redacted(<redacted>)) └─ Encoding hashed passwords back to plain text is forbidden.*/Composition
Combining and reusing schemas is often needed in complex applications, and the Schema.compose combinator provides an efficient way to do this. With Schema.compose, you can chain two schemas, Schema<B, A, R1> and Schema<C, B, R2>, into a single schema Schema<C, A, R1 | R2>:
Example (Composing Schemas to Parse a Delimited String into Numbers)
import { Schema } from "effect"
// Schema to split a string by commas into an array of strings//// ┌─── Schema<readonly string[], string, never>// ▼const schema1 = Schema.asSchema(Schema.split(","))
// Schema to convert an array of strings to an array of numbers//// ┌─── Schema<readonly number[], readonly string[], never>// ▼const schema2 = Schema.asSchema(Schema.Array(Schema.NumberFromString))
// Composed schema that takes a string, splits it by commas,// and converts the result into an array of numbers//// ┌─── Schema<readonly number[], string, never>// ▼const ComposedSchema = Schema.asSchema(Schema.compose(schema1, schema2))Non-strict Option
When composing schemas, you may encounter cases where the output of one schema does not perfectly match the input of the next, for example, if you have Schema<R1, A, B> and Schema<R2, C, D> where C differs from B. To handle these cases, you can use the { strict: false } option to relax type constraints.
Example (Using Non-strict Option in Composition)
import { Schema } from "effect"
// Without the `strict: false` option,// this composition raises a TypeScript errorSchema.compose( // @errors: 2769 Schema.Union(Schema.Null, Schema.Literal("0")), Schema.NumberFromString,)
// Use `strict: false` to allow type flexibilitySchema.compose(Schema.Union(Schema.Null, Schema.Literal("0")), Schema.NumberFromString, { strict: false,})Effectful Filters
The Schema.filterEffect function enables validations that require asynchronous or dynamic scenarios, making it suitable for cases where validations involve side effects like network requests or database queries. For simple synchronous validations, see Schema.filter.
Example (Asynchronous Username Validation)
import { Effect, Schema } from "effect"
// Mock async function to validate a usernameasync function validateUsername(username: string) { return Promise.resolve(username === "gcanti")}
// Define a schema with an effectful filterconst ValidUsername = Schema.String.pipe( Schema.filterEffect((username) => Effect.promise(() => // Validate the username asynchronously, // returning an error message if invalid validateUsername(username).then((valid) => valid || "Invalid username"), ), ),).annotations({ identifier: "ValidUsername" })
Effect.runPromise(Schema.decodeUnknown(ValidUsername)("xxx")).then(console.log)/*ParseError: ValidUsername└─ Transformation process failure └─ Invalid username*/String Transformations
split
Splits a string by a specified delimiter into an array of substrings.
Example (Splitting a String by Comma)
import { Schema } from "effect"
const schema = Schema.split(",")
const decode = Schema.decodeUnknownSync(schema)
console.log(decode("")) // [""]console.log(decode(",")) // ["", ""]console.log(decode("a,")) // ["a", ""]console.log(decode("a,b")) // ["a", "b"]Trim
Removes whitespace from the beginning and end of a string.
Example (Trimming Whitespace)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Trim)
console.log(decode("a")) // "a"console.log(decode(" a")) // "a"console.log(decode("a ")) // "a"console.log(decode(" a ")) // "a"Lowercase
Converts a string to lowercase.
Example (Converting to Lowercase)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Lowercase)
console.log(decode("A")) // "a"console.log(decode(" AB")) // " ab"console.log(decode("Ab ")) // "ab "console.log(decode(" ABc ")) // " abc "Uppercase
Converts a string to uppercase.
Example (Converting to Uppercase)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Uppercase)
console.log(decode("a")) // "A"console.log(decode(" ab")) // " AB"console.log(decode("aB ")) // "AB "console.log(decode(" abC ")) // " ABC "Capitalize
Converts the first character of a string to uppercase.
Example (Capitalizing a String)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Capitalize)
console.log(decode("aa")) // "Aa"console.log(decode(" ab")) // " ab"console.log(decode("aB ")) // "AB "console.log(decode(" abC ")) // " abC "Uncapitalize
Converts the first character of a string to lowercase.
Example (Uncapitalizing a String)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Uncapitalize)
console.log(decode("AA")) // "aA"console.log(decode(" AB")) // " AB"console.log(decode("Ab ")) // "ab "console.log(decode(" AbC ")) // " AbC "parseJson
The Schema.parseJson constructor offers a method to convert JSON strings into the unknown type using the underlying functionality of JSON.parse.
It also employs JSON.stringify for encoding.
Example (Parsing JSON Strings)
import { Schema } from "effect"
const schema = Schema.parseJson()const decode = Schema.decodeUnknownSync(schema)
// Parse valid JSON stringsconsole.log(decode("{}")) // Output: {}console.log(decode(`{"a":"b"}`)) // Output: { a: "b" }
// Attempting to decode an empty string results in an errordecode("")/*throws:ParseError: (JsonString <-> unknown)└─ Transformation process failure └─ Unexpected end of JSON input*/To further refine the result of JSON parsing, you can provide a schema to the Schema.parseJson constructor. This schema will validate that the parsed JSON matches a specific structure.
Example (Parsing JSON with Structured Validation)
In this example, Schema.parseJson uses a struct schema to ensure the parsed JSON is an object with a numeric property a. This adds validation to the parsed data, confirming that it follows the expected structure.
import { Schema } from "effect"
// ┌─── SchemaClass<{ readonly a: number; }, string, never>// ▼const schema = Schema.parseJson(Schema.Struct({ a: Schema.Number }))StringFromBase64
Decodes a base64 (RFC4648) encoded string into a UTF-8 string.
Example (Decoding Base64)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.StringFromBase64)
console.log(decode("Zm9vYmFy"))// Output: "foobar"StringFromBase64Url
Decodes a base64 (URL) encoded string into a UTF-8 string.
Example (Decoding Base64 URL)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.StringFromBase64Url)
console.log(decode("Zm9vYmFy"))// Output: "foobar"StringFromHex
Decodes a hex encoded string into a UTF-8 string.
Example (Decoding Hex String)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.StringFromHex)
console.log(new TextEncoder().encode(decode("0001020304050607")))/*Output:Uint8Array(8) [ 0, 1, 2, 3, 4, 5, 6, 7]*/StringFromUriComponent
Decodes a URI-encoded string into a UTF-8 string. It is useful for encoding and decoding data in URLs.
Example (Decoding URI Component)
import { Schema } from "effect"
const PaginationSchema = Schema.Struct({ maxItemPerPage: Schema.Number, page: Schema.Number,})
const UrlSchema = Schema.compose(Schema.StringFromUriComponent, Schema.parseJson(PaginationSchema))
console.log(Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 }))// Output: %7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7DNumber Transformations
NumberFromString
Transforms a string into a number by parsing the string using the parse function of the effect/Number module.
It returns an error if the value can’t be converted (for example when non-numeric characters are provided).
The following special string values are supported: “NaN”, “Infinity”, “-Infinity”.
Example (Parsing Number from String)
import { Schema } from "effect"
const schema = Schema.NumberFromString
const decode = Schema.decodeUnknownSync(schema)
// success casesconsole.log(decode("1")) // 1console.log(decode("-1")) // -1console.log(decode("1.5")) // 1.5console.log(decode("NaN")) // NaNconsole.log(decode("Infinity")) // Infinityconsole.log(decode("-Infinity")) // -Infinity
// failure casesdecode("a")/*throws:ParseError: NumberFromString└─ Transformation process failure └─ Expected NumberFromString, actual "a"*/clamp
Restricts a number within a specified range.
Example (Clamping a Number)
import { Schema } from "effect"
// clamps the input to -1 <= x <= 1const schema = Schema.Number.pipe(Schema.clamp(-1, 1))
const decode = Schema.decodeUnknownSync(schema)
console.log(decode(-3)) // -1console.log(decode(0)) // 0console.log(decode(3)) // 1parseNumber
Transforms a string into a number by parsing the string using the parse function of the effect/Number module.
It returns an error if the value can’t be converted (for example when non-numeric characters are provided).
The following special string values are supported: “NaN”, “Infinity”, “-Infinity”.
Example (Parsing and Validating Numbers)
import { Schema } from "effect"
const schema = Schema.String.pipe(Schema.parseNumber)
const decode = Schema.decodeUnknownSync(schema)
console.log(decode("1")) // 1console.log(decode("Infinity")) // Infinityconsole.log(decode("NaN")) // NaNconsole.log(decode("-"))/*throwsParseError: (string <-> number)└─ Transformation process failure └─ Expected (string <-> number), actual "-"*/Boolean Transformations
Not
Negates a boolean value.
Example (Negating Boolean)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Not)
console.log(decode(true)) // falseconsole.log(decode(false)) // trueSymbol transformations
Symbol
Converts a string to a symbol using Symbol.for.
Example (Creating Symbols from Strings)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Symbol)
console.log(decode("a")) // Symbol(a)BigInt transformations
BigInt
Converts a string to a BigInt using the BigInt constructor.
Example (Parsing BigInt from String)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.BigInt)
// success casesconsole.log(decode("1")) // 1nconsole.log(decode("-1")) // -1n
// failure casesdecode("a")/*throws:ParseError: bigint└─ Transformation process failure └─ Expected bigint, actual "a"*/decode("1.5") // throwsdecode("NaN") // throwsdecode("Infinity") // throwsdecode("-Infinity") // throwsBigIntFromNumber
Converts a number to a BigInt using the BigInt constructor.
Example (Parsing BigInt from Number)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.BigIntFromNumber)const encode = Schema.encodeSync(Schema.BigIntFromNumber)
// success casesconsole.log(decode(1)) // 1nconsole.log(decode(-1)) // -1nconsole.log(encode(1n)) // 1console.log(encode(-1n)) // -1
// failure casesdecode(1.5)/*throws:ParseError: BigintFromNumber└─ Transformation process failure └─ Expected BigintFromNumber, actual 1.5*/
decode(NaN) // throwsdecode(Infinity) // throwsdecode(-Infinity) // throwsencode(BigInt(Number.MAX_SAFE_INTEGER) + 1n) // throwsencode(BigInt(Number.MIN_SAFE_INTEGER) - 1n) // throwsclampBigInt
Restricts a BigInt within a specified range.
Example (Clamping BigInt)
import { Schema } from "effect"
// clamps the input to -1n <= x <= 1nconst schema = Schema.BigIntFromSelf.pipe(Schema.clampBigInt(-1n, 1n))
const decode = Schema.decodeUnknownSync(schema)
console.log(decode(-3n))// Output: -1n
console.log(decode(0n))// Output: 0n
console.log(decode(3n))// Output: 1nDate transformations
Date
Converts a string into a valid Date, ensuring that invalid dates, such as new Date("Invalid Date"), are rejected.
Example (Parsing and Validating Date)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.Date)
console.log(decode("1970-01-01T00:00:00.000Z"))// Output: 1970-01-01T00:00:00.000Z
decode("a")/*throws:ParseError: Date└─ Predicate refinement failure └─ Expected Date, actual Invalid Date*/
const validate = Schema.validateSync(Schema.Date)
console.log(validate(new Date(0)))// Output: 1970-01-01T00:00:00.000Z
console.log(validate(new Date("Invalid Date")))/*throws:ParseError: Date└─ Predicate refinement failure └─ Expected Date, actual Invalid Date*/BigDecimal Transformations
BigDecimal
Converts a string to a BigDecimal.
Example (Parsing BigDecimal from String)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.BigDecimal)
console.log(decode(".124"))// Output: { _id: 'BigDecimal', value: '124', scale: 3 }BigDecimalFromNumber
Converts a number to a BigDecimal.
Example (Parsing BigDecimal from Number)
import { Schema } from "effect"
const decode = Schema.decodeUnknownSync(Schema.BigDecimalFromNumber)
console.log(decode(0.111))// Output: { _id: 'BigDecimal', value: '111', scale: 3 }clampBigDecimal
Clamps a BigDecimal within a specified range.
Example (Clamping BigDecimal)
import { Schema } from "effect"import { BigDecimal } from "effect"
const schema = Schema.BigDecimal.pipe( Schema.clampBigDecimal(BigDecimal.fromNumber(-1), BigDecimal.fromNumber(1)),)
const decode = Schema.decodeUnknownSync(schema)
console.log(decode("-2"))// Output: { _id: 'BigDecimal', value: '-1', scale: 0 }
console.log(decode("0"))// Output: { _id: 'BigDecimal', value: '0', scale: 0 }
console.log(decode("3"))// Output: { _id: 'BigDecimal', value: '1', scale: 0 }