@hyperfrontend/json-utils§

A schema and a broken config being typed on the left, and on the right one validate call printing all three violations as a path and keyword-code table, the missing required property reported at the root pointerA schema and a broken config being typed on the left, and on the right one validate call printing all three violations as a path and keyword-code table, the missing required property reported at the root pointer

Zero-dependency JSON Schema Draft v4 validation and schema generation utilities.

What is @hyperfrontend/json-utils?

@hyperfrontend/json-utils provides lightweight utilities for validating JSON data against JSON Schema Draft v4 specifications and generating schemas from JavaScript values via a functional-programming-first implementation.

The library provides two core capabilities: validation (checking if data conforms to a schema) and generation (creating a schema from sample data). Both functions use pure functional design with no side effects, making them predictable and easy to test.

Key Features

What This Library Does NOT Support

  • JSON Schema Draft 6, 7, 2019-09, or 2020-12 - Only Draft v4 is implemented
  • External $ref Resolution - Only internal references (#/definitions/...) are supported; no HTTP or file-based schema loading
  • const, contains, propertyNames, if/then/else - These are Draft 6+ features
  • Custom Format Validators - Built-in formats only (email, uri, date-time, etc.)

If you need full JSON Schema support across multiple draft versions, consider Ajv. This library is intentionally scoped for Draft v4 use cases where a lightweight, zero-dependency solution is preferred.

Why Use @hyperfrontend/json-utils?

Validate User-Provided Schemas

When your application accepts user-defined JSON Schema (like contract definitions or API specifications), you need reliable validation. This library validates data against those schemas without a large dependency tree, and because a schema is itself just JSON, you can validate a submitted schema by writing a schema for it and running validate twice.

Generate Schemas from Sample Data

Instead of manually writing schemas for data structures, generate them from representative samples. Useful for testing, documentation generation, and bootstrap schema creation.

Functional Programming Approach

All functions are pure with no side effects. Create validator functions with createValidator() and reuse them without worrying about shared state. Validation contexts are immutable and composable.

Installation

npm install @hyperfrontend/json-utils

Quick Start

Validate Data Against a Schema

import { validate } from '@hyperfrontend/json-utils'
import type { Schema } from '@hyperfrontend/json-utils'

const schema: Schema = {
  type: 'object',
  properties: {
    name: { type: 'string', minLength: 1 },
    age: { type: 'integer', minimum: 0 },
    email: { type: 'string', format: 'email' },
  },
  required: ['name', 'email'],
}

const result = validate({ name: 'Alice', email: 'alice@example.com' }, schema)
console.log(result.valid) // true

const invalid = validate({ name: '', age: -5 }, schema)
console.log(invalid.valid) // false
console.log(invalid.errors)
// [
//   { message: 'String must be at least 1 characters, got 0', path: '/name', code: 'minLength' },
//   { message: 'Number must be at least 0, got -5', path: '/age', code: 'minimum' },
//   { message: 'Missing required property: email', path: '/', code: 'required' }
// ]

Create Reusable Validators

import { createValidator } from '@hyperfrontend/json-utils'

const validateUser = createValidator({
  type: 'object',
  properties: {
    id: { type: 'integer' },
    username: { type: 'string', pattern: '^[a-z0-9_]+$' },
  },
  required: ['id', 'username'],
})

// Reuse without re-parsing schema
validateUser({ id: 1, username: 'alice' }) // { valid: true, errors: [] }
validateUser({ id: 'bad' }) // { valid: false, errors: [...] }

Generate Schemas from Data

import { toJsonSchema } from '@hyperfrontend/json-utils'

const data = {
  users: [
    { name: 'Alice', active: true },
    { name: 'Bob', active: false },
  ],
  metadata: { version: '1.0' },
}

const schema = toJsonSchema(data)
// {
//   type: 'object',
//   properties: {
//     users: {
//       type: 'array',
//       items: {
//         type: 'object',
//         properties: {
//           name: { type: 'string' },
//           active: { type: 'boolean' }
//         },
//         required: ['name', 'active']
//       }
//     },
//     metadata: {
//       type: 'object',
//       properties: { version: { type: 'string' } },
//       required: ['version']
//     }
//   },
//   required: ['users', 'metadata']
// }

Use $ref for Reusable Definitions

import { validate } from '@hyperfrontend/json-utils'

const schema = {
  definitions: {
    address: {
      type: 'object',
      properties: {
        street: { type: 'string' },
        city: { type: 'string' },
      },
      required: ['street', 'city'],
    },
  },
  type: 'object',
  properties: {
    home: { $ref: '#/definitions/address' },
    work: { $ref: '#/definitions/address' },
  },
}

validate(
  {
    home: { street: '123 Main', city: 'Boston' },
    work: { street: '456 Oak', city: 'Cambridge' },
  },
  schema
) // { valid: true, errors: [] }

API Overview

Two halves that mirror each other. validate walks a value against a schema and reports what is wrong with it; toJsonSchema runs the other way, inferring a schema from a value you already have. Everything else here is a variant of one of those two, or a type one of them hands back.

Validation is where the shape matters. validate(instance, schema, options?) returns a ValidationResult: a valid flag and a flat array of errors. Flat, because the walk carries an immutable context down through each property and array index, forking a child that knows the JSON Pointer for that position, while every keyword that fails appends to one shared list instead of throwing.

So a single call over a broken object comes back with every violation at once, each carrying the pointer that found it (/port, /items/2, or / when the failure is a missing key that no pointer can address) and the keyword code that raised it (minimum, pattern, type, required). Collecting everything is the default; collectAllErrors: false stops at the first. When one schema is used repeatedly, createValidator closes the schema and its options over a one-argument function, so callers pass only data.

Two things bound that walk, and both of them keep it offline. $ref resolves inside the document only: the root schema's definitions are indexed up front and any other #/... pointer is walked against the root, so a schema that refers back to itself validates arbitrarily deep recursive data with no network or filesystem access (a $ref that resolves to nothing is skipped, and one that loops without consuming data is reported). And with safePatterns on, every pattern and patternProperties regex goes to checkPatternSafety, or to a checker of your own, before RegExp is ever constructed: a user-supplied pattern can then come back as an ordinary validation error instead of being executed.

Generation is the smaller half. toJsonSchema(value, options?) returns a Schema describing the sample, and GenerateOptions decides how far that sample is trusted: whether an array's items are merged across every element, taken from the first, or assumed uniform, whether the observed keys become required, and whether additionalProperties: false is stamped on.

Every option, error field and type is in the full API reference.

Security: ReDoS Protection

JSON Schema's pattern and patternProperties keywords accept arbitrary regex patterns. Malicious or poorly-crafted patterns can cause ReDoS (Regular Expression Denial of Service) attacks with exponential backtracking.

Built-in Heuristics

Enable built-in ReDoS detection with safePatterns: true:

import { validate } from '@hyperfrontend/json-utils'

const schema = { type: 'string', pattern: '(a+)+' } // Known ReDoS pattern

const result = validate('aaa', schema, { safePatterns: true })
// result.valid = false
// result.errors[0].message = 'Unsafe regex pattern: Nested quantifiers detected...'

The built-in checker detects common dangerous patterns:

  • Nested quantifiers: (a+)+, (a*)*, ([a-z]+)+
  • Overlapping alternations: (a|a)+
  • Multiple unbounded wildcards: .*.*
  • Extremely large quantifier bounds: a{1,100000}

Custom Checker (Recommended for Production)

For comprehensive protection, provide a custom checker using safe-regex2:

import { validate, type PatternSafetyChecker } from '@hyperfrontend/json-utils'
import safeRegex from 'safe-regex2'

const checker: PatternSafetyChecker = (pattern) => ({
  safe: safeRegex(pattern),
  reason: 'Pattern may cause catastrophic backtracking',
})

validate(data, schema, { safePatterns: checker })

Exported Utilities

import { checkPatternSafety } from '@hyperfrontend/json-utils'

// Use built-in heuristics directly
const result = checkPatternSafety('(a+)+')
// { safe: false, reason: 'Nested quantifiers detected...' }

Supported Keywords

Compatibility

Runs on

  • Node.js>=18.0.0supported
  • Browserssupported
  • Web Workerssupported

Ships as

  • ESMTree-shakeable
  • CJSNode and older bundlers
  • IIFEHyperfrontendJsonUtils
  • UMDHyperfrontendJsonUtils

CDN Usage

<!-- unpkg -->
<script src="https://unpkg.com/@hyperfrontend/json-utils"></script>

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@hyperfrontend/json-utils"></script>

<script>
  const { validate, toJsonSchema } = HyperfrontendJsonUtils
</script>

Global variable: HyperfrontendJsonUtils

Dependencies

None: zero external dependencies.

Architecture Highlights§

$ref resolves against the schema's own definitions, so a schema that refers back to itself validates recursive data without looping. Validation accumulates errors instead of stopping at the first failure, so one validate call reports every violation in the data, each with its path, message, and code.

API Reference§

Filter:

ƒFunctions

§function

checkPatternSafety(pattern: string): PatternSafetyResult

Built-in heuristic pattern safety checker. Detects common ReDoS-vulnerable patterns without external dependencies.
This is a best-effort implementation that catches obvious cases:
  • Nested quantifiers: (a+)+, (a), (a+), etc.
  • Overlapping alternations with repetition: (a|a)+
  • Quantified groups followed by overlapping content: (.+)x
For comprehensive protection, use a dedicated library like safe-regex2.
Parameters
NameTypeDescription
§pattern
stringThe regex pattern string to check
Returns
PatternSafetyResult
Result indicating if the pattern is safe
Example

Checking pattern safety

checkPatternSafety('^[a-z]+$')  // => { safe: true }
checkPatternSafety('(a+)+')     // => { safe: false, reason: 'Nested quantifiers...' }
checkPatternSafety('.*.*')      // => { safe: false, reason: 'Multiple unbounded wildcards...' }
§function

createValidator(schema: Schema, options?: ValidateOptions): (data: unknown) => ValidationResult

Creates a reusable validator function from a JSON Schema.
Parameters
NameTypeDescription
§schema
SchemaJSON Schema to validate against
§options?
ValidateOptionsValidation options
Returns
(data: unknown) => ValidationResult
A function that validates data against the schema
Example

Creating reusable validator

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'integer', minimum: 0 }
  },
  required: ['name']
}

