Skip to content

suchipi/serializable-types

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

serializable-types

serializable-types is a combination runtime type checker and value (de)serializer for node and the browser.

It's kind of like prop-types, flow-runtime, or ow, but it can use its type awareness to serialize and deserialize values into JSON-safe objects, kinda like ejson.

Code Example

import * as t from "serializable-types";

// --------------
// Checking types
// --------------

t.isOfType(42, t.number); // true
t.isOfType(42, t.boolean); // false
t.isOfType(true, t.boolean); // true
t.isOfType(true, t.number); // false

t.isOfType([1, 2, 3], t.array(t.number)); // true

t.isOfType(
  {
    foo: 42,
    bar: ["hi", 65],
    baz: [new Date(), Buffer.from("bla bla"), new Date()],
  },
  t.object({
    foo: t.number,
    bar: t.tuple(t.string, t.number),
    baz: t.array(t.union(t.Date, t.Buffer)),
  })
); // true

// ---------------
// Asserting types
// ---------------

t.assertType(true, t.boolean); // No error
t.assertType(42, t.boolean); // throws TypeError: Expected boolean, but received 42​​

t.assertType([true, false, null], t.array(t.boolean));
// throws TypeError: ​​Expected Array<boolean>, but received: [ true, false, null ]

t.assertType(
  {
    bad: true,
  },
  t.object({
    foo: t.number,
    bar: t.tuple(t.string, t.number),
    baz: t.array(t.union(t.Date, t.Buffer)),
  })
);
// TypeError: Expected { foo: number, bar: [string, number], baz: Array<Date | Buffer> }, but received: { "bad" : true }

// ----------------------------------
// Serializing values of a known type
// ----------------------------------

t.serializeWithType(Buffer.from("hello"), t.Buffer);
// { $type: "Buffer", $value: [104, 101, 108, 108, 111] }

t.serializeWithType(
  {
    data: Buffer.from([104, 101, 108, 108, 111]),
    encoding: "utf-8",
  },
  t.object({
    data: t.union(t.Buffer, t.string),
    encoding: t.string,
  })
);
// {
//   $type: "object",
//   $value: {
//     data: {
//       $type: "Buffer",
//       $value: [104, 101, 108, 108, 111],
//     },
//     encoding: { $type: "string", $value: "utf-8" },
//   },
// }

// ------------------------------------
// Deserializing values of a known type
// ------------------------------------

t.deserializeWithType(
  {
    $type: "object",
    $value: {
      data: {
        $type: "Buffer",
        $value: [104, 101, 108, 108, 111],
      },
      encoding: { $type: "string", $value: "utf-8" },
    },
  },
  t.object({
    data: t.union(t.Buffer, t.string),
    encoding: t.string,
  })
);
// { data: <Buffer 68 65 6c 6c 6f>, encoding: "utf-8" }

Try it out on RunKit!

API Overview

The types namespace exported by this module has three kinds of objects on it: TypeDefs, functions which create TypeDefs (hereafter known as "TypeDef constructors"), and functions you use to work with TypeDefs (hereafter known as "utility functions").

A TypeDef is an object that represents a given type within JavaScript, that has methods on it that can be used to work with values of that type.

Every TypeDef has this shape:

interface TypeDef {
  // Human-readable description of the type, eg `Buffer` or `Array<number>`
  description: string;

  // Human-readbale description of the type this serializes to, eg `{ $type: "Buffer", $value: Array<string> }`
  serializedDescription: string;

  // Check if a given value is of this type. Returns true if it's this type, false otherwise.
  check(val: any): boolean;

  // Serialize the given value so that it can be encoded as JSON.
  // If the given value is not of this type, an error will be thrown.
  serialize(val: any): any;

  // Check if a given object can be deserialized to this type. True if it can, false otherwise.
  checkSerialized(serialized: any): boolean;

  // Deserialize the given object into this type.
  // If the given object cannot be deserialized into this type, an error will be thrown.
  deserialize(serialized: any): any;
}

