Skip to content

kyawmincs/vson

Repository files navigation

VSON

VSON — Validation Schema Objects in Notation A DSL that compiles to JSON Schema Draft 2020-12

See it in action

Live Demo

Getting started: Run pnpm app to launch the validation UI and backend. From the UI you can compile VSON, validate JSON against generated schemas, and run the full workflow. For CLI-only use, run pnpm compile:default after building.


Prerequisites

  • Node.js v20 or higher (download)
  • pnpm v10 or higher

To install pnpm:

npm install -g pnpm

Quick Start

pnpm install           # Install root dependencies
cd ui
pnpm install           # Install UI dependencies
cd ..
pnpm app               # Open validation UI and start backend (compile from the UI)
pnpm test              # Run tests
pnpm compile:default   # Compile input/user.vson → output/schema.json (CLI)

Project Structure

src/
├── index.ts                # Entry point, CLI runner
├── cli.ts                  # CLI argument parsing and help text
├── compiler.ts             # compile() and tryCompile() functions
├── server.ts               # HTTP backend for the validation UI
│
├── grammars/               # ANTLR grammar definitions
│   ├── VSONLexer.g4        # Token definitions
│   └── VSONParser.g4       # Grammar rules
│
├── lexer/
│   ├── lexer.ts            # tokenize(source) → Token[]
│   └── VSONLexer.ts        # ANTLR-generated TypeScript (do not edit)
│
├── parser/
│   ├── parser.ts           # parse(), parseToCST(), Parser class
│   ├── util.ts             # Parser utilities (error context paths)
│   ├── VSONParser.ts       # ANTLR-generated TypeScript (do not edit)
│   ├── VSONParserListener.ts   # ANTLR-generated listener (do not edit)
│   └── VSONParserVisitor.ts    # ANTLR-generated visitor (do not edit)
│
├── ast/
│   ├── ast.ts              # AST node interfaces + type guards
│   └── visitor.ts          # Visitor pattern base class
│
├── errors/
│   ├── context.ts          # ErrorContext — structured error collection
│   └── listener.ts         # ANTLR ErrorListener with line/column info
│
├── evaluator/
│   ├── evaluator.ts        # Evaluator class, EvaluationContext
│   ├── evaluation-visitor.ts   # Visitor that builds context from AST
│   ├── type-check-visitor.ts   # Example type-check visitor (optional)
│   └── generator.ts        # Converts context → JSON Schema
│
└── builtin/
    └── builtin.ts          # Built-in type → JSON Schema mappings

ui/                         # React validation UI (Vite + TypeScript)

tests/
├── lexer.test.ts           # Token output tests
├── parser.test.ts          # CST structure tests
├── ast.test.ts             # AST / parse() error handling and shape
└── basic.test.ts           # End-to-end compile() tests

Pipeline

The compiler turns .vson source into JSON Schema in four stages:

  1. Lexing — character stream → tokens (VSONLexer)
  2. Parsing — tokens → CST → AST (VSONParser, buildAST()). Custom ErrorListener reports syntax errors with line/column info.
  3. Evaluation — AST → evaluation context (EvaluationVisitor: schema declarations, field declarations, mutate statements, conditionals with has() / && / || / !). Field types are resolved via resolveType(): built-in types, arrays (Type[]), and schema composition (references to other schemas → inline object schema).
  4. Generation — context → JSON Schema Draft 2020-12 (Generator); multiple schemas emitted when composition is used.

Diagram: pipeline.mmd (Mermaid). Rendered: Pipeline


DSL Syntax

Current Syntax

schema User {
  name: string;
  age: integer;
  toBeRemoved: boolean @optional;
}

if (has(User, "name") || has(User, "missingField")) {
  add(User, "requiredField", boolean);

  if (!has(User, "missingField")) {
    addOptional(User, "missingField", integer);
  }

  addOptional(User, "optionalField", string);
  remove(User, "toBeRemoved");
}

Built-in types: string, integer, float, number, boolean → JSON Schema types. Any built-in type can be used as an

array with Type[] (e.g. string[], integer[]). Fields can be marked inline optional with @optional.

Schema composition: a field type can be another schema name (e.g. address: Address, waitlist: User[]), producing nested or array-of-object output; schemas must be declared before use and cannot be circular. Mutable statements (add, addOptional, remove) modify the schema after declaration.

Conditionals (if) use has(Schema, "field") with logical operators (&&, ||, !) and support nesting.


Schema Composition

You can reference other schemas as field types. Each schema is emitted as a separate JSON Schema object; composition is inlined (no $ref). Declaration order matters: a schema must be declared before it is used. Circular references are not allowed.

Syntax

  • Single schema: fieldName: SchemaName; (e.g. instructor: User)
  • Array of schema: fieldName: SchemaName[]; (e.g. waitlist: User[])

Example

schema Address {
  city: string;
}

schema User {
  name: string;
  address: Address;
}

