Skip to content
Docs menu / Transformations

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 a SourceType.
  • The target schema Schema<TargetType, TargetEncoded> expects a TargetEncoded.
  • The decoding path looks like this: SourceEncodedTargetType.

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 a TargetEncoded.
  • The source schema Schema<SourceType, SourceEncoded> expects a SourceType.
  • The encoding path looks like this: TargetTypeSourceEncoded.

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 back
const 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: true

The 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 back
9 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 schema
Schema.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 is Schema<number, string>.
  • The target schema is BooleanFromString (defined above), which is Schema<boolean, "on" | "off">.

This example involves four types and requires two conversions:

  • When decoding, convert a number into "on" | "off". For example, treat any positive number as "on".
  • When encoding, convert "on" | "off" back into a number. For example, treat "on" as 1 and "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 back
9 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: true

Example (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 items
const 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:

  1. 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
  2. Using the Non-Strict Option: Setting strict: false in 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 requests
const 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 ID
const PeopleId = Schema.String.pipe(Schema.brand("PeopleId"))
// Define a schema with async transformation
const 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 operation
Effect.runPromiseExit(Schema.decodeUnknown(PeopleIdFromString)("1")).then(console.log)
/*
Output:
{ _id: 'Exit', _tag: 'Success', value: '1' }
*/
// Run a decode operation that will fail
Effect.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 injection
class Validation extends Context.Tag("Validation")<
Validation,
{
readonly validatePeopleid: (s: string) => Effect.Effect<void, Error>
}
>() {}
// Create a branded schema for a person's ID
const PeopleId = Schema.String.pipe(Schema.brand("PeopleId"))
// Transform a string into a validated PeopleId,
// using an external validation service
const 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 service
const SuccessTest = Layer.succeed(Validation, {
validatePeopleid: (_) => Effect.void,
})
// Run a successful decode operation
Effect.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 service
const FailureTest = Layer.succeed(Validation, {
validatePeopleid: (_) => Effect.fail(new Error("404")),
})
// Run a decode operation that will fail
Effect.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 requirement
const PlainPassword = Schema.String.pipe(
Schema.minLength(6),
Schema.brand("PlainPassword", { identifier: "PlainPassword" }),
)
// Define a schema for hashed passwords as a separate branded type
const HashedPassword = Schema.String.pipe(
Schema.brand("HashedPassword", { identifier: "HashedPassword" }),
)
// Define a one-way transformation from plain passwords to hashed passwords
export 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 password
console.log(Schema.decodeUnknownSync(PasswordHashing)("myPlainPassword123"))
// Output: <redacted>
// Example: Attempting to encode a hashed password back to plain text
console.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 error
Schema.compose(
// @errors: 2769
Schema.Union(Schema.Null, Schema.Literal("0")),
Schema.NumberFromString,
)
// Use `strict: false` to allow type flexibility
Schema.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 username
async function validateUsername(username: string) {
return Promise.resolve(username === "gcanti")
}
// Define a schema with an effectful filter
const 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 strings
console.log(decode("{}")) // Output: {}
console.log(decode(`{"a":"b"}`)) // Output: { a: "b" }
// Attempting to decode an empty string results in an error
decode("")
/*
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%7D

Number 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 cases
console.log(decode("1")) // 1
console.log(decode("-1")) // -1
console.log(decode("1.5")) // 1.5
console.log(decode("NaN")) // NaN
console.log(decode("Infinity")) // Infinity
console.log(decode("-Infinity")) // -Infinity
// failure cases
decode("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 <= 1
const schema = Schema.Number.pipe(Schema.clamp(-1, 1))
const decode = Schema.decodeUnknownSync(schema)
console.log(decode(-3)) // -1
console.log(decode(0)) // 0
console.log(decode(3)) // 1

parseNumber

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")) // 1
console.log(decode("Infinity")) // Infinity
console.log(decode("NaN")) // NaN
console.log(decode("-"))
/*
throws
ParseError: (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)) // false
console.log(decode(false)) // true

Symbol 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 cases
console.log(decode("1")) // 1n
console.log(decode("-1")) // -1n
// failure cases
decode("a")
/*
throws:
ParseError: bigint
└─ Transformation process failure
└─ Expected bigint, actual "a"
*/
decode("1.5") // throws
decode("NaN") // throws
decode("Infinity") // throws
decode("-Infinity") // throws

BigIntFromNumber

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 cases
console.log(decode(1)) // 1n
console.log(decode(-1)) // -1n
console.log(encode(1n)) // 1
console.log(encode(-1n)) // -1
// failure cases
decode(1.5)
/*
throws:
ParseError: BigintFromNumber
└─ Transformation process failure
└─ Expected BigintFromNumber, actual 1.5
*/
decode(NaN) // throws
decode(Infinity) // throws
decode(-Infinity) // throws
encode(BigInt(Number.MAX_SAFE_INTEGER) + 1n) // throws
encode(BigInt(Number.MIN_SAFE_INTEGER) - 1n) // throws

clampBigInt

Restricts a BigInt within a specified range.

Example (Clamping BigInt)

import { Schema } from "effect"
// clamps the input to -1n <= x <= 1n
const 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: 1n

Date 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 }