Hyperlinkv0.8.0-beta.28

Schema

Schema.encodeKeysfunctioneffect/Schema.ts:3502
encodeKeys<S, M>

Renames struct keys in the encoded form without changing the decoded type.

Details

Takes a partial mapping { decodedKey: encodedKey } and produces a transformation schema that decodes from the renamed keys and encodes back to the renamed keys. Keys not present in the mapping are left unchanged. If two existing fields would produce the same encoded key, construction fails.

Example (Renaming name to full_name in the encoded form)

import { Schema } from "effect"

const Person = Schema.Struct({ name: Schema.String, age: Schema.Number })
const Encoded = Person.pipe(Schema.encodeKeys({ name: "full_name" }))

// Decodes { full_name: "Alice", age: 30 } → { name: "Alice", age: 30 }
const alice = Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 })
console.log(alice)
// { name: 'Alice', age: 30 }
transforming
Source effect/Schema.ts:350280 lines
export interface encodeKeys<
  S extends Constraint & { readonly fields: Struct.Fields },
  M extends { readonly [K in keyof S["fields"]]?: PropertyKey }
> extends
  decodeTo<
    S,
    Struct<
      {
        [
          K in keyof S["fields"] as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K
        ]: toEncoded<S["fields"][K]>
      }
    >
  >
{}

const canonicalPropertyKey = (key: PropertyKey): string | symbol =>
  typeof key === "symbol" ? key : globalThis.String(key)

/**
 * Renames struct keys in the encoded form without changing the decoded type.
 *
 * **Details**
 *
 * Takes a partial mapping `{ decodedKey: encodedKey }` and produces a
 * transformation schema that decodes from the renamed keys and encodes back to
 * the renamed keys. Keys not present in the mapping are left unchanged.
 * If two existing fields would produce the same encoded key, construction
 * fails.
 *
 * **Example** (Renaming `name` to `full_name` in the encoded form)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number })
 * const Encoded = Person.pipe(Schema.encodeKeys({ name: "full_name" }))
 *
 * // Decodes { full_name: "Alice", age: 30 } → { name: "Alice", age: 30 }
 * const alice = Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 })
 * console.log(alice)
 * // { name: 'Alice', age: 30 }
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function encodeKeys<
  S extends Constraint & { readonly fields: Struct.Fields },
  const M extends { readonly [K in keyof S["fields"]]?: PropertyKey }
>(mapping: M) {
  return function(self: S): encodeKeys<S, M> {
    const fields: any = {}
    const appliedMapping: any = {}
    const reverseMapping: any = {}
    const seenEncodedKeys = new Set<string | symbol>()
    for (const k of Reflect.ownKeys(self.fields)) {
      const encoded = toEncoded(self.fields[k])
      const hasMapping = Object.hasOwn(mapping, k)
      const encodedKey = hasMapping ? (mapping as any)[k] as PropertyKey : k
      const canonical = canonicalPropertyKey(encodedKey)
      if (seenEncodedKeys.has(canonical)) {
        throw new globalThis.Error(`Duplicate encoded keys: ${formatPropertyKey(encodedKey)}`)
      }
      seenEncodedKeys.add(canonical)
      fields[encodedKey] = encoded
      if (hasMapping) {
        appliedMapping[k] = encodedKey
        reverseMapping[encodedKey] = k
      }
    }
    return Struct(fields).pipe(decodeTo(
      self,
      SchemaTransformation.transform<any, any>({
        decode: Struct_.renameKeys(reverseMapping),
        encode: Struct_.renameKeys(appliedMapping)
      })
    )) as any
  }
}