Here is a list of all the TypeDefs:

  • t.any
  • t.anyObject
  • t.boolean
  • t.Buffer
  • t.Date
  • t.Element
  • t.Error
  • t.false
  • t.Function
  • t.Infinity
  • t.integer
  • t.NaN
  • t.NegativeInfinity
  • t.never
  • t.null
  • t.number
  • t.RegExp
  • t.string
  • t.Symbol
  • t.true
  • t.Int8Array
  • t.Uint8Array
  • t.Uint8ClampedArray
  • t.Int16Array
  • t.Uint16Array
  • t.Int32Array
  • t.Uint32Array
  • t.Float32Array
  • t.Float64Array
  • t.undefined
  • t.URL

Here is a list of all the TypeDef constructors:

  • t.array(memberTypeDef)
  • t.arrayContaining(memberTypeDef)
  • t.exactNumber(num)
  • t.exactString(str)
  • t.func(params, returnValue)
  • t.instanceOf(klass)
  • t.intersection(...memberTypeDefs)
  • t.map(keyTypeDef, valueTypeDef)
  • t.maybe(typeDef)
  • t.object(typeDefObjectMap)
  • t.objectMap(valueTypeDef, keyTypeDef)
  • t.predicate(matcherFunction)
  • t.set(memberTypeDef)
  • t.shape(typeDefObjectMap)
  • t.stringMatching(regex)
  • t.symbolFor(tag)
  • t.tuple(...memberTypeDefs)
  • t.union(...memberTypeDefs)

And here is a list of all the utility functions:

  • t.assertType(value, typeDef)
  • t.isOfType(value, typeDef)
  • t.serializeWithType(value, typeDef)
  • t.deserializeWithType(serialized, typeDef)
  • t.installGlobals()
  • t.coerceToType(value)

Each export is documented in further detail below.

API: Utility Functions

Note that these functions will work with any object that implements the TypeDef interface described above; so they can be used not only with TypeDefs from this package, but also custom TypeDefs you write.

Additionally, these functions support "Automatic TypeDef Coercion", which means you can pass in placeholder values instead of TypeDef objects in many places. See the Notes section at the bottom of the README for more info.

t.assertType(value, typeDef)

A function that, given a value and a TypeDef, throws an error if the value is not of the type described by the TypeDef.

t.isOfType(value, typeDef)

A function that, given a value and a TypeDef, returns true if the value is of the type described by the TypeDef, and false otherwise.

t.serializeWithType(value, typeDef)

A function that, given a value and a TypeDef, serializes the value using the TypeDef, and returns the serialized value. If the value you are trying to serialize is not of the type described by the TypeDef, an error will be thrown.

t.deserializeWithType(serialized, typeDef)

A function that, given a serialized value and a TypeDef, deserializes the value using the TypeDef, and returns the deserialized value. If the value you are trying to deserialize is not of the serialized type described by the TypeDef, an error will be thrown.

installGlobals()

Makes several exports from this library global:

  • All the Utility Functions
  • All the type constructors
  • These specific TypeDef objects:
    • boolean
    • integer
    • number
    • string
    • never

Not all TypeDefs are exported

API: TypeDefs

t.any

A TypeDef which represents anything. Cannot be used for serialization/deserialization, but can be used for runtime type-checking.

t.anyObject

A TypeDef which represents any object. More specifically, any non-falsy (not null) value whose type string obtained from using the typeof operator returns "object".

t.boolean

A TypeDef which represents boolean values, either true or false.

t.Buffer

A TypeDef which represents a Buffer.

t.Date

A TypeDef which represents a Date.

t.Element

A TypeDef which represents an Element.

t.Error

A TypeDef which represents an Error.

t.false

A TypeDef which represents the value false.

t.Function

A TypeDef which represents a Function. Note that functions cannot be (de)serialized.

t.Infinity

A TypeDef which represents the value Infinity.

t.integer

A TypeDef which represents integers.

t.NaN

A TypeDef which represents the value NaN.

t.NegativeInfinity

A TypeDef which represents the value -Infinity.

t.never

A TypeDef that no value satisfies.

t.null

A TypeDef which represents the value null.

t.number

