npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

validate-structure

v2.0.0

Published

Check that an object matches the expected structure.

Downloads

8

Readme

validate-structure

Check that an object matches the expected structure.

Installation

npm i validate-structure

Note: Node 14+ is required.

Usage

Functions

validateStructure()

Signature: validateStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): ValidationError[]

Arguments:

  1. val The value to validate.
  2. structure The expected structure of val. This must be a Structure.
  3. (optional) strict Whether or not extra keys should be treated as a failure (defaults to true)
  4. (optional) customTypes Any custom types that you want to refer to multiple times can be placed here for convenience. This should be an object where each key is the name of the type and the value is a Structure.

matchesStructure()

Signature: matchesStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): boolean

This is a simple wrapper around validateStructure() that returns true if and only if there were no errors returned by validateStructure(). It can be used if you only care that it doesn't match, but if you need to know what didn't match you should use validateStructure() directly.

Structure

validate-structure is built around a robust type system, which means it can handle any data structure you throw at it.

Basic Structure

The most basic Structure is a single string representing a native type. The following typeofs are supported out-of-the-box:

  1. "any" (matches anything except null and undefined)
  2. "null"
  3. "undefined" (note that this still requires the key to be present)
  4. "boolean"
  5. "number"
  6. "bigint"
  7. "string"
  8. "symbol"
  9. "function"

There are also some non-typeof checks provided as well:

  1. "int" Matches any integer
  2. "array" Matches any array
  3. "object" Matches any object that is not an array or null

Operators

Sometimes you might find that just the basic types aren't enough. That's why validate-structure has a range of operators you can use to form complex Structures.

  1. "<type>?" Optional: matches "<type>" or null or undefined.
  2. "<type>[]" Array: matches an array of "<type>".
    • To ensure that an array's length is valid, use <type>[min...max]
    • If you need to enforce a minimum length you can leave off max: <type>[min...]
    • Likewise for a maximum length: <type>[...max]
    • If your array has a fixed length (min === max) you can use the shorthand form <type>[N]
    • If the array can't be empty, you can either use <type>[1...] or the shorthand form <type>[.]
    • If null values are allowed inside the array, put a ? between the <type> and the [ (like so: int?[]).
  3. "[<type 1>, ..., <type n>]" Tuple: matches an array of "<type>"s with strict length. For example, if you have an image size stored as a pair of integers you can validate that with "[int, int]".
  4. <type 1> | ... | <type n> Union: matches at least one of the "<type>"s.
  5. (<type>) Group: used to group types together. For instance the structure (string | int)[] will match an array where each item is either a string or an integer.

Objects

If your data can't be represented by one of the basic types you can use an object structure. It should exactly match the expected structure with a few exceptions:

  1. If a value is optional you can append a '?' to the key. For example: { 'optional?': 'string' }.
  2. If a value should be an array you can use the array syntax above ([], [min...max], [min...], [...max], [.], and [N]). For example: { 'arr[]': 'string' }.
  3. If a value should be a tuple, you can use an array of values. For example: { size: ["int", "int"] }.

Custom types

If you have a complex type that you would like to reuse in multiple places you can specify it as a custom type. It also allows for recursion.

const types = {
  Person: {
    name: "Name",
    "parents[]?": "Person",
    "spouse?": "Person",
    "children[]?": "Person",
  },

  Name: { first: "string", last: "string" },
};

validateStructure(person, "Person", true, types);

Functions

If you have a use case that is not satisfied by the aforementioned methods, you can write a custom validator function. These come in two flavors and can be used anywhere a Structure can.

  1. Matcher (type MatcherFn = (val: any) => boolean). This is the simpler of the two. It takes in a value and should return true if the value matches and false if it doesn't.

  2. Validator (type ValidatorFn = (val: any, path: string, types: TypeValidators, strict: boolean) => ValidationError[]). This is more complicated, but gives much more control. The arguments are as follows:

    1. val The value to validate.
    2. path The keypath of the value in dot notation, used for error messages.
    3. types A key:value map of other ValidatorFns. For example, types.array is the ValidatorFn that matches any array. This includes matchers for any custom types you have defined.
    4. strict Whether or not the validation is running in strict mode. See validateStructure() for details.

    The return type is an array of ValidationError objects. Each ValidationError is an object consisting of three keys: msg, path, and type. msg is a string explaining what the error is, path is the path to the invalid item, and type is the type of the error. The default types are "key", "val-start" and "val-end", depending on where the mismatch was, but any string may be used. Here is an example error: { msg: 'array must not be empty', path: ['arr'], type: "val-start" }.

    Here is an example to check if a value is a string that starts with a $:

    import type { ValidatorFn } from "validate-structure";
    import { validateStructure, buildError } from "validate-structure";
    
    const dollarString: ValidatorFn = (val, path, types, strict) => {
      // Check if the value is a string
      const errors = types.string(val, path, types, strict);
      if (errors.length > 0) return errors;
    
      // The value is fine, return no errors.
      if (val.startsWith("$")) return [];
    
      // The value is invalid, return an error
      return buildError(`'${val}' does not start with a '$'`, path);
    };
    
    validateStructure("$12", dollarString); // -> []
    validateStructure("12", dollarString); // -> [{msg: "'12' does not start with a '$'", path: ""}]
    validateStructure({ price: "12" }, { price: dollarString }); // -> [{msg: "'12' does not start with a '$'", path: "price"}]

Examples

import { matchesStructure } from "validate-structure";

// A single string
matchesStructure("hello world", "string"); // -> true

// A single integer
matchesStructure(14, "int"); // -> true
matchesStructure(14.2, "int"); // -> false

// An array of numbers
matchesStructure([], "number[]"); // -> true
matchesStructure([14], "number[]"); // -> true
matchesStructure([1, 2, 3, 4, 5], "number[]"); // -> true

// A tuple of 2 numbers
const sizeTuple = "[number, number]"; // This could also be written "number[2]"
matchesStructure([1, 2], sizeTuple); // -> true
matchesStructure([1], sizeTuple); // -> false
matchesStructure([1, 2, 3], sizeTuple); // -> false

// A tuple of a string and an int
const ruleTuple = "[string, int]";
matchesStructure(["tabLength", 2], sizeTuple); // -> true
matchesStructure([14, 2], sizeTuple); // -> false

// A custom object structure
const structure = { id: "int", name: "string" };
matchesStructure({ id: 14, name: "John" }, structure); // -> true

// Strict mode is on by default
matchesStructure({ id: 14, name: "John" }, { id: "int" }); // -> false
matchesStructure({ id: 14, name: "John" }, { id: "int" }, false); // -> true

// Complex structures
const structure = {
  name: "string",
  tabs: {
    name: "string",
    "color?": "string",
    "contents[.]": {
      "title?": "string",
      body: "string",
    },
  },
};

matchesStructure(
  {
    name: "Home",
    tabs: {
      name: "About",
      contents: [
        { title: "About Us", body: "Lorem ipsum dolor sit amet" },
        { body: "consectetur adipiscing elit" },
      ],
    },
  },
  structure
); // -> true

matchesStructure(
  {
    name: "Home",
    tabs: {
      name: "About",
      contents: [],
    },
  },
  structure
); // -> false (tabs.contents must be non-empty)

Contributing

If you want to help out, please read the CONTRIBUTING.md.