0.5.0 • Published 3 years ago

poi-ts v0.5.0

Weekly downloads
10
License
MIT
Repository
github
Last release
3 years ago

Poi

Poi is a simple JSON type validator for TypeScript (>=4.0.0).

import * as Poi from 'poi-ts';

const json = '{ "age": 17, "name": "Alice" }';

const value = JSON.parse(json);
// The type of 'value' is 'any' here...

try {
  Poi.validate(value, Poi.object({ age: Poi.number(), name: Poi.string() }));
  // The type of 'value' is '{ age: number; name: string; }' here!!!

} catch (error: unknown) {
  // Validation error

}

Introduction

We often validate types of JSON values fetched from network or filesystem. For example, we can do it with joi:

import Joi from 'joi';

const json = '{ "age": 17, "name": "Alice" }';

const { error, value } = Joi.object({
  age: Joi.number().required(),
  name: Joi.string().required(),
}).validate(JSON.parse(json));

if (!error) {
  // The type of 'value' is 'any' here...

} else {
  // Validation error

}

This is good for JavaScript, but not for TypeScript. Although value obviously has type { age: number; name: string; } if !error, its type in TypeScript is still any.

// The type of 'value' is 'any' here...

value.age = '37';         // type mismatch, but no compile error
console.log(value.namae); // typo, but no compile error

Poi derives the type of value automatically.

import * as Poi from 'poi-ts';

const json = '{ "age": 17, "name": "Alice" }';

const value = JSON.parse(json);

try {
  Poi.validate(value, Poi.object({ age: Poi.number(), name: Poi.string() }));
  // The type of 'value' is '{ age: number; name: string; }' here!!!

  value.age = '37';         // error TS2322: Type '"37"' is not assignable to type 'number'.
  console.log(value.namae); // error TS2551: Property 'namae' does not exist on type
                            // '{ age: number; name: string; }'. Did you mean 'name'?
} catch (error: unknown) {
  // Validation error

}

Getting Started

Poi can be installed from npm. typescript >=4.0.0 is required to use Poi in TypeScript.

npm install poi-ts

# or

yarn add poi-ts
import * as Poi from 'poi-ts';

Usage

Types

ValidationError

ValidationError is the type of errors thrown by validate(value, validator, expression?).

class ValidationError extends Error {
  readonly expression: string,
  readonly expectedType: string,
  readonly subErrors: readonly ValidationError[];
}

The message property may be useful for debug.

const value: unknown = [23, 'str'];
try {
  Poi.validate(value, Poi.array(Poi.number()));

} catch (error: unknown) {
  if (error instanceof Error) {
    console.error(error.message); // 'value' is not of type 'number[]'
                                  //   'value[1]' is not of type 'number'
  }
}

ValidatorType\<Validator>

ValidatorType<Validator> extracts the type that Validator expects.

const personValidator = Poi.object({ age: Poi.number(), name: Poi.string() });

type Person = Poi.ValidatorType<typeof personValidator>;
// Equivalent to:
// type Person = { age: number; name: string; };

Functions

validate(value, validator, expression?)

validate(value, validator, expression?) is an assertion function that asserts value has the type that validator expects.

It throws an error of type ValidationError if validation fails. The optional parameter expression (default: 'value') is used for error messages.

const value: unknown = [23, 42];

Poi.validate(value, Poi.array(Poi.number()));
// The type of 'value' is 'number[]'.

const tuple: unknown = [23, 'str'];
try {
  Poi.validate(tuple, Poi.array(Poi.number()), 'tuple');

} catch (error: unknown) {
  if (error instanceof Error) {
    console.error(error.message); // 'tuple' is not of type 'number[]'
                                  //   'tuple[1]' is not of type 'number'
  }
}

tryValidate(value, validator)

tryValidate(value, validator) is a user-defined type guard that returns whether value has the type that validator expects. It never throws.

const value: unknown = [23, 'str'];

