Skip to main content
The decoders library uses Result types to represent the outcome of decoding operations. This provides a type-safe way to handle both successful and failed validations without throwing exceptions.

Result<T, E>

A discriminated union type representing either a successful result or an error.
type Result<T, E> = Ok<T> | Err<E>

Type Parameters

T
type
The type of the success value
E
type
The type of the error value

Ok<T>

Represents a successful computation.
type Ok<T> = {
  readonly ok: true;
  readonly value: T;
  readonly error?: never;
}

Properties

ok
true
Discriminant property that’s always true for successful results
value
T
The successful result value
error
never
Always undefined for successful results (TypeScript will prevent access)

Err<E>

Represents a failed computation.
type Err<E> = {
  readonly ok: false;
  readonly value?: never;
  readonly error: E;
}

Properties

ok
false
Discriminant property that’s always false for error results
value
never
Always undefined for error results (TypeScript will prevent access)
error
E
The error value

DecodeResult<T>

A specialized Result type used by decoders, where errors are always Annotation objects.
type DecodeResult<T> = Result<T, Annotation>
This is equivalent to:
type DecodeResult<T> = Ok<T> | Err<Annotation>

Constructor Functions

ok()

Creates a successful Result.
value
T
required
The success value
Returns: Ok<T>
import { ok } from 'decoders';

const result = ok(42);
console.log(result.ok); // true
console.log(result.value); // 42
console.log(result.error); // undefined

err()

Creates an error Result.
error
E
required
The error value
Returns: Err<E>
import { err } from 'decoders';

const result = err('Something went wrong');
console.log(result.ok); // false
console.log(result.value); // undefined
console.log(result.error); // 'Something went wrong'

// With Error object
const result2 = err(new Error('Validation failed'));
console.log(result2.ok); // false
console.log(result2.error); // Error object

Usage Examples

Pattern Matching with Type Guards

The ok property acts as a discriminant for type-safe pattern matching:
import { string } from 'decoders';

const result = string.decode(input);

if (result.ok) {
  // TypeScript knows this is Ok<string>
  console.log(result.value); // string
  // result.error // ❌ TypeScript error - property doesn't exist
} else {
  // TypeScript knows this is Err<Annotation>
  console.log(result.error); // Annotation
  // result.value // ❌ TypeScript error - property doesn't exist
}

Handling Results

import { number } from 'decoders';

function processNumber(input: unknown): number | null {
  const result = number.decode(input);
  
  if (result.ok) {
    return result.value;
  }
  
  console.error('Validation failed:', result.error);
  return null;
}

console.log(processNumber(42)); // 42
console.log(processNumber('not a number')); // null

Accessing Result Properties

import { ok, err } from 'decoders';

const success = ok(42);
console.log(success.ok); // true
console.log(success.value); // 42
console.log(success.error); // undefined

const failure = err('Oops');
console.log(failure.ok); // false
console.log(failure.value); // undefined
console.log(failure.error); // 'Oops'

Using in Custom Decoders

import { define, type DecodeResult } from 'decoders';

const positiveNumber = define((blob, ok, err): DecodeResult<number> => {
  if (typeof blob !== 'number') {
    return err('Must be a number');
  }
  
  if (blob <= 0) {
    return err('Must be positive');
  }
  
  return ok(blob);
});

const result = positiveNumber.decode(5);
if (result.ok) {
  console.log('Valid:', result.value);
}

Extracting Values Safely

Use the .value property of the Result, which is undefined for errors:
import { string } from 'decoders';

const result1 = string.decode('hello');
const result2 = string.decode(123);

console.log(result1.value); // 'hello'
console.log(result2.value); // undefined
Or use the decoder’s .value() method for convenience:
import { string } from 'decoders';

const value1 = string.value('hello'); // 'hello'
const value2 = string.value(123);     // undefined

Comparison with Exceptions

Traditional Exception Handling

try {
  const value = string.verify(input);
  console.log('Success:', value);
} catch (error) {
  console.error('Failed:', error);
}

Result-Based Handling

const result = string.decode(input);

if (result.ok) {
  console.log('Success:', result.value);
} else {
  console.error('Failed:', result.error);
}

Benefits of Result Types

Type Safety

TypeScript enforces that you handle both success and error cases:
import { number } from 'decoders';

const result = number.decode(input);

// ❌ TypeScript error - value might not exist
const x = result.value.toFixed(2);

// ✅ Correct - check the discriminant first
if (result.ok) {
  const x = result.value.toFixed(2);
}

No Exceptions in Happy Path

Results allow you to handle validation without try-catch blocks:
import { object, string, number } from 'decoders';

const userDecoder = object({
  name: string,
  age: number,
});

const result = userDecoder.decode(data);

if (result.ok) {
  // Use the validated data
  saveUser(result.value);
} else {
  // Handle the error
  return { error: formatError(result.error) };
}

Composability

Results can be easily composed and transformed:
import { string, number } from 'decoders';

function parseNumbers(inputs: unknown[]): number[] {
  const results = inputs.map(input => number.decode(input));
  
  // Filter out errors
  return results
    .filter(r => r.ok)
    .map(r => r.value);
}

// Or fail fast on first error
function parseNumbersStrict(inputs: unknown[]): number[] | null {
  const numbers: number[] = [];
  
  for (const input of inputs) {
    const result = number.decode(input);
    if (!result.ok) {
      return null; // Stop on first error
    }
    numbers.push(result.value);
  }
  
  return numbers;
}
  • Decoder<T> - The decoder class that produces DecodeResults
  • Annotation - The error type used in DecodeResults (see formatting documentation)

See Also

  • Decoder Class - Methods for working with decoders
  • define() - Creating custom decoders that return Results

Build docs developers (and LLMs) love