![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
arri-validate
Advanced tools
A type builder and validation library built on top of the [Json Type Definition (RFC 8927)](https://jsontypedef.com) This library is pretty similar to [Typebox](https://github.com/sinclairzx81/typebox) except that it creates Json Type Definition (JTD) obj
A type builder and validation library built on top of the Json Type Definition (RFC 8927) This library is pretty similar to Typebox except that it creates Json Type Definition (JTD) objects instead of Json Schema objects.
A lot of inspiration was taken from both Typebox and Zod when designing this library
# npm
npm install arri-validate
# pnpm
pnpm install arri-validate
import { a } from "arri-validate";
const User = a.object({
id: a.string(),
name: a.string(),
});
type User = a.infer<typeof User>;
// passes and returns User
a.parse(User, `{"id": "1", "name": "John Doe"}`);
// throws error
a.parse(User, `{"id": "1", "name": null}`);
// returns true
a.validate(User, { id: "1", name: "John Doe" });
// returns false
a.validate(User, { id: "1", name: null });
// outputs valid json
a.serialize(User, { id: "1", name: "John Doe" });
The goals of this project are as follows:
I am not looking to support every feature of Typescript's type system or even every possible representation of JSON. The goal is that the data models defined through this library can be used as a source of truth across multiple programming languages. Both JSON and Typescript have to be limited to accomplish this.
To represent the data-models in a language agnostic way this library heavily relies on JSON Type Definition (JTD). However, this library does not strictly comply with the JTD specification. The reason for this is because JTD does not support 64-bit integers. I believe sharing large integers across languages is a huge pain point especially when going to and from Javascript. For this reason alone, I have opted to break away from the JTD spec and add support for int64
and uint64
. So while I have no intention to break further away from the spec I am open to it if a large enough issue arises (in my view). If you use this library be aware that I'm using a superset of JTD rather than a strict spec compliant implementation.
Arri Schema | Typescript | Json Type Definition |
---|---|---|
a.any() | any | {} |
a.string() | string | { "type": "string" } |
a.boolean() | boolean | {"type": "boolean"} |
a.timestamp() | Date | {"type": "timestamp"} |
a.float32() | number | {"type": "float32"} |
a.float64() | number | {"type": "float64"} |
a.int8() | number | {"type": "int8"} |
a.int16() | number | {"type": "int16"} |
a.int32() | number | {"type": "int32"} |
a.int64() | BigInt | {"type": "int64"} |
a.uint8() | number | {"type": "uint8"} |
a.uint16() | number | {"type": "uint16"} |
a.uint32() | number | {"type": "uint32"} |
a.uint64() | BigInt | {"type": "uint64"} |
Enum schemas allow you to specify a predefine list of accepted strings
Usage
const Status = a.enumerator(["ACTIVE", "INACTIVE", "UNKNOWN"]);
type Status = a.infer<typeof Status>; // "ACTIVE" | "INACTIVE" | "UNKNOWN";
a.validate(Status, "BLAH"); // false
a.validate(Status, "ACTIVE"); // true
Outputted JTD
{
"enum": ["ACTIVE", "INACTIVE", "UNKNOWN"]
}
Usage
const MyList = a.array(a.string());
type MyList = a.infer<typeof MyList>; // string[];
a.validate(MyList, [1, 2]); // false
a.validate(MyList, ["hello", "world"]); // true
Outputted JTD
{
"elements": {
"type": "string"
}
}
Usage
const User = a.object({
id: a.string(),
email: a.string(),
created: a.timestamp(),
});
type User = a.infer<typeof User>; // { id: string; email: string; created: Date; }
a.validate({
id: "1",
email: "johndoe@example.com",
created: new Date(),
}); // true
a.validate({
id: "1",
email: null,
created: new Date(),
}); // false
Outputted JTD
{
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"created": {
"type": "timestamp"
}
}
}
Usage
const R = a.record(a.boolean());
type R = a.infer<typeof R>; // Record<string, boolean>
a.validate(R, {
hello: true,
world: false,
}); // true;
a.validate(R, {
hello: "world",
}); // false;
Outputted JTD
{
"values": {
"type": "boolean"
}
}
Usage
const Shape = a.discriminator("type", {
RECTANGLE: a.object({
width: a.float32(),
height: a.float32(),
}),
CIRCLE: a.object({
radius: a.float32(),
}),
});
type Shape = a.infer<typeof Shape>; // { type: "RECTANGLE"; width: number; height: number; } | { type: "CIRCLE"; radius: number; }
// Infer specific sub types of the union
type ShapeTypeRectangle = a.inferSubType<Shape, "type", "RECTANGLE">; // { type "RECTANGLE"; width: number; height: number; };
type ShapeTypeCircle = a.inferSubType<Shape, "type", "CIRCLE">; // { type "CIRCLE"; radius: number; }
a.validate(Shape, {
type: "RECTANGLE",
width: 1,
height: 1.5,
}); // true
a.validate(Shape, {
type: "CIRCLE",
radius: 5,
}); // true
a.validate(Shape, {
type: "CIRCLE",
width: 1,
height: 1.5,
}); // false
Outputted JTD
{
"discriminator": "type",
"mapping": {
"RECTANGLE": {
"properties": {
"width": {
"type": "float32"
},
"height": {
"type": "float32"
}
}
},
"CIRCLE": {
"properties": {
"radius": {
"type": "float32"
}
}
}
}
}
Use a.optional()
to make an object field optional.
const User = a.object({
id: a.string(),
email: a.optional(a.string()),
date: a.timestamp();
})
/**
* Resulting type
* {
* id: string;
* email: string | undefined;
* date: Date;
* }
*/
Outputted JTD
{
"properties": {
"id": {
"type": "string"
},
"date": {
"type": "timestamp"
}
},
"optionalProperties": {
"email": {
"type": "string"
}
}
}
Use a.nullable()
to make a particular type nullable
const name = a.nullable(a.string());
/**
* Resulting type
* string | null
*/
Outputted JTD
{
"type": "string",
"nullable": true
}
Copy another schema without copying it's metadata using the a.clone()
helper
const A = a.object(
{
a: a.string(),
b: a.float32(),
},
{ id: "A" },
);
console.log(A.metadata.id); // "A"
const B = a.clone(A);
console.log(B.metadata.id); // undefined
Extend an object schema with the a.extend()
helper.
const A = a.object({
a: a.string(),
b: a.float32(),
});
// { a: string; b: number; }
const B = a.object({
c: a.timestamp(),
});
// { c: Date }
const C = a.extend(A, B);
// { a: string; b: number; c: Date }
Use a.omit()
to create a new object schema with certain properties removed
const A = a.object({
a: a.string(),
b: a.float32(),
});
// { a: string; b: number; }
const B = a.omit(A, ["a"]);
// { b: number; }
Use a.pick()
to create a new object schema with the a subset of properties from the parent object
const A = a.object({
a: a.string(),
b: a.float32(),
c: a.timestamp(),
});
// { a: string; b: number; c: Date; }
const B = a.pick(A, ["a", "c"]);
// { a: string; c: Date; }
Use a.partial()
to create a new object schema that makes all of the properties of the parent schema optional.
const A = a.object({
a: a.string(),
b: a.float32(),
c: a.timestamp(),
});
// { a: string; b: number; c: Date; }
const B = a.partial(A);
// { a: string | undefined; b: number | undefined; c: Date | undefined; }
Call a.validate()
to validate an input against an arri schema. This method also acts as a type guard, so any any
or unknown
types that pass validation will automatically gain autocomplete for the validated fields
const User = a.object({
id: a.string(),
name: a.string(),
});
a.validate(User, true); // false
a.validate(User, { id: "1", name: "john doe" }); // true
if (a.validate(User, someInput)) {
console.log(someInput.id); // intellisense works here
}
Call a.parse()
to parse a JSON string against an arri schema. It will also handle parsing normal objects as well.
const User = a.object({
id: a.string(),
name: a.string(),
});
// returns a User if successful or throws a ValidationError if fails
const result = a.parse(User, jsonString);
A safer alternative to a.parse()
that doesn't throw an error.
const User = a.object({
id: a.string(),
name: a.string(),
});
const result = a.safeParse(User, jsonString);
if (result.success) {
console.log(result.value); // result.value will be User
} else {
console.error(result.error);
}
a.coerce()
will attempt to convert inputs to the correct type. If it fails to convert the inputs it will throw a ValidationError
const A = a.object({
a: a.string(),
b: a.boolean(),
c: a.float32(),
});
a.coerce(A, {
a: "1",
b: "true",
c: "500.24",
});
// { a: "1", b: true, c: 500.24 };
a.safeCoerce()
is an alternative to a.coerce()
that doesn't throw.
const A = a.object({
a: a.string(),
b: a.boolean(),
c: a.float32(),
});
const result = a.safeCoerce(A, someInput);
if (result.success) {
console.log(result.value);
} else {
console.error(result.error);
}
a.serialize()
will take an input and serialize it to a valid JSON string.
const User = a.object({
id: a.string(),
name: a.string(),
});
a.serialize(User, { id: "1", name: "john doe" });
// {"id":"1","name":"john doe"}
Be aware that this function does not validate the input. So if you are passing in an any or unknown type into this function it is recommended that you validate it first.
Use a.errors()
to get all of the validation errors of a given input.
const User = a.object({
id: a.string(),
date: a.timestamp(),
});
a.errors(User, { id: 1, date: "hello world" });
/**
* [
* {
* instancePath: "/id",
* schemaPath: "/properties/id/type",
* message: "Expected string",
* },
* {
* instancePath: "/date",
* schemaPath: "/properties/id/type",
* message: "Expected instanceof Date",
* }
* ]
*
*/
arri-validate
comes with a high performance JIT compiler that transforms Arri Schemas into highly optimized validation, parsing, serialization functions.
const User = a.object({
id: a.string(),
email: a.nullable(a.string()),
created: a.timestamp(),
});
const $$User = a.compile(User);
$$User.validate(someInput);
$$User.parse(someJson);
$$User.serialize({ id: "1", email: null, created: new Date() });
In most cases, the compiled validators will be much faster than the standard utilities. However there is some overhead with compiling the schemas so ideally each validator would be compiled once.
Additionally the resulting methods make use of eval so they can only be used in an environment that you control such as a backend server. They WILL NOT work in a browser environment.
You can use a.compile
for code generation. The compiler result gives you access to the generated function bodies.
$$User.compiledCode.validate; // the generated validation code
$$User.compiledCode.parse; // the generated parsing code
$$User.compiledCode.serialize; // the generated serialization code
Last Updated: 2024-02-17
All benchmarks were run on my personal desktop. You can view the methodology used in ./benchmarks/src.
OS - Pop!_OS 22.04 LTS
CPU - AMD Ryzen 9 5900 12-Core Processor
RAM - 32GB
Graphics - AMD® Radeon rx 6900 xt
The following data was used in these benchmarks. Relevant schemas were created in each of the mentioned libraries.
{
id: 12345,
role: "moderator",
name: "John Doe",
email: null,
createdAt: 0,
updatedAt: 0,
settings: {
preferredTheme: "system",
allowNotifications: true,
},
recentNotifications: [
{
type: "POST_LIKE",
postId: "1",
userId: "2",
},
{
type: "POST_COMMENT",
postId: "1",
userId: "1",
commentText: "",
},
],
};
Library | op/s |
---|---|
Arri (Compiled) | 135,577,679 |
Typebox (Compiled) | 59,141,334 |
Ajv -JTD (Compiled) | 38,308,645 |
Ajv - JTD | 30,481,986 |
Ajv - JSON Schema (Compiled) | 12,266,309 |
Ajv -JSON Schema | 10,430,898 |
Arri | 2,243,081 |
Typebox | 98,2233 |
Zod | 52,9865 |
Library | op/s |
---|---|
JSON.parse | 777,989 |
Arri (Compiled) | 729,225 |
Arri | 376,368 |
Ajv -JTD (Compiled) | 245,730 |
Library | op/s |
---|---|
Ajv - JTD (Compiled) | 2,125,422 |
Arri (Compiled) | 1,963,027 |
Arri (Compiled) Validate and Serialize | 1,873,083 |
JSON.stringify | 981,663 |
Arri | 224,707 |
Library | op/s |
---|---|
Arri | 839,642 |
Typebox | 212,706 |
Zod | 488,505 |
Library | op/s |
---|---|
Arri | 210,933,339 |
Arri (Compiled) | 207,119,519 |
Ajv - JSON Schema (Compiled) | 193,992,382 |
Ajv - JTD (Compiled) | 169,603,655 |
Typebox (Compiled) | 113,166,065 |
Ajv - JSON Schema | 55,942,777 |
Ajv - JTD | 51,083,383 |
Typebox | 45,753,844 |
Zod | 1,163,445 |
Library | op/s |
---|---|
Arri (Compiled) | 153,161,724 |
Arri | 138,649,162 |
JSON.parse() | 19,045,882 |
Ajv - JTD (Compiled) | 9,363,809 |
Run nx build arri-validate
to build the library.
Run nx test arri-validate
to execute the unit tests via Vitest
Run nx benchmark arri-validate
to execute benchmarks
FAQs
A type builder and validation library built on top of the [Json Type Definition (RFC 8927)](https://jsontypedef.com) This library is pretty similar to [Typebox](https://github.com/sinclairzx81/typebox) except that it creates Json Type Definition (JTD) obj
The npm package arri-validate receives a total of 27 weekly downloads. As such, arri-validate popularity was classified as not popular.
We found that arri-validate demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.