schema Course {
  instructor: User;
  waitlist: User[];
}

The compiler outputs one JSON Schema per declared schema (Address, User, Course). The instructor and address fields are inlined as type: "object" with the corresponding properties and required.


Mutable State Operations

Mutable operations allow post-declaration schema modification. They execute sequentially after the schema declaration.

Syntax

Operation Syntax Description
add add(SchemaName, "fieldName", type); Adds a required field to an existing schema
addOptional addOptional(SchemaName, "fieldName", type); Adds an optional field to an existing schema
remove remove(SchemaName, "fieldName"); Removes a field from an existing schema

Types in mutable operations can be scalars (string, integer, …) or arrays (string[], float[], …).

Example

Input:

schema User {
  name: string;
}
add(User, "age", integer);

Output:

{
  "type": "object",
  "properties": {
    "name": {"type": "string"},
    "age": {"type": "integer"}
  },
  "required": ["name", "age"]
}

JSON Schema Output Format

Target: JSON Schema Draft 2020-12

Input (matches input/user.vson):

schema User {
  name: string;
  age: integer;
  toBeRemoved: boolean @optional;
}

if (has(User, "name") || has(User, "missingField")) {
  add(User, "requiredField", boolean);

  if (!has(User, "missingField")) {
    addOptional(User, "missingField", integer);
  }

  addOptional(User, "optionalField", string);
  remove(User, "toBeRemoved");
}

Output (output/schema.json):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {"type": "string"},
    "age": {"type": "integer"},
    "requiredField": {"type": "boolean"},
    "missingField": {"type": "integer"},
    "optionalField": {"type": "string"}
  },
  "required": ["name", "age", "requiredField"]
}

Scripts

Command Description
pnpm install Install dependencies
pnpm test Run Jest tests (pretest runs scripts/pretest.ts first)
pnpm test tests/ast.test.ts Run specific test files, e.g. tests/ast.test.ts
pnpm build Compile TypeScript to dist/
pnpm dev -- <args> Run CLI without building (e.g. pnpm dev -- compile ...)
pnpm start -- <args> Run built CLI (node dist/index.js)
pnpm app Starts the backend and UI together (backend on :8787, UI on :5173)
pnpm compile:default Compile input/user.vsonoutput/schema.json
pnpm gen Regenerate lexer/parser (run manually if you change grammars; requires Java). Not used by other scripts.
pnpm check Full validation: build, test, compile:default, conflicts, strict TS. No Java — scripts do not run or require Java.
pnpm backend Starts the backend locally on port 8787
pnpm ui Starts the frontend on http://localhost:5173/

Note: All scripts are cross-platform and work on Windows, macOS, and Linux.


Key Files to Modify

Task Files
Add new token/keyword src/grammars/VSONLexer.g4pnpm gen
Change grammar rules src/grammars/VSONParser.g4pnpm gen
Add new AST node src/ast/ast.ts, src/ast/visitor.ts
Change how AST is built src/parser/parser.ts (buildAST function)
Change evaluation logic src/evaluator/evaluation-visitor.ts
Change JSON Schema output src/evaluator/generator.ts
Add new built-in type src/builtin/builtin.ts
Add CLI commands src/cli.ts, src/index.ts
Add mutable operations src/ast/ast.ts, src/evaluator/evaluation-visitor.ts

Technology Stack

Category Choice Version
Package Manager pnpm 10.x
Language TypeScript 5.x
Parser Generator antlr4ts 0.5.0-alpha.4
ANTLR Runtime antlr4ts 0.5.0-alpha.4
Testing Jest + ts-jest 29.x
Dev Runtime tsx 4.x
Module System ESM -

Note: Lexer and parser use only native TypeScript and antlr4ts — no Java at runtime. Scripts do not run or require Java. All scripts (scripts/*.ts) are in TypeScript and run via tsx. To regenerate lexer/parser after grammar changes, run pnpm gen manually (that command uses Java via antlr4ts-cli). pnpm check works the same on Windows, macOS, and Linux.


Current Limitations

  • Composition is inlined (no JSON Schema $ref); each schema is a standalone object
  • No circular schema references
  • No schema imports from other files
  • Only has() is supported inside a conditional statement (eg. if (has(...) .... ))
  • Conditional only supports at most two operands (eg. if (A && B) is supported, but if (A && B && C) is not)

Planned Features

  • Inline optional fields (field: type @optional)
  • Mutable state operations (add, addOptional, remove)
  • Array types (tags: string[], float[], etc.)
  • Conditionals with has(), &&, ||, ! (including nesting)
  • Custom error reporting with line/column info
  • Validation UI with live editing
  • Schema composition (address: Address, waitlist: User[])
  • JSON Schema $ref output (optional)
  • Ajv validation integration

About

External DSL that compiles into JSON Schema. Pipeline includes lexing, parsing, custom AST structure and JSON Schema generation with React code editor

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors