Transformations
On this page
Transformations play a key role in working with schemas, especially when you need to convert data from one type to another, such as parsing a string into a number or converting a date string into a Date
object.
transform
The Schema.transform
function is designed to facilitate these conversions by linking two schemas together: one for the input type and one for the output type.
Here's an overview of the Schema.transform
function, which accepts five parameters:
Parameter | Description | Type |
---|---|---|
from | The source schema, representing the starting point of the transformation. | Schema<B, A, R1> where A is the input type and B is the intermediate type after initial validation. |
to | The target schema, representing the endpoint of the transformation. | Schema<D, C, R2> where C is the transformed type from B , and D is the final output type. |
decode | A function that converts an intermediate value of type B to a value of type C . | (b: B, a: A) => C |
encode | A function that reverses the transformation, converting type C back to type B . | (c: C, d: D) => B |
strict | optional (but recommended) | boolean |
This function results in a schema Schema<D, A, R1 | R2>
, integrating both the dependencies and transformations of the from
and to
schemas.
Example: Doubling a Number
Here's an example that demonstrates a schema transformation to double an input number:
ts
import {Schema } from "@effect/schema"// Define a transformation that doubles the input numberconsttransformedSchema =Schema .transform (// Source schemaSchema .Number ,// Target schemaSchema .Number ,{// optional but you get better error messages from TypeScriptstrict : true,// Transformation function to double the numberdecode : (n ) =>n * 2,// Reverse transformation to revert to the original numberencode : (n ) =>n / 2})
ts
import {Schema } from "@effect/schema"// Define a transformation that doubles the input numberconsttransformedSchema =Schema .transform (// Source schemaSchema .Number ,// Target schemaSchema .Number ,{// optional but you get better error messages from TypeScriptstrict : true,// Transformation function to double the numberdecode : (n ) =>n * 2,// Reverse transformation to revert to the original numberencode : (n ) =>n / 2})
In this example, if you input 2
, the schema will decode it to 4
and encode it back to 2
.
Example: Converting an array to a ReadonlySet
Here's how you can convert an array to a ReadonlySet
:
ts
import {Schema } from "@effect/schema"constReadonlySetFromArray = <A ,I ,R >(itemSchema :Schema .Schema <A ,I ,R >):Schema .Schema <ReadonlySet <A >,ReadonlyArray <I >,R > =>Schema .transform (Schema .Array (itemSchema ),Schema .ReadonlySetFromSelf (Schema .typeSchema (itemSchema )),{strict : true,decode : (items ) => newSet (items ),encode : (set ) =>Array .from (set .values ())})
ts
import {Schema } from "@effect/schema"constReadonlySetFromArray = <A ,I ,R >(itemSchema :Schema .Schema <A ,I ,R >):Schema .Schema <ReadonlySet <A >,ReadonlyArray <I >,R > =>Schema .transform (Schema .Array (itemSchema ),Schema .ReadonlySetFromSelf (Schema .typeSchema (itemSchema )),{strict : true,decode : (items ) => newSet (items ),encode : (set ) =>Array .from (set .values ())})
Please note that to define the target schema, we used
Schema.typeSchema. This is because the
decoding/encoding of the elements is already handled by the from
schema,
Schema.Array(itemSchema)
.
Example: Trim Whitespace
Here's how to use the transform
function to trim whitespace from strings:
ts
import {Schema } from "@effect/schema"consttransformedSchema =Schema .transform (// Source schema: accepts any stringSchema .String ,// Target schema: also accepts any stringSchema .String ,{strict : true,// Trim the string during decodingdecode : (s ) =>s .trim (),// No change during encodingencode : (s ) =>s })
ts
import {Schema } from "@effect/schema"consttransformedSchema =Schema .transform (// Source schema: accepts any stringSchema .String ,// Target schema: also accepts any stringSchema .String ,{strict : true,// Trim the string during decodingdecode : (s ) =>s .trim (),// No change during encodingencode : (s ) =>s })
This schema automatically trims leading and trailing whitespace from a string during decoding. During encoding, it returns the string unchanged.
Improving the Transformation with a Filter
To ensure that strings are not only trimmed but also validated to exclude untrimmed inputs, you can restrict the target schema to only accept strings that are already trimmed:
ts
import {Schema } from "@effect/schema"consttransformedSchema =Schema .transform (// Source schema: accepts any stringSchema .String ,// Target schema now only accepts strings that are trimmedSchema .String .pipe (Schema .filter ((s ) =>s ===s .trim ())),{strict : true,// Trim the string during decodingdecode : (s ) =>s .trim (),// No change during encodingencode : (s ) =>s })
ts
import {Schema } from "@effect/schema"consttransformedSchema =Schema .transform (// Source schema: accepts any stringSchema .String ,// Target schema now only accepts strings that are trimmedSchema .String .pipe (Schema .filter ((s ) =>s ===s .trim ())),{strict : true,// Trim the string during decodingdecode : (s ) =>s .trim (),// No change during encodingencode : (s ) =>s })
In this improved example, the target schema is piped through a filter
function. This function checks that the string is equal to its trimmed version, effectively ensuring that only strings without leading or trailing whitespace are considered valid. This is particularly useful for maintaining data integrity and can help prevent errors or inconsistencies in data processing.
Non-strict option
Sometimes the strict type checking can impede certain operations where types might slightly deviate during the transformation process. For such cases, transform
provides an option, strict: false
, to relax type constraints and allow for more flexible data manipulation.
Example: 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:
ts
import {Schema } from "@effect/schema"import {Number } from "effect"constclamp =(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 )),{Argument of type '{ strict: true; decode: (a: A) => number; encode: (a: A) => A; }' is not assignable to parameter of type '{ readonly decode: (fromA: A, fromI: I) => A; readonly encode: (toI: A, toA: A) => A; readonly strict?: true | undefined; } | { readonly decode: (fromA: A, fromI: I) => unknown; readonly encode: (toI: A, toA: A) => unknown; readonly strict: false; }'. The types returned by 'decode(...)' are incompatible between these types. Type 'number' is not assignable to type 'A'. 'number' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'number'.2345Argument of type '{ strict: true; decode: (a: A) => number; encode: (a: A) => A; }' is not assignable to parameter of type '{ readonly decode: (fromA: A, fromI: I) => A; readonly encode: (toI: A, toA: A) => A; readonly strict?: true | undefined; } | { readonly decode: (fromA: A, fromI: I) => unknown; readonly encode: (toI: A, toA: A) => unknown; readonly strict: false; }'. The types returned by 'decode(...)' are incompatible between these types. Type 'number' is not assignable to type 'A'. 'number' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'number'.strict : true,decode : (a ) =>Number .clamp (a , {minimum ,maximum }),encode : (a ) =>a })
ts
import {Schema } from "@effect/schema"import {Number } from "effect"constclamp =(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 )),{Argument of type '{ strict: true; decode: (a: A) => number; encode: (a: A) => A; }' is not assignable to parameter of type '{ readonly decode: (fromA: A, fromI: I) => A; readonly encode: (toI: A, toA: A) => A; readonly strict?: true | undefined; } | { readonly decode: (fromA: A, fromI: I) => unknown; readonly encode: (toI: A, toA: A) => unknown; readonly strict: false; }'. The types returned by 'decode(...)' are incompatible between these types. Type 'number' is not assignable to type 'A'. 'number' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'number'.2345Argument of type '{ strict: true; decode: (a: A) => number; encode: (a: A) => A; }' is not assignable to parameter of type '{ readonly decode: (fromA: A, fromI: I) => A; readonly encode: (toI: A, toA: A) => A; readonly strict?: true | undefined; } | { readonly decode: (fromA: A, fromI: I) => unknown; readonly encode: (toI: A, toA: A) => unknown; readonly strict: false; }'. The types returned by 'decode(...)' are incompatible between these types. Type 'number' is not assignable to type 'A'. 'number' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'number'.strict : true,decode : (a ) =>Number .clamp (a , {minimum ,maximum }),encode : (a ) =>a })
In this code, Number.clamp
is a function that adjusts the given number to stay within the specified range. However, the return type of Number.clamp
may not strictly be of type A
but just a number
, which can lead to type mismatches according to TypeScript's strict type-checking.
There are two ways to resolve the type mismatch:
-
Using Type Assertion: Adding a type cast can enforce the return type to be treated as type
A
:tsdecode: (a) => Number.clamp(a, { minimum, maximum }) as Atsdecode: (a) => Number.clamp(a, { minimum, maximum }) as A -
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:tsimport {Schema } from "@effect/schema"import {Number } from "effect"constclamp =(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 })tsimport {Schema } from "@effect/schema"import {Number } from "effect"constclamp =(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:
Type
Missing
Unexpected
Forbidden
Pointer
Refinement
Transformation
Composite
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.
ts
import {ParseResult ,Schema } from "@effect/schema"export constNumberFromString =Schema .transformOrFail (Schema .String , // Source schema: accepts any stringSchema .Number , // Target schema: expects a number{strict : true, // optional but you get better error messages from TypeScriptdecode : (input ,options ,ast ) => {constparsed =parseFloat (input )if (isNaN (parsed )) {returnParseResult .fail (newParseResult .Type (ast ,input ,"Failed to convert string to number"))}returnParseResult .succeed (parsed )},encode : (input ,options ,ast ) =>ParseResult .succeed (input .toString ())})
ts
import {ParseResult ,Schema } from "@effect/schema"export constNumberFromString =Schema .transformOrFail (Schema .String , // Source schema: accepts any stringSchema .Number , // Target schema: expects a number{strict : true, // optional but you get better error messages from TypeScriptdecode : (input ,options ,ast ) => {constparsed =parseFloat (input )if (isNaN (parsed )) {returnParseResult .fail (newParseResult .Type (ast ,input ,"Failed to convert string to number"))}returnParseResult .succeed (parsed )},encode : (input ,options ,ast ) =>ParseResult .succeed (input .toString ())})
In this example:
- Decoding: Attempts to parse the input string into a number. If the parsing results in
NaN
(indicating that the string is not a valid number), it fails with a descriptive error. - Encoding: Converts the number back to a string, assuming that the input number is valid.
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: Asynchronously Converting a String to a Number Using an API
Consider a situation where you need to validate a person's ID by fetching data from an external API. Here's how you can implement it:
ts
import {ParseResult ,Schema ,TreeFormatter } from "@effect/schema"import {Effect } from "effect"// Define an API call functionconstapi = (url : string):Effect .Effect <unknown,Error > =>Effect .tryPromise ({try : () =>fetch (url ).then ((res ) => {if (res .ok ) {returnres .json () asPromise <unknown>}throw newError (String (res .status ))}),catch : (e ) => newError (String (e ))})constPeopleId =Schema .String .pipe (Schema .brand ("PeopleId"))// Define a schema with async transformationconstPeopleIdFromString =Schema .transformOrFail (Schema .String ,PeopleId , {strict : true,decode : (s ,_ ,ast ) =>Effect .mapBoth (api (`https://swapi.dev/api/people/${s }`), {onFailure : (e ) => newParseResult .Type (ast ,s ,e .message ),onSuccess : () =>s }),encode :ParseResult .succeed })constdecode = (id : string) =>Effect .mapError (Schema .decodeUnknown (PeopleIdFromString )(id ), (e ) =>TreeFormatter .formatErrorSync (e ))Effect .runPromiseExit (decode ("1")).then (console .log )/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/Effect .runPromiseExit (decode ("fail")).then (console .log )/*Output:{_id: 'Exit',_tag: 'Failure',cause: {_id: 'Cause',_tag: 'Fail',failure: '(string <-> string & Brand<"PeopleId">)\n' +'└─ Transformation process failure\n' +' └─ Error: 404'}}*/
ts
import {ParseResult ,Schema ,TreeFormatter } from "@effect/schema"import {Effect } from "effect"// Define an API call functionconstapi = (url : string):Effect .Effect <unknown,Error > =>Effect .tryPromise ({try : () =>fetch (url ).then ((res ) => {if (res .ok ) {returnres .json () asPromise <unknown>}throw newError (String (res .status ))}),catch : (e ) => newError (String (e ))})constPeopleId =Schema .String .pipe (Schema .brand ("PeopleId"))// Define a schema with async transformationconstPeopleIdFromString =Schema .transformOrFail (Schema .String ,PeopleId , {strict : true,decode : (s ,_ ,ast ) =>Effect .mapBoth (api (`https://swapi.dev/api/people/${s }`), {onFailure : (e ) => newParseResult .Type (ast ,s ,e .message ),onSuccess : () =>s }),encode :ParseResult .succeed })constdecode = (id : string) =>Effect .mapError (Schema .decodeUnknown (PeopleIdFromString )(id ), (e ) =>TreeFormatter .formatErrorSync (e ))Effect .runPromiseExit (decode ("1")).then (console .log )/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/Effect .runPromiseExit (decode ("fail")).then (console .log )/*Output:{_id: 'Exit',_tag: 'Failure',cause: {_id: 'Cause',_tag: 'Fail',failure: '(string <-> string & Brand<"PeopleId">)\n' +'└─ Transformation process failure\n' +' └─ Error: 404'}}*/
Declaring Dependencies
For more complex scenarios where your transformation might depend on external services like a fetching function, you can declare these dependencies explicitly.
Example: Injecting Dependencies
Here's how to inject a fetch dependency into your transformation process:
ts
import {ParseResult ,Schema ,TreeFormatter } from "@effect/schema"import {Context ,Effect ,Layer } from "effect"constFetch =Context .GenericTag <"Fetch", typeoffetch >("Fetch")// API call function with dependencyconstapi = (url : string):Effect .Effect <unknown,Error , "Fetch"> =>Fetch .pipe (Effect .flatMap ((fetch ) =>Effect .tryPromise ({try : () =>fetch (url ).then ((res ) => {if (res .ok ) {returnres .json () asPromise <unknown>}throw newError (String (res .status ))}),catch : (e ) => newError (String (e ))})))constPeopleId =Schema .String .pipe (Schema .brand ("PeopleId"))constPeopleIdFromString =Schema .transformOrFail (Schema .String ,PeopleId , {strict : true,decode : (s ,_ ,ast ) =>Effect .mapBoth (api (`https://swapi.dev/api/people/${s }`), {onFailure : (e ) => newParseResult .Type (ast ,s ,e .message ),onSuccess : () =>s }),encode :ParseResult .succeed })constdecode = (id : string) =>Effect .mapError (Schema .decodeUnknown (PeopleIdFromString )(id ), (e ) =>TreeFormatter .formatErrorSync (e ))constFetchLive =Layer .succeed (Fetch ,fetch )Effect .runPromiseExit (decode ("1").pipe (Effect .provide (FetchLive ))).then (console .log )/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/Effect .runPromiseExit (decode ("fail").pipe (Effect .provide (FetchLive ))).then (console .log )/*Output:{_id: 'Exit',_tag: 'Failure',cause: {_id: 'Cause',_tag: 'Fail',failure: '(string <-> string & Brand<"PeopleId">)\n' +'└─ Transformation process failure\n' +' └─ Error: 404'}}*/
ts
import {ParseResult ,Schema ,TreeFormatter } from "@effect/schema"import {Context ,Effect ,Layer } from "effect"constFetch =Context .GenericTag <"Fetch", typeoffetch >("Fetch")// API call function with dependencyconstapi = (url : string):Effect .Effect <unknown,Error , "Fetch"> =>Fetch .pipe (Effect .flatMap ((fetch ) =>Effect .tryPromise ({try : () =>fetch (url ).then ((res ) => {if (res .ok ) {returnres .json () asPromise <unknown>}throw newError (String (res .status ))}),catch : (e ) => newError (String (e ))})))constPeopleId =Schema .String .pipe (Schema .brand ("PeopleId"))constPeopleIdFromString =Schema .transformOrFail (Schema .String ,PeopleId , {strict : true,decode : (s ,_ ,ast ) =>Effect .mapBoth (api (`https://swapi.dev/api/people/${s }`), {onFailure : (e ) => newParseResult .Type (ast ,s ,e .message ),onSuccess : () =>s }),encode :ParseResult .succeed })constdecode = (id : string) =>Effect .mapError (Schema .decodeUnknown (PeopleIdFromString )(id ), (e ) =>TreeFormatter .formatErrorSync (e ))constFetchLive =Layer .succeed (Fetch ,fetch )Effect .runPromiseExit (decode ("1").pipe (Effect .provide (FetchLive ))).then (console .log )/*Output:{ _id: 'Exit', _tag: 'Success', value: '1' }*/Effect .runPromiseExit (decode ("fail").pipe (Effect .provide (FetchLive ))).then (console .log )/*Output:{_id: 'Exit',_tag: 'Failure',cause: {_id: 'Cause',_tag: 'Fail',failure: '(string <-> string & Brand<"PeopleId">)\n' +'└─ Transformation process failure\n' +' └─ Error: 404'}}*/
Effectful Filters
The Schema.filterEffect
function enhances the Schema.filter
functionality by allowing the integration of effects, thus enabling asynchronous or dynamic validation scenarios. This is particularly useful when validations need to perform operations that require side effects, such as network requests or database queries.
Example: Validating Usernames Asynchronously
ts
import {Schema } from "@effect/schema"import {Effect } from "effect"async functionvalidateUsername (username : string) {returnPromise .resolve (username === "gcanti")}constValidUsername =Schema .String .pipe (Schema .filterEffect ((username ) =>Effect .promise (() =>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*/
ts
import {Schema } from "@effect/schema"import {Effect } from "effect"async functionvalidateUsername (username : string) {returnPromise .resolve (username === "gcanti")}constValidUsername =Schema .String .pipe (Schema .filterEffect ((username ) =>Effect .promise (() =>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 into an array of strings.
ts
import {Schema } from "@effect/schema"constschema =Schema .split (",")constdecode =Schema .decodeUnknownSync (schema )console .log (decode ("")) // [""]console .log (decode (",")) // ["", ""]console .log (decode ("a,")) // ["a", ""]console .log (decode ("a,b")) // ["a", "b"]
ts
import {Schema } from "@effect/schema"constschema =Schema .split (",")constdecode =Schema .decodeUnknownSync (schema )console .log (decode ("")) // [""]console .log (decode (",")) // ["", ""]console .log (decode ("a,")) // ["a", ""]console .log (decode ("a,b")) // ["a", "b"]
Trim
Removes whitespaces from the beginning and end of a string.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Trim )console .log (decode ("a")) // "a"console .log (decode (" a")) // "a"console .log (decode ("a ")) // "a"console .log (decode (" a ")) // "a"
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Trim )console .log (decode ("a")) // "a"console .log (decode (" a")) // "a"console .log (decode ("a ")) // "a"console .log (decode (" a ")) // "a"
Note. If you were looking for a combinator to check if a string is trimmed, check out the trimmed
filter.
Lowercase
Converts a string to lowercase.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Lowercase )console .log (decode ("A")) // "a"console .log (decode (" AB")) // " ab"console .log (decode ("Ab ")) // "ab "console .log (decode (" ABc ")) // " abc "
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Lowercase )console .log (decode ("A")) // "a"console .log (decode (" AB")) // " ab"console .log (decode ("Ab ")) // "ab "console .log (decode (" ABc ")) // " abc "
If you were looking for a combinator to check if a string is lowercased,
check out the Schema.Lowercased
schema or the Schema.lowercased
filter.
Uppercase
Converts a string to uppercase.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Uppercase )console .log (decode ("a")) // "A"console .log (decode (" ab")) // " AB"console .log (decode ("aB ")) // "AB "console .log (decode (" abC ")) // " ABC "
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Uppercase )console .log (decode ("a")) // "A"console .log (decode (" ab")) // " AB"console .log (decode ("aB ")) // "AB "console .log (decode (" abC ")) // " ABC "
If you were looking for a combinator to check if a string is uppercased,
check out the Schema.Uppercased
schema or the Schema.uppercased
filter.
Capitalize
Converts a string to capitalized one.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Capitalize )console .log (decode ("aa")) // "Aa"console .log (decode (" ab")) // " ab"console .log (decode ("aB ")) // "AB "console .log (decode (" abC ")) // " abC "
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Capitalize )console .log (decode ("aa")) // "Aa"console .log (decode (" ab")) // " ab"console .log (decode ("aB ")) // "AB "console .log (decode (" abC ")) // " abC "
If you were looking for a combinator to check if a string is capitalized,
check out the Schema.Capitalized
schema or the Schema.capitalized
filter.
Uncapitalize
Converts a string to uncapitalized one.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Uncapitalize )console .log (decode ("AA")) // "aA"console .log (decode (" AB")) // " AB"console .log (decode ("Ab ")) // "ab "console .log (decode (" AbC ")) // " AbC "
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Uncapitalize )console .log (decode ("AA")) // "aA"console .log (decode (" AB")) // " AB"console .log (decode ("Ab ")) // "ab "console .log (decode (" AbC ")) // " AbC "
If you were looking for a combinator to check if a string is uncapitalized,
check out the Schema.Uncapitalized
schema or the Schema.uncapitalized
filter.
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.
ts
import {Schema } from "@effect/schema"constschema =Schema .parseJson ()constdecode =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*/
ts
import {Schema } from "@effect/schema"constschema =Schema .parseJson ()constdecode =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*/
Additionally, you can refine the parsing result by providing a schema to the parseJson
constructor:
ts
import {Schema } from "@effect/schema"// Schema<{ readonly a: number; }, string, never>constschema =Schema .parseJson (Schema .Struct ({a :Schema .Number }))
ts
import {Schema } from "@effect/schema"// Schema<{ readonly a: number; }, string, never>constschema =Schema .parseJson (Schema .Struct ({a :Schema .Number }))
In this example, we've used parseJson
with a struct schema to ensure that the parsed result has a specific structure, including an object with a numeric property "a". This helps in handling JSON data with predefined shapes.
StringFromBase64
Decodes a base64 (RFC4648) encoded string into a UTF-8 string.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromBase64 )console .log (decode ("Zm9vYmFy")) // "foobar"
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromBase64 )console .log (decode ("Zm9vYmFy")) // "foobar"
StringFromBase64Url
Decodes a base64 (URL) encoded string into a UTF-8 string.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromBase64Url )console .log (decode ("Zm9vYmFy")) // "foobar"
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromBase64Url )console .log (decode ("Zm9vYmFy")) // "foobar"
StringFromHex
Decodes a hex encoded string into a UTF-8 string.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromHex )console .log (newTextEncoder ().encode (decode ("0001020304050607")))/*Output:Uint8Array(8) [0, 1, 2, 3,4, 5, 6, 7]*/
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .StringFromHex )console .log (newTextEncoder ().encode (decode ("0001020304050607")))/*Output:Uint8Array(8) [0, 1, 2, 3,4, 5, 6, 7]*/
Number Transformations
NumberFromString
Transforms a string into a number by parsing the string using parseFloat
.
The following special string values are supported: "NaN", "Infinity", "-Infinity".
ts
import {Schema } from "@effect/schema"constschema =Schema .NumberFromString constdecode =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"*/
ts
import {Schema } from "@effect/schema"constschema =Schema .NumberFromString constdecode =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
Clamps a number between a minimum and a maximum value.
ts
import {Schema } from "@effect/schema"constschema =Schema .Number .pipe (Schema .clamp (-1, 1)) // clamps the input to -1 <= x <= 1constdecode =Schema .decodeUnknownSync (schema )console .log (decode (-3)) // -1console .log (decode (0)) // 0console .log (decode (3)) // 1
ts
import {Schema } from "@effect/schema"constschema =Schema .Number .pipe (Schema .clamp (-1, 1)) // clamps the input to -1 <= x <= 1constdecode =Schema .decodeUnknownSync (schema )console .log (decode (-3)) // -1console .log (decode (0)) // 0console .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".
ts
import {Schema } from "@effect/schema"constschema =Schema .String .pipe (Schema .parseNumber )constdecode =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 "-"*/
ts
import {Schema } from "@effect/schema"constschema =Schema .String .pipe (Schema .parseNumber )constdecode =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.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Not )console .log (decode (true)) // falseconsole .log (decode (false)) // true
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Not )console .log (decode (true)) // falseconsole .log (decode (false)) // true
Symbol transformations
Symbol
Transforms a string into a symbol by parsing the string using Symbol.for
.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Symbol )console .log (decode ("a")) // Symbol(a)
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Symbol )console .log (decode ("a")) // Symbol(a)
BigInt transformations
BigInt
Transforms a string into a BigInt
by parsing the string using the BigInt
constructor.
ts
import {Schema } from "@effect/schema"constdecode =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") // throws
ts
import {Schema } from "@effect/schema"constdecode =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") // throws
BigIntFromNumber
Transforms a number into a BigInt
by parsing the number using the BigInt
constructor.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigIntFromNumber )constencode =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) // throws
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigIntFromNumber )constencode =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) // throws
clamp
Clamps a BigInt
between a minimum and a maximum value.
ts
import {Schema } from "@effect/schema"// clamps the input to -1n <= x <= 1nconstschema =Schema .BigIntFromSelf .pipe (Schema .clampBigInt (-1n, 1n))constdecode =Schema .decodeUnknownSync (schema )console .log (decode (-3n)) // -1nconsole .log (decode (0n)) // 0nconsole .log (decode (3n)) // 1n
ts
import {Schema } from "@effect/schema"// clamps the input to -1n <= x <= 1nconstschema =Schema .BigIntFromSelf .pipe (Schema .clampBigInt (-1n, 1n))constdecode =Schema .decodeUnknownSync (schema )console .log (decode (-3n)) // -1nconsole .log (decode (0n)) // 0nconsole .log (decode (3n)) // 1n
Date transformations
Date
Transforms a string into a valid Date
, ensuring that invalid dates, such as new Date("Invalid Date")
, are rejected.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Date )console .log (decode ("1970-01-01T00:00:00.000Z")) // 1970-01-01T00:00:00.000Zdecode ("a")/*throws:ParseError: Date└─ Predicate refinement failure└─ Expected Date, actual Invalid Date*/constvalidate =Schema .validateSync (Schema .Date )console .log (validate (newDate (0))) // 1970-01-01T00:00:00.000Zvalidate (newDate ("Invalid Date"))/*throws:ParseError: Date└─ Predicate refinement failure└─ Expected Date, actual Invalid Date*/
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .Date )console .log (decode ("1970-01-01T00:00:00.000Z")) // 1970-01-01T00:00:00.000Zdecode ("a")/*throws:ParseError: Date└─ Predicate refinement failure└─ Expected Date, actual Invalid Date*/constvalidate =Schema .validateSync (Schema .Date )console .log (validate (newDate (0))) // 1970-01-01T00:00:00.000Zvalidate (newDate ("Invalid Date"))/*throws:ParseError: Date└─ Predicate refinement failure└─ Expected Date, actual Invalid Date*/
BigDecimal Transformations
BigDecimal
Transforms a string into a BigDecimal
.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigDecimal )console .log (decode (".124")) // { _id: 'BigDecimal', value: '124', scale: 3 }
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigDecimal )console .log (decode (".124")) // { _id: 'BigDecimal', value: '124', scale: 3 }
BigDecimalFromNumber
Transforms a number into a BigDecimal
.
When encoding, this Schema will produce incorrect results if the BigDecimal exceeds the 64-bit range of a number.
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigDecimalFromNumber )console .log (decode (0.111)) // { _id: 'BigDecimal', value: '111', scale: 3 }
ts
import {Schema } from "@effect/schema"constdecode =Schema .decodeUnknownSync (Schema .BigDecimalFromNumber )console .log (decode (0.111)) // { _id: 'BigDecimal', value: '111', scale: 3 }
clampBigDecimal
Clamps a BigDecimal
between a minimum and a maximum value.
ts
import {Schema } from "@effect/schema"import {BigDecimal } from "effect"constschema =Schema .BigDecimal .pipe (Schema .clampBigDecimal (BigDecimal .fromNumber (-1),BigDecimal .fromNumber (1)))constdecode =Schema .decodeUnknownSync (schema )console .log (decode ("-2")) // { _id: 'BigDecimal', value: '-1', scale: 0 }console .log (decode ("0")) // { _id: 'BigDecimal', value: '0', scale: 0 }console .log (decode ("3")) // { _id: 'BigDecimal', value: '1', scale: 0 }
ts
import {Schema } from "@effect/schema"import {BigDecimal } from "effect"constschema =Schema .BigDecimal .pipe (Schema .clampBigDecimal (BigDecimal .fromNumber (-1),BigDecimal .fromNumber (1)))constdecode =Schema .decodeUnknownSync (schema )console .log (decode ("-2")) // { _id: 'BigDecimal', value: '-1', scale: 0 }console .log (decode ("0")) // { _id: 'BigDecimal', value: '0', scale: 0 }console .log (decode ("3")) // { _id: 'BigDecimal', value: '1', scale: 0 }