A TypeDef which represents all numbers except NaN, Infinity, and -Infinity.

t.RegExp

A TypeDef which represents a RegExp.

t.string

A TypeDef which represents any string value.

t.Symbol

A TypeDef which represents a Symbol. Note: only shared Symbols from the global Symbol registry can be (de)serialized.

t.true

A TypeDef which represents the value true.

t.Int8Array, t.Uint8Array, t.Uint8ClampedArray, t.Int16Array, t.Uint16Array, t.Int32Array, t.Uint32Array, t.Float32Array, t.Float64Array

TypeDefs which represent typed array views.

t.undefined

A TypeDef which represents the value undefined.

t.URL

A TypeDef which represents a WHATWG URL object.

API: TypeDef constructors

t.array(memberTypeDef)

A function which returns a TypeDef which represents a homogenous Array of the given type; for example, t.array(t.boolean) represents an Array of unbounded length containing only booleans. This is like Array<boolean> in Flow/TypeScript.

t.arrayContaining(memberTypeDef)

A function which returns a TypeDef which represents an Array containing at least one of the given type; for example, t.arrayContaining(t.string) represents an Array of undetermined length containing at least one string. This is like Array<string | any> in Flow/TypeScript.

t.exactNumber(num)

A function which returns a TypeDef which represents an exact number. For example, t.exactNumber(42). This is most useful when combined with t.union to simulate enums; for example, t.union(t.exactNumber(0), t.exactNumber(1)), which is like 0 | 1 in Flow/TypeScript.

t.exactString(str)

A function which returns a TypeDef which represents an exact string. For example, t.exactString("foo"). This is most useful when combined with t.union to simulate enums; for example, t.union(t.exactString("GET"), t.exactString("POST")), which is like "GET" | "POST" in Flow/TypeScript.

t.func(params, returnType)

A function which returns a TypeDef which represents a function with the given parameter type(s) and return type.

Note that when using thie TypeDef, the parameter types and return type are NOT actually checked, because there is no way to do so in JavaScript at runtime without annotating or wrapping every function. As such, at runtime, this TypeDef only checks that the value is a Function. However, it may still useful to use instead of Function if you do not have a static type system in your codebase and want to document things for future readers.

t.instanceOf(klass)

A function which returns a TypeDef which represents an instance of the provided klass.

Note that serialization will not be supported with this TypeDef unless the class implements the three following static methods:

  • static serialize(instance), which should serialize the instance into a JSON-compatible format (like an Object)
  • static checkSerialized(anyValue), which should return a boolean indicating whether some given value is of the type that serialize returns
  • static deserialize(serializedInstance), which should return an instance of the class by using the serialized representation.

t.intersection(...memberTypeDefs)

A function which returns a TypeDef which represents the intersection of the given TypeDefs. It's kind of like a logical "AND". For example:

t.intersection(
  t.object({
    foo: t.number,
  }),
  t.object({
    bar: t.number,
  })
);

This is like { foo: number } & { bar: number } in Flow/TypeScript.

t.map(keyTypeDef, valueTypeDef)

A function which returns a TypeDef which represents a Map, containing the given key and value types. For example, t.map(t.string, t.Buffer). This is like Map<string, Buffer> in Flow/TypeScript.

t.maybe(typeDef)

A function which returns a TypeDef which represents the union between the given TypeDef and undefined. For example: t.maybe(t.string). This is similar to ?string in Flow and string? in TypeScript.

This is most useful in object t.for representing optional properties, eg:

t.object({
  size: t.number,
  data: t.maybe(t.Buffer),
});

t.object(typeDefObjectMap)

A function which returns a TypeDef which represents an Object whose properties are typed by the passed typeDefObjectMap. For example:

t.object({
  size: t.number,
  data: t.union(t.string, t.Buffer),
  encoding: t.maybe(t.string),
});

// This is similar to the following in Flow/TypeScript:
// {
//   size: number,
//   data: string | Buffer,
//   encoding?: string
// }

t.objectMap(valueTypeDef, keyTypeDef?)

A function which returns a TypeDef which represents an Object whose keys are arbitrary and whose values are the same. For example:

