Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

RenameKeys

Giulio Canti edited this page Jun 13, 2023 · 3 revisions

Manual transformation

@effect/schema version: v0.20.1

Goal: just make it happen.

import * as S from '@effect/schema/Schema'

const From = S.struct({
  key_one: S.string,
  key_two: S.string
})

const To = S.struct({
  keyOne: S.string,
  keyTwo: S.string
})

export const schema = S.transform(
  From,
  To,
  (from) => ({ keyOne: from.key_one, keyTwo: from.key_two }),
  (to) => ({ key_one: to.keyOne, key_two: to.keyTwo })
)

console.log(S.decode(schema)({ key_one: 'a', key_two: 'b' }))
// { keyOne: 'a', keyTwo: 'b' }

One downside to this method is that it is boilerplate and can become repetitive for larger schemas.

Another downside is that it does not work well with the extend combinator:

// throws "`extend` can only handle type literals or unions of type literals"
export const schema2 = S.extend(schema, S.struct({ a: S.number }))

Manual property signatures transformations

Goal: extend works.

import * as S from '@effect/schema/Schema'
import * as AST from '@effect/schema/AST'
import { identity } from '@effect/data/Function'

const From = S.struct({
  key_one: S.string,
  key_two: S.string
})

const To = S.struct({
  keyOne: S.string,
  keyTwo: S.string
})

export const schema: S.Schema<
  {
    readonly key_one: string
    readonly key_two: string
  },
  {
    readonly keyOne: string
    readonly keyTwo: string
  }
> = S.make(
  AST.createTransformByPropertySignatureTransformations(From.ast, To.ast, [
    AST.createPropertySignatureTransformation(
      'key_one',
      'keyOne',
      identity,
      identity
    ),
    AST.createPropertySignatureTransformation(
      'key_two',
      'keyTwo',
      identity,
      identity
    )
  ])
)

console.log(S.decode(schema)({ key_one: 'a', key_two: 'b' }))
// { keyOne: 'a', keyTwo: 'b' }

// ok
export const schema2 = S.extend(schema, S.struct({ a: S.number }))

console.log(S.decode(schema2)({ key_one: 'a', key_two: 'b', a: 1 }))
// { keyOne: 'a', keyTwo: 'b', a: 1 }

Property signatures transformations via API

Goal: less boilerplate.

import * as S from '@effect/schema/Schema'
import * as AST from '@effect/schema/AST'
import { identity } from '@effect/data/Function'

// from @effect/schema/internal/common.ts
const ownKeys = (o: object): ReadonlyArray<PropertyKey> =>
  (Object.keys(o) as ReadonlyArray<PropertyKey>).concat(
    Object.getOwnPropertySymbols(o)
  )

const rename = <A, Mapping extends Record<keyof A, PropertyKey>>(
  schema: S.Schema<A>,
  mapping: Mapping
): S.Schema<A, { [K in keyof A as Mapping[K]]: A[K] }> => {
  const from = schema.ast
  if (AST.isTypeLiteral(from)) {
    const to = AST.createTypeLiteral(
      from.propertySignatures.map((p) =>
        AST.createPropertySignature(
          (mapping as any)[p.name],
          p.type,
          p.isOptional,
          p.isReadonly,
          p.annotations
        )
      ),
      from.indexSignatures
    )
    return S.make(
      AST.createTransformByPropertySignatureTransformations(
        from,
        to,
        ownKeys(mapping).map((from) =>
          AST.createPropertySignatureTransformation(
            from,
            (mapping as any)[from],
            identity,
            identity
          )
        )
      )
    )
  }
  throw new Error('`rename` can only handle type literals')
}

const From = S.struct({
  key_one: S.string,
  key_two: S.number
})

const schema = rename(From, { key_one: 'keyOne', key_two: 'keyTwo' } as const)

console.log(S.decode(schema)({ key_one: 'a', key_two: 1 }))
// { keyOne: 'a', keyTwo: 1 }

// ok
export const schema2 = S.extend(schema, S.struct({ a: S.boolean }))

console.log(S.decode(schema2)({ key_one: 'a', key_two: 1, a: true }))
// { a: true, keyOne: 'a', keyTwo: 1 }
Clone this wiki locally