typed-j

A simple type-safe JSON parser


Keywords
json, parser, type, safe, type-safe, typescript, typescript-library, type-checking, type-checker, runtime-typechecking
License
MIT
Install
npm install typed-j@0.0.1

Documentation

Typist JSON

minzipped size types license ci

A simple runtime JSON type checker.

Features

  • Simple. No JSON Schema, No validation rules
  • Type-safe. Written in TypeScript
  • Intuitive. Familiar syntax like TypeScript interface

Typist JSON is focused on type checking, so there is no validation rules like range of numbers or length of strings.

Install

npm install typist-json

NOTE: Require TypeScript 4.1 or higher because Typist JSON uses Key Remapping and Template Literal Types.

Example

import { j } from "typist-json";

const NameJson = j.object({
  firstname: j.string,
  lastname: j.string,
});

const UserJson = j.object({
  name: NameJson,
  age: j.number,
  "nickname?": j.string, // optional property
});

const userJson = await fetch("/api/user")
    .then(res => res.json());

if (UserJson.check(userJson)) {
  // now, the userJson is narrowed to:
  // {
  //   name: {
  //     firstname: string
  //     lastname: string
  //   }
  //   age: number
  //   nickname?: string | undefined
  // }
}

Circular References

Sometimes JSON structures can form circular references.

Typist JSON can represent circular references by wrapping checkers in the arrow function.

const FileJson = j.object({
  filename: j.string,
});

const DirJson = j.object({
  dirname: j.string,
  entries: () => j.array(j.any([FileJson, DirJson])), // references itself
});

DirJson.check({
  dirname: "animals",
  entries: [
    {
      dirname: "cat",
      entries: [
        { filename: "american-shorthair.jpg" },
        { filename: "munchkin.jpg" },
        { filename: "persian.jpg" },
      ],
    },
    {
      dirname: "dog",
      entries: [
        { filename: "chihuahua.jpg" },
        { filename: "pug.jpg" },
        { filename: "shepherd.jpg" },
      ],
    },
    { filename: "turtle.jpg" },
    { filename: "shark.jpg" },
  ],
}); // true

Type checkers

Strings

j.string.check("foo"); // true
j.string.check("bar"); // true

Numbers

j.number.check(42); // true
j.number.check(12.3); // true
j.number.check("100"); // false

Booleans

j.boolean.check(true); // true
j.boolean.check(false); // true

Literals

j.literal("foo").check("foo"); // true
j.literal("foo").check("fooooo"); // false

Arrays

j.array(j.string).check(["foo", "bar"]); // true
j.array(j.string).check(["foo", 42]); // false
j.array(j.string).check([]); // true
j.array(j.number).check([]); // true

Objects

j.object({
  name: j.string,
  age: j.number,
  "nickname?": j.string,
}).check({
  name: "John",
  age: 20,
  nickname: "Johnny",
}); // true

j.object({
  name: j.string,
  age: j.number,
  "nickname?": j.string,
}).check({
  name: "Emma",
  age: 20,
}); // true, since "nickname" is optional

j.object({
  name: j.string,
  age: j.number,
  "nickname?": j.string,
}).check({
  id: "xxxx",
  type: "android",
}); // false, since "name" and "age" is required

If a property that ends with ? is not optional, you should replace all trailing ? by ??.

More details about escaping

As mentioned above, you need to escape all trailing ? as ??.

j.object({
    "foo??": j.boolean,
}).check({
    "foo?": true,
}); // true

So if you want optional property with a name "foo???", you should use "foo???????" as key.

j.object({
  "foo???????": j.boolean,
}).check({}); // true, since "foo???" is optional

Nulls

j.nil.check(null); // true
j.nil.check(undefined); // false

Nullables

j.nullable(j.string).check("foo"); // true
j.nullable(j.string).check(null); // true
j.nullable(j.string).check(undefined); // false

Unknowns

j.unknown.check("foo"); // true
j.unknown.check(123); // true
j.unknown.check(null); // true
j.unknown.check(undefined); // true
j.unknown.check([{}, 123, false, "foo"]); // true

Unions

j.any([j.string, j.boolean]).check(false); // true

j.any([
  j.literal("foo"),
  j.literal("bar"),
]).check("foo"); // true

Get JSON type of checkers

import { j, JsonTypeOf } from "typist-json";

const UserJson = j.object({
  name: j.string,
  age: j.number,
  "nickname?": j.string,
});

type UserJsonType = JsonTypeOf<typeof UserJson>;
// 
// ^ This is same as:
// 
// type UserJsonType = {
//   name: string;
//   age: number;
//   nickname?: string;
// }