t.objectMap(t.number, t.string);
// This is similar to `{ [string]: number }` in Flow/TypeScript.

Note that the value TypeDef is the first argument and the key TypeDef is the second argument, which may be somewhat unintuitive. This is because the key TypeDef is optional and defaults to t.union(t.string, t.Symbol).

t.objectMap(t.number);
// This is similar to `{ [string | Symbol]: number }` in Flow/TypeScript.

t.predicate(matcherFunction)

A function that returns a TypeDef which represents any value for which the provided matcherFunction returns true. You can use this to create TypeDefs that conform to any user-defined bounds. However, be aware that a TypeDef returned from t.predicate cannot be used for (de)serialization; it can only be used for runtime type checking.

t.set(memberTypeDef)

A function which returns a TypeDef which represents a Set, containing the given member type. For example, t.set(t.string). This is like Set<string> in Flow/TypeScript.

t.shape(typeDefObjectMap)

A function which is the same as t.object but all of the object properties in the returned TypeDef are wrapped with t.maybe. This is similar to $Shape in Flow, and is useful for config options, React Props, etc.

t.stringMatching(regex)

A function which returns a TypeDef which represents any string that matches the provided regular expression.

t.symbolFor(tag)

A function which returns a TypeDef which represents the value of calling Symbol.for with the provided tag string.

t.tuple(...memberTypeDefs)

A function which returns a TypeDef which represents an Array of fixed length with typed values at each index. For example:

t.tuple(t.string, t.number);
// This is like `[string, number]` in Flow/TypeScript.

t.union(...memberTypeDefs)

A function which returns a TypeDef which represents the union of the given TypeDefs. It kind of works like a logical "OR". For example:

t.union(t.string, t.number);
// This is like `string | number` in Flow/TypeScript.

Notes

Automatic TypeDef Coercion

Every function in this library that accepts a TypeDef as one of its parameters support something called "Automatic TypeDef Coercion". That means that in place of a TypeDef, you can pass in a value from which a desired TypeDef can be inferred. This means that in many cases, you do not need to import a ton of TypeDefs from this library, but can instead use common globals or literals in their place.

For instance, instead of writing this:

var someVariable = true;

assertType(someVariable, t.boolean);

You could write this:

var someVariable = true;

// `Boolean` is automatically "coerced" to `t.boolean`
assertType(someVariable, Boolean);

Here is a list of values which will be automatically coerced to TypeDefs. The left side of the arrow indicates the value you pass in to the function, and the right side of the arrow indicates the TypeDef it will be coerced into.

  • true -> t.true
  • false -> t.false
  • null -> t.null
  • undefined -> t.undefined
  • NaN -> t.NaN
  • Infinity -> t.Infinity
  • -Infinity -> t.NegativeInfinity
  • Object -> t.anyObject
  • t.object (the function itself) -> t.anyObject
  • URL -> t.URL
  • Symbol -> t.Symbol
  • RegExp -> t.RegExp
  • Function -> t.Function
  • Error -> t.Error
  • Element -> t.Element
  • Buffer -> t.Buffer
  • Date -> t.Date
  • String -> t.string
  • Number -> t.number
  • Boolean -> t.boolean
  • any string -> The result of calling t.exactString with that string
  • any number -> The result of calling t.exactNumber with that number
  • any array -> The result of calling t.tuple with that array's elements as arguments
  • any class -> The result of calling t.instanceOf with that class
  • any object -> The result of calling t.object with that object (also recurses through property values and coerces them)

Some things to note about Automatic TypeDef Coercion:

  • Array literals are always coerced into tuples; as such, [String] refers to a tuple with one element in it, a string. This may not be what you want; if you instead want an Array of any size with consistent items inside, use t.array(String).

To manually coerce a value into a type, you can use t.coerceToType:

t.coerceToType(Boolean); // returns t.boolean
t.coerceToType("hello"); // returns t.exactString("hello")

License

MIT

About

Runtime type assertion and serialization system

Resources

License

Stars

Watchers

Forks

Packages

No packages published