# Decoder overview

A Decoder is a function, that from an input I to create an output O, or produce an DecodeError. As such, a common use-case for Decoders are type and value validations.

This namespace contains the core utilities to:

  • Create Decoders
  • Use or combine them
  • Extract / infer the resulting type informations
export const TodoDto = ObjectDecoder.struct({
  id: IntegerDecoder.int,
  title: TextDecoder.varchar(1, 100),
  description: pipe(
    TextDecoder.varchar(0, 2000),
    TextDecoder.nullable
  ),
  done: BooleanDecoder.boolean
})

export interface TodoDto extends Decoder.TypeOf<typeof TodoDto> {}

# Summary

# Functions

# Decoder.create

# Description

Create a new decoder

<I, O>(fn: (input: I) => any) => Decoder<I, O>

# Decoder.fromGuard

# Description

Creates a new decoder from a type guard

<I, O extends I>(fn: any, message: string, meta?: any) => Decoder<I, O>

# Example

const stringDecoder = Decoder.fromGuard(
  (input: unknown): input is string => typeof input === 'string',
  'value is not a string'
)

expect(pipe('Hello', Decoder.validate(decoder), Result.isOk)).toBe(true)
expect(pipe(42, Decoder.validate(decoder), Result.isKo)).toBe(true)

# Decoder.map

# Description

Map over the resulting value of an Decoder

<A, B>(fn: (input: A) => B) => <I>(decoder: Decoder<I, A>) => Decoder<I, B>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.map(str => str.trim())
)

expect(pipe('  Hello  ', Decoder.validate(decoder), Result.get)).toBe('Hello')

# Decoder.mapError

# Description

Map over the resulting error of an Decoder

<I>(fn: (err: DecodeError, input: I) => DecodeError) => <A>(decoder: Decoder<I, A>) => Decoder<I, A>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.mapError(err => DecodeError.object([
    DecodeError.key('firstName', err)
  ]))
)

# Decoder.withMessage

# Description

Catch the validation error and create a new error with the given message.

(msg: string, meta?: any) => <I, A>(decoder: Decoder<I, A>) => Decoder<I, A>

# Example

const decoder = pipe(
  Decoder.union(
    NumberDecoder.number,
    NumberDecoder.fromString
  ),
  Decoder.withMessage('The given value is not a number', {
    code: 'invalid_number'
  })
)

const expectedError = DecodeError.value('  Hello  ', 'This value is not a number', {
  code: 'invalid_number'
})
expect(pipe('  Hello  ', Decoder.validate(decoder))).toEqual(Result.ko(expectedError))

# Decoder.parse

# Description

Add a custom validation function to an Decoder.

Compared to Decoder.guard, this function gives more control and allows the user to modify the resulting value.

<B, C>(fn: (input: B) => any) => <A>(decoder: Decoder<A, B>) => Decoder<A, C>

# Example

const validateAge = (date: Date): Result<string, DecodeError> => {
  const now = new Date()

  if (date.getFullYear() < now.getFullYear() - 100) {
    return Result.ko(DecodeError.value(dob, 'Date of birth is more than 100 years ago'))
  }
  if (date.getFullYear() > now.getFullYear() - 18) {
    return Result.ko(DecodeError.value(dob, 'Date of birth is less than 18 years ago'))
  }
  return Result.ok(dob)
}

const birthdayDecoder = pipe(
  DateDecoder.date,
  Decoder.parse(validateAge)
)

expect(pipe('1930-01-01', Decoder.validate(birthdayDecoder), Result.isOk)).toBe(true)
expect(pipe('1920-01-01', Decoder.validate(birthdayDecoder), Result.isKo)).toBe(true)

# References

  • Decoder.guard
  • Decoder.filter
  • Decoder.reject

# Decoder.chain

# Description

Chain another decoder to execute with the current input. This allows you to dynamically compute the decoder to use depending on a value.

<B, C>(fn: (input: B) => Decoder<B, C>) => <A>(decoder: Decoder<A, B>) => Decoder<A, C>