const validateUser = createValidator(schema)
const result = validateUser({ name: 'Alice', age: 30 })
console.log(result.valid) // true
§function

getJsonType(value: unknown): JsonType

Gets the JSON Schema type of a JavaScript value.
Parameters
NameTypeDescription
§value
unknownThe value to determine the type of
Returns
JsonType
The JSON Schema type
Example

Detecting JSON types

getJsonType('hello')    // => 'string'
getJsonType(42)         // => 'integer'
getJsonType(3.14)       // => 'number'
getJsonType(true)       // => 'boolean'
getJsonType(null)       // => 'null'
getJsonType([1, 2, 3])  // => 'array'
getJsonType({ key: 1 }) // => 'object'
§function

isEqual(a: unknown, b: unknown): boolean

Performs deep equality check for JSON values.
Used for enum validation and uniqueItems validation.
Parameters
NameTypeDescription
unknownFirst value to compare
unknownSecond value to compare
Returns
boolean
true if values are deeply equal, false otherwise
Example

Comparing values for equality

isEqual({ name: 'Alice' }, { name: 'Alice' }) // => true
isEqual([1, 2, 3], [1, 2, 3])                 // => true
isEqual({ a: 1 }, { a: 2 })                   // => false
isEqual([1, 2], [2, 1])                       // => false (order matters)
§function

