ts.data.json - v4.2.1
    Preparing search index...

    Basic Usage

    ts.data.json helps you validate JSON data at runtime with compile-time type safety. This guide will show you how to use the library effectively.

    Let's start with the basics. Here's how to decode simple JSON values:

    import * as JsonDecoder from 'ts.data.json';

    // String decoder
    const nameDecoder = JsonDecoder.string();
    nameDecoder.decode('John'); // Ok({ value: 'John' })
    nameDecoder.decode(123); // Err({ issues: [{ message: '123 is not a valid string', path: [] }] })

    // Number decoder
    const ageDecoder = JsonDecoder.number();
    ageDecoder.decode(25); // Ok({ value: 25 })
    ageDecoder.decode('25'); // Err({ issues: [{ message: '"25" is not a valid number', path: [] }] })

    // Boolean decoder
    const isActiveDecoder = JsonDecoder.boolean();
    isActiveDecoder.decode(true); // Ok({ value: true })
    isActiveDecoder.decode('true'); // Err({ issues: [{ message: '"true" is not a valid boolean', path: [] }] })

    Decode against a TypeScript enum with enumeration. A rejected value is formatted with JSON.stringify, just like the other primitive decoders, so the quoting depends on the value's type:

    enum Color {
    Red = 'red',
    Blue = 'blue'
    }

    const colorDecoder = JsonDecoder.enumeration<Color>(Color);
    colorDecoder.decode('red'); // Ok({ value: 'red' })

    // A string value keeps its quotes...
    colorDecoder.decode('green');
    // Err({ issues: [{ message: '"green" is not a valid enum value', path: [] }] })

    // ...while a numeric value is rendered without quotes.
    enum Priority {
    Low = 1,
    High = 2
    }

    const priorityDecoder = JsonDecoder.enumeration<Priority>(Priority);
    priorityDecoder.decode(3);
    // Err({ issues: [{ message: '3 is not a valid enum value', path: [] }] })

    Most of the time, you'll work with objects. Here's how to decode them:

    // Define your type
    interface User {
    id: number;
    name: string;
    email: string;
    age?: number; // Optional field
    }

    // Create a decoder
    const userDecoder = JsonDecoder.object<User>({
    id: JsonDecoder.number(),
    name: JsonDecoder.string(),
    email: JsonDecoder.string(),
    age: JsonDecoder.optional(JsonDecoder.number())
    });

    // Valid data
    const validJson = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    age: 30
    };

    const user = await userDecoder.decodePromise(validJson);
    console.log(`Hello ${user.name}!`); // Hello John Doe!

    // Invalid data -- all field errors are reported at once
    const invalidJson = {
    id: 'not-a-number',
    name: 'John Doe',
    email: 'john@example.com'
    };

    try {
    await userDecoder.decodePromise(invalidJson);
    } catch (error) {
    console.log(error.message); // 'id: "not-a-number" is not a valid number'
    }

    For complex objects with nested structures:

    interface Address {
    street: string;
    city: string;
    country: string;
    }

    interface User {
    id: number;
    name: string;
    address: Address;
    }

    // Create decoders for nested structures
    const addressDecoder = JsonDecoder.object<Address>({
    street: JsonDecoder.string(),
    city: JsonDecoder.string(),
    country: JsonDecoder.string()
    });

    const userDecoder = JsonDecoder.object<User>({
    id: JsonDecoder.number(),
    name: JsonDecoder.string(),
    address: addressDecoder // Use the nested decoder
    });

    const json = {
    id: 1,
    name: 'John Doe',
    address: {
    street: '123 Main St',
    city: 'Boston',
    country: 'USA'
    }
    };

    console.log(
    await userDecoder.decodePromise(json).then(user => `${user.name} lives in ${user.address.city}`) // John Doe lives in Boston
    );

    Decoding arrays of values:

    // Array of strings
    const tagsDecoder = JsonDecoder.array(JsonDecoder.string());
    tagsDecoder.decode(['typescript', 'json', 'decoder']); // Ok({ value: ["typescript", "json", "decoder"] })
    tagsDecoder.decode(['typescript', 123, 'decoder']);
    // Err({ issues: [{ message: '123 is not a valid string', path: [1] }] })

    // Array of objects
    const usersDecoder = JsonDecoder.array(userDecoder);
    await usersDecoder
    .decodePromise([
    { id: 1, name: 'John', email: 'john@example.com' },
    { id: 2, name: 'Jane', email: 'jane@example.com' }
    ])
    .then(users => users.map(user => user.id)); // [1, 2]

    usersDecoder.decode([
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane', email: 'jane@example.com' }
    ]);
    // Err({ issues: [{ message: 'undefined is not a valid string', path: [0, 'email'] }] })

    Use fallback to provide fallback values:

    const numberOrZero = JsonDecoder.fallback(0, JsonDecoder.number());

    numberOrZero.decode('not a number'); // Ok({ value: 0 })

    You can use other strategies combining other decoders:

    const statusDecoder = JsonDecoder.oneOf([
    JsonDecoder.literal('active'),
    JsonDecoder.literal('inactive'),
    JsonDecoder.constant('unknown') // always succeeds with 'unknown'
    ]);
    statusDecoder.decode('inactive'); // Ok({ value: 'inactive' })
    statusDecoder.decode('zxytwqgtyb'); // Ok({ value: 'unknown' })

    The library uses a Result type to handle success and failure cases safely:

    const myUserResult: JsonDecoder.Result<User> = userDecoder.decode(validUserJson);
    const uppercasedUserEmail: JsonDecoder.Result<string> = myUserResult
    .map(user => {
    return user.email;
    })
    .map(email => {
    return email.toUpperCase();
    });
    // isOk() is a type guard
    if (uppercasedUserEmail.isOk()) {
    console.log(uppercasedUserEmail.value); // JOHN@EXAMPLE.COM
    }

    When a decode fails, the Err result holds an issues array. Each entry contains a human-readable message and a path pointing to the failing field:

    const result = userDecoder.decode({ id: 'bad', name: 42, email: 'john@example.com' });
    if (!result.isOk()) {
    result.issues.forEach(issue => {
    const location = issue.path.length > 0 ? JsonDecoder.formatIssuePath(issue.path) : 'root';
    console.log(`${location}: ${issue.message}`);
    // id: "bad" is not a valid number
    // name: 42 is not a valid string
    });
    }

    You can use the FromDecoder type to infer types from decoders:

    import { FromDecoder } from 'ts.data.json';

    const userDecoder = JsonDecoder.object({
    id: JsonDecoder.number(),
    name: JsonDecoder.string(),
    email: JsonDecoder.string()
    });

    // Instead of manually defining the User interface:
    type User = JsonDecoder.FromDecoder<typeof userDecoder>;
    // type User = { id: number; name: string; email: string }

    Choosing an Entry Point

    Everything above uses the main entry point, where a decoder is a class instance and you call methods on it. There is a second entry point, ts.data.json/mini, where a decoder is a plain function and the methods are standalone imports:

    import * as J from 'ts.data.json/mini';

    const userDecoder = J.object({
    id: J.number(),
    name: J.string()
    });

    J.decode(userDecoder, json); // instead of userDecoder.decode(json)
    J.parse(userDecoder, json); // instead of userDecoder.parse(json)
    J.map(J.string(), s => s.trim()); // instead of .map(s => s.trim())

    Both entry points validate identically and report the same issues. mini exists to drop the class wrapper, which saves about 1 kB minified: roughly 450 bytes instead of 1450 for a single decoder. Use it when you are counting bytes, and the main entry point when you prefer the chained method style.

    Decoders from the two entry points are different types and cannot be mixed, so pick one per project.

    1. Reuse Decoders: Create reusable decoders for common patterns:

      const numToStringDecoder = JsonDecoder.number().map(n => n.toString(10));
      numToStringDecoder.decode(123); // Ok({ value: "123" })

      const dateDecoder = JsonDecoder.string().flatMap(...);
      const emailDecoder = JsonDecoder.string().flatMap(...);
    2. Type Safety: Let TypeScript help you by using type annotations and inference:

       const myDecoder = JsonDecoder.object(...);
    type User = JsonDecoder.FromDecoder<typeof myDecoder>;