# Decoder.guard

# Description

Add a custom validation function, returning either:

  • A DecodeError if the input is incorrect
  • undefined if there is no error to report.

This function gives more control about the returned error than Decoder.filter or Decoder.reject, but does not allow the value to be modified.

<O>(fn: (input: O) => any) => <A>(decoder: Decoder<A, O>) => Decoder<A, unknown>

# Example

const validateAge = (date: Date): Option<DecodeError> => {
  const now = new Date()

  if (date.getFullYear() < now.getFullYear() - 100) {
    return DecodeError.value(dob, 'Date of birth is more than 100 years ago')
  }
  if (date.getFullYear() > now.getFullYear() - 18) {
    return DecodeError.value(dob, 'Date of birth is less than 18 years ago')
  }
  return undefined
}

const birthdayDecoder = pipe(
  DateDecoder.date,
  Decoder.guard(validateAge)
)

expect(pipe('1930-01-01', Decoder.validate(birthdayDecoder), Result.isOk)).toBe(true)
expect(pipe('1920-01-01', Decoder.validate(birthdayDecoder), Result.isKo)).toBe(true)

# References

  • Decoder.parse
  • Decoder.filter
  • Decoder.reject

# Decoder.filter

# Description

Add a Predicate filter function to a Decoder. If the value matches the Predicate, the value is kept. Else, an DecodeError with the given message is returned.

<A, B extends A>(fn: any, message: string, meta?: any): <I>(value: Decoder<I, A>) => Decoder<I, B>
<A>(fn: any, message: string, meta?: any): <I>(value: Decoder<I, A>) => Decoder<I, A>

# Example

const maxAge = (age: number) => (dob: Date) => {
  const now = new Date()
  return date.getFullYear() > now.getFullYear() - age
}
const minAge = (age: number) => (dob: Date) => {
  const now = new Date()
  return date.getFullYear() < now.getFullYear() - age
}

const birthdayDecoder = pipe(
  DateDecoder.date,
  Decoder.filter(maxAge(100), `Date of birth is more than 100 years ago`),
  Decoder.filter(minAge(18), `Date of birth is less than 18 years ago`)
)

expect(pipe('1930-01-01', Decoder.validate(birthdayDecoder), Result.isOk)).toBe(true)
expect(pipe('1920-01-01', Decoder.validate(birthdayDecoder), Result.isKo)).toBe(true)

# References

  • Decoder.parse
  • Decoder.guard
  • Decoder.reject
  • DateDecoder.min
  • DateDecoder.max

# Decoder.reject

# Description

Add a Predicate filter function to a Decoder. If the value matches the Predicate, an DecodeError with the given message is returned. Else, the value is kept.

<A, B extends A>(fn: any, message: string, meta?: any): <I>(value: Decoder<I, A>) => Decoder<I, any>
<A>(fn: any, message: string, meta?: any): <I>(value: Decoder<I, A>) => Decoder<I, A>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.reject(str => str.length === 0, `string should not be empty`)
)

expect(pipe('Hello', Decoder.validate(decoder), Result.isOk)).toBe(true)
expect(pipe('', Decoder.validate(decoder), Result.isKo)).toBe(true)

# References

  • Decoder.parse
  • Decoder.guard
  • Decoder.filter

# Decoder.nullable

# Description

Makes the value nullable. If the input is undefined, the decoder will return null instead.

Note: If you want to transform an empty string to null, use TextDecoder.nullable instead.

<I, O>(decoder: Decoder<I, O>) => Decoder<I, O | null>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.nullable
)

expect(pipe('Hello', Decoder.validate(decoder), Result.get)).toBe('Hello')
expect(pipe('', Decoder.validate(decoder), Result.get)).toBe('')
expect(pipe(null, Decoder.validate(decoder), Result.get)).toBe(null)
expect(pipe(undefined, Decoder.validate(decoder), Result.get)).toBe(null)

# References

  • Decoder.optional
  • TextDecoder.nullable

