Transformations
A transformation connects two schemas with a pair of functions — one that runs
while decoding (Encoded → Type) and one that runs while encoding
(Type → Encoded). This is how a schema can store a date as a string on the
wire yet hand you a real Date, or accept "42" and give you the number 42.
import { Schema, SchemaGetter } from "effect"
// `from.pipe(Schema.decodeTo(to, { decode, encode }))`// - decode runs Encoded -> Type (here: string -> number)// - encode runs Type -> Encoded (here: number -> string)const NumberFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, { decode: SchemaGetter.Number(), // parse the string into a number encode: SchemaGetter.String() // serialize the number back to a string }))
console.log(Schema.decodeUnknownSync(NumberFromString)("123")) // 123 (number)console.log(Schema.encodeUnknownSync(NumberFromString)(123)) // "123" (string)decodeTo
Section titled “decodeTo”Schema.decodeTo(to, transformation) is curried: you call it with the target
schema and then pipe the source schema into it. The result has
Encoded = from.Encoded and Type = to.Type.
The transformation object holds two getters:
decode: turns the source’s decoded value into the target’s encoded value (used when decoding).encode: turns the target’s encoded value back into the source’s decoded value (used when encoding).
SchemaGetter provides ready-made getters. SchemaGetter.transform(f) lifts a
plain function; SchemaGetter.Number(), SchemaGetter.String(),
SchemaGetter.trim(), and friends cover common conversions.
import { Schema, SchemaGetter } from "effect"
// Trim surrounding whitespace while decoding (and again while encoding).const TrimmedString = Schema.String.pipe( Schema.decode({ decode: SchemaGetter.trim(), encode: SchemaGetter.passthrough() }))
console.log(Schema.decodeUnknownSync(TrimmedString)(" hello ")) // "hello"Transformations that can fail
Section titled “Transformations that can fail”Decoding often involves parsing that may not succeed. Use
SchemaGetter.transformOrFail, which returns an Effect and reports failures as
a SchemaIssue. The failure is woven into the normal decode error tree, so it
benefits from path tracking and error formatting.
import { Effect, Option, Schema, SchemaGetter, SchemaIssue } from "effect"
// A schema that decodes a string into an integer, failing cleanly on bad input.const IntFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, { decode: SchemaGetter.transformOrFail((s: string) => { const n = Number.parseInt(s, 10) return Number.isNaN(n) ? Effect.fail( new SchemaIssue.InvalidValue(Option.some(s), { message: "expected an integer string" }) ) : Effect.succeed(n) }), encode: SchemaGetter.String() }))
console.log(Schema.decodeUnknownSync(IntFromString)("7")) // 7// Schema.decodeUnknownSync(IntFromString)("abc") -> throws "expected an integer string"encodeTo
Section titled “encodeTo”Schema.encodeTo is the mirror image: it lets you name the encoded schema
first. to.pipe(Schema.encodeTo(encodedSchema, { decode, encode })) reads
naturally when you think of a value being encoded outward. It is otherwise
equivalent to decodeTo with the schemas swapped.
import { Schema, SchemaGetter } from "effect"
const NumberFromString = Schema.Number.pipe( Schema.encodeTo(Schema.String, { decode: SchemaGetter.transform((s: string) => Number(s)), encode: SchemaGetter.transform((n: number) => String(n)) }))Composing transformations
Section titled “Composing transformations”Because a transformation produces a regular schema, you can keep piping. A common pattern is to parse a JSON string and then validate its shape — chaining a string→JSON transformation into a struct schema.
import { Schema } from "effect"
const Payload = Schema.Struct({ id: Schema.String, count: Schema.Number})
// Encoded: a JSON string -> Type: the validated Payload object.const PayloadFromJson = Schema.fromJsonString(Payload)
const value = Schema.decodeUnknownSync(PayloadFromJson)( '{"id":"a","count":3}')
console.log(value) // { id: "a", count: 3 }Built-in transformations
Section titled “Built-in transformations”Many useful transformations ship ready to use, so you rarely write the getters by hand:
| Schema | Encoded → Type |
| -------------------------- | --------------------- |
| Schema.NumberFromString | string → number |
| Schema.BigIntFromString | string → bigint |
| Schema.DateFromString | string → Date |
| Schema.Trim | string → trimmed string |
| Schema.fromJsonString(S) | JSON string → S |
Reach for these first; drop down to decodeTo/encodeTo only when you need a
conversion that is not already provided.