toJsonSchema(value: unknown, options?: GenerateOptions): Schema

Generates a JSON Schema from a JavaScript value.
Parameters
NameTypeDescription
§value
unknownThe value to generate a schema from
§options?
GenerateOptionsGeneration options
Returns
Schema
A JSON Schema describing the value
Example

Generating schema from data

const data = {
  name: 'Alice',
  age: 30,
  tags: ['developer', 'typescript']
}

const schema = toJsonSchema(data)
// {
//   type: 'object',
//   properties: {
//     name: { type: 'string' },
//     age: { type: 'integer' },
//     tags: { type: 'array', items: { type: 'string' } }
//   },
//   required: ['name', 'age', 'tags']
// }
§function

validate(instance: unknown, schema: Schema, options?: ValidateOptions): ValidationResult

Validates a value against a JSON Schema.
Parameters
NameTypeDescription
§instance
unknownThe value to validate
§schema
SchemaThe JSON Schema to validate against
§options?
ValidateOptionsValidation options
Returns
ValidationResult
Validation result with valid flag and any errors
Example

Basic validation

const schema = { type: 'string', minLength: 1 }
const result = validate('hello', schema)
console.log(result.valid) // true

◈Interfaces

§interface

GenerateOptions

Options for schema generation.
Properties
§arrays?:ArrayGenerateOptionsArray handling mode
§includeRequired?:booleanWhether to include required fields (default: true)
§additionalProperties?:booleanWhether to allow additional properties (default: true)
§interface