# Decoder.optional

# Description

Makes the value optional (allows undefined).

Note: If you want to transform an empty string to undefined, use TextDecoder.optional instead.

<I, O>(decoder: Decoder<I, O>) => Decoder<I, O | undefined>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.optional
)

expect(pipe('Hello', Decoder.validate(decoder), Result.get)).toBe('Hello')
expect(pipe('', Decoder.validate(decoder), Result.get)).toBe('')
expect(pipe(undefined, Decoder.validate(decoder), Result.get)).toBe(undefined)

# References

  • Decoder.nullable
  • TextDecoder.optional

# Decoder.required

# Description

Explicitely return an "input is required" error when input is "null" or "undefined"

<I, O>(decoder: Decoder<I, O>) => Decoder<I, O>

# Example

const decoder = pipe(
  TextDecoder.string,
  Decoder.required
)

# Decoder.lazy

# Description

This function allows the creation of recursive type decoders.

<I, O>(fn: () => Decoder<I, O>) => Decoder<I, O>

# Example

interface Tree<T> {
  value: T
  forest: Tree<T>[]
}

// Recursive types require manual typing
const TreeDecoder = <O>(decoder: Decoder<unknown, O>): Decoder<unknown, Tree<O>> =>
  Decoder.lazy(() =>
    ObjectDecoder.struct({
      value: decoder,
      forest: ArrayDecoder.array(TreeDecoder(decoder))
    })
  )

const input: unknown = {
  value: 'Hello',
  forest: [
    {
       value: 'World',
       forest: []
    }
  ]
}

expect(pipe(input, TreeDecoder(TextDecoder.string), Result.isOk)).toBe(true)

# Decoder.default

# Description

This operator makes the decoder optional and returns the given value when the input is undefined.

(value: Array<never>): <I, O extends Array<any>>(decoder: Decoder<I, O | undefined>) => Decoder<I, O>
<T>(value: T): <I, O>(decoder: Decoder<I, O | undefined>) => Decoder<I, T | O>

# Example

const decoder = pipe(
  BooleanDecoder.boolean,
  Decoder.default(false)
)

expect(pipe(true, Decoder.validate(decoder), Result.get)).toEqual(true)
expect(pipe(false, Decoder.validate(decoder), Result.get)).toEqual(false)
expect(pipe(undefined, Decoder.validate(decoder), Result.get)).toEqual(false)

# Decoder.union

# Description

Creates a union Decoder that tries, in the given order, if the input is valid.

<I, O1, O2>(a: Decoder<I, O1>, b: Decoder<I, O2>): Decoder<I, O1 | O2>
<I, O1, O2, O3>(a: Decoder<I, O1>, b: Decoder<I, O2>, c: Decoder<I, O3>): Decoder<I, O1 | O2 | O3>
<I, O1, O2, O3, O4>(a: Decoder<I, O1>, b: Decoder<I, O2>, c: Decoder<I, O3>, d: Decoder<I, O4>): Decoder<I, O1 | O2 | O3 | O4>

# Example

const decoder = Decoder.union(
  NumberDecoder.number,
  NumberDecoder.fromString
)

expect(pipe(42, Decoder.validate(decoder), Result.isOk)).toBe(true)
expect(pipe("42", Decoder.validate(decoder), Result.isOk)).toBe(true)
expect(pipe("Hello", Decoder.validate(decoder), Result.isKo)).toBe(true)

# Decoder.ref

# Description

Utility to bind a given generated type to a Decoder

<A>(decoder: Decoder<unknown, A>) => Decoder<unknown, A>

# Decoder.validate

# Description

Validate an input by the given Decoder

<O>(decoder: Decoder<unknown, O>): (input: unknown) => any
<I, O>(decoder: Decoder<I, O>): (input: I) => any

# Example

const result = pipe(
  input,
  Decoder.validate(TextDecoder.string)
)

if (Result.isKo(result)) {
  console.log(result.ko)
  return
}

const value = result.ok
console.log(value)