if (Poi.tryValidate(value, Poi.array(Poi.number()))) {
  // Never reached
} else if (Poi.tryValidate(value, Poi.tuple(Poi.number(), Poi.string())) {
  // The type of 'value' is '[number, string]'.

}

parseJSON(json, validator, expression?)

parseJSON(json, validator, expression?) is a shorthand for value = JSON.parse(json) and validate(value, validator, expression?).

const value = Poi.parseJSON(
  '{ "foo": 42, "bar": "str" }',
  Poi.object({ foo: Poi.number(), bar: Poi.string() }),
);
// The type of 'value' is '{ foo: number; bar: string; }'.
// The value of 'value' is '{ foo: 42, bar: "str" }'.

tryParseJSON(json, validator)

tryParseJSON(json, validator) is a shorthand for value = JSON.parse(json) and validate(value, validator). Unlike parseJSON, it returns undefined if parsing or validation fails. It never throws.

const json = '{ "foo": 42, "bar": "str" }';

const value1 = Poi.tryParseJSON(json, Poi.object({ foo: Poi.number(), bar: Poi.number() }));
// The type of 'value1' is '{ foo: number; bar: number; } | undefined'.
// The value of 'value1' is 'undefined'.

const value2 = Poi.tryParseJSON(json, Poi.object({ foo: Poi.number(), bar: Poi.string() }));
// The type of 'value2' is '{ foo: number; bar: string; } | undefined'.
// The value of 'value2' is '{ foo: 42, bar: "str" }'.

Validators

Poi has some basic validators.

null_()

null_() matches only null.

const value: unknown = null;

Poi.validate(value, Poi.null_());
// The type of 'value' is 'null'.

boolean()

boolean() matches a boolean value.

const value: unknown = true;

Poi.validate(value, Poi.boolean());
// The type of 'value' is 'boolean'.

number()

number() matches a number value.

const value: unknown = 42;

Poi.validate(value, Poi.number());
// The type of 'value' is 'number'.

string()

string() matches a string value.

const value: unknown = 'str';

Poi.validate(value, Poi.string());
// The type of 'value' is 'string'.

array(element)

array(element) matches an array of element.

const value: unknown = [23, 42];

Poi.validate(value, Poi.array(Poi.number()));
// The type of 'value' is 'number[]'.

tuple(...elements)

tuple(...elements) matches a tuple of elements.

const value: unknown = [42, 'str'];

Poi.validate(value, Poi.tuple(Poi.number(), Poi.string()));
// The type of 'value' is '[number, string]'.

NOTE: Optional elements and rest elements (e.g. [boolean, number?, ...string[]]) are not yet supported.

object(shape)

object(shape) matches an object of shape.

const value: unknown = { foo: 42, bar: 'str' };

Poi.validate(value, Poi.object({ foo: Poi.number(), bar: Poi.string() }));
// The type of 'value' is '{ foo: number; bar: string; }'.

Unlike joi, properties are required by default. To make optional properties, use optional(property).

const value: unknown = { foo: 42 };

Poi.validate(value, Poi.object({ foo: Poi.number(), bar: Poi.optional(Poi.string()) }));
// The type of 'value' is '{ foo: number; bar?: string; }'.

Unknown properties are always ignored.

const value: unknown = { foo: 42, bar: 'str', baz: true };

Poi.validate(value, Poi.object({ foo: Poi.number(), bar: Poi.string() }));
// The type of 'value' is '{ foo: number; bar: string; }'.

record(value)

record(value) matches a record which value type is value.

const value: unknown = { foo: 23, bar: 42 };

Poi.validate(value, Poi.record(Poi.number()));
// The type of 'value' is 'Record<string, number>'.

literal(lit)

literal(lit) matches a value equal to lit. lit shall be a literal.

const value: unknown = 42;

Poi.validate(value, Poi.literal(42));
// The type of 'value' is '42'.

union(...alternatives)

union(...alternatives) matches one of alternatives.

const value: unknown = 42;

Poi.validate(value, Poi.union(Poi.boolean(), Poi.number(), Poi.string()));
// The type of 'value' is 'boolean | number | string'.

unknown()

unknown() matches any.

const value: unknown = { foo: 42, bar: 'str' };

Poi.validate(value, Poi.object({ foo: Poi.unknown(), bar: Poi.unknown() }));
// The type of 'value' is '{ foo: unknown; bar: unknown; }'.

Author

iorate (Twitter)

License

MIT License