PatternSafetyResult

Result of a pattern safety check.
Properties
§safe:booleanWhether the pattern is considered safe
§reason?:stringReason if unsafe (for error reporting)
§interface

Schema

JSON Schema Draft v4 type definition. Represents the structure of a JSON Schema document.
Properties
§id?:stringSchema identifier (Draft v4 uses 'id', not '$id')
§$schema?:stringDraft version
§$ref?:stringReference to another schema
§title?:stringHuman-readable title
§description?:stringHuman-readable description
§default?:unknownDefault value
§type?:JsonType | JsonType[]Type constraint
§enum?:unknown[]Enum constraint - value must be one of these
§minLength?:numberMinimum string length
§maxLength?:numberMaximum string length
§pattern?:stringRegex pattern the string must match
§format?:stringString format (e.g., 'email', 'uri', 'date-time')
§minimum?:numberMinimum value
§maximum?:numberMaximum value
§exclusiveMinimum?:booleanWhether minimum is exclusive (Draft v4 style)
§exclusiveMaximum?:booleanWhether maximum is exclusive (Draft v4 style)
§multipleOf?:numberValue must be a multiple of this
§properties?:Record<string, Schema>Property schemas
§required?:string[]Required property names
§additionalProperties?:boolean | SchemaSchema for additional properties, or false to disallow
§patternProperties?:Record<string, Schema>Pattern-based property schemas
§minProperties?:numberMinimum number of properties
§maxProperties?:numberMaximum number of properties
§dependencies?:Record<string, string[] | Schema>Property dependencies
§items?:Schema | Schema[]Schema for array items (single or tuple)
§additionalItems?:boolean | SchemaSchema for additional items beyond tuple
§minItems?:numberMinimum array length
§maxItems?:numberMaximum array length
§uniqueItems?:booleanWhether array items must be unique
§allOf?:Schema[]All schemas must match
§anyOf?:Schema[]At least one schema must match
§oneOf?:Schema[]Exactly one schema must match
§not?:SchemaSchema must NOT match
§definitions?:Record<string, Schema>Reusable schema definitions
§interface

ValidateOptions

Options for validation operations.
Properties
§collectAllErrors?:booleanWhether to collect all errors or stop at first (default: true)
§strictPatterns?:booleanWhether to report errors for invalid regex patterns (default: false)
§safePatterns?:boolean | PatternSafetyCheckerWhether to check patterns for ReDoS vulnerabilities (default: false). When true, uses built-in heuristics to detect common dangerous patterns. When a function is provided, uses that function for safety checking.
Built-in heuristics catch obvious cases like nested quantifiers. For comprehensive protection, provide a custom checker using safe-regex2.
§interface

ValidationError

Individual validation error.
Properties
§message:stringHuman-readable error message
§path:stringJSON Pointer path to the failing property (e.g., '/foo/bar/0')
§instance?:unknownThe value that failed validation
§code?:stringOptional error code for programmatic handling
§params?:Record<string, unknown>Additional error parameters
§interface

ValidationResult

Result of a validation operation.
Properties
§valid:booleanWhether validation passed
§errors:ValidationError[]Array of validation errors (empty if valid)

◆Types

§type

JsonType

JSON Schema type values.
type JsonType = "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"
§type

PatternSafetyChecker

Function type for custom pattern safety checkers. Users can provide their own implementation (e.g., using safe-regex2).
type PatternSafetyChecker = (pattern: string) => PatternSafetyResult
Example
import safeRegex from 'safe-regex2'

const checker: PatternSafetyChecker = (pattern) => ({
  safe: safeRegex(pattern),
  reason: 'Pattern may cause catastrophic backtracking'
})

validate(data, schema, { patternSafetyChecker: checker })

Browse guides filtered to this packageSuggest a guide