JSON Schema to Luau
Convert JSON Schema to Luau type definitions with full support for constraints and advanced schema features.
✨ Features
- ✅ Full JSON Schema support (objects, arrays, primitives, enums, const)
- ✅ Handles
$ref,definitions, and$defs - ✅ Composition support (
allOf,anyOf,oneOf) - ✅ Constraints preserved as Luau comments (ranges, string limits, patterns, array bounds)
- ✅ Required/optional property handling
- ✅ CLI and library usage
- ✅ Type-safe conversion with clear errors
⬇️ Installation
You can install json-schema-to-luau via Cargo or download a pre-built binary from GitHub Releases.
Cargo (Library)
To use as a Rust library in your project:
Cargo (CLI)
To install the command-line interface globally:
GitHub Releases (CLI Binaries)
Pre-compiled binaries for various platforms (Linux, macOS, Windows) are available on the GitHub Releases page. This is the fastest way to get the CLI without needing a Rust toolchain.
🚀 Usage
Command Line Interface (CLI)
# Convert a file and output to another file
# Read schema from standard input
|
# Specify a custom type name (defaults to 'Root')
Rust Library
use convert_schema;
Output:
export type Root =
🛠️ Luau Type Mapping & Behavior
The converter maps JSON Schema concepts to the closest viable Luau types. This is crucial as Luau is not TypeScript and has different type system limitations.
Primitive Mapping
| JSON Schema | Luau Output | Notes |
|---|---|---|
"string" |
string |
|
"number", "integer" |
number |
|
"boolean" |
boolean |
|
"null" |
nil |
Often combined: string | nil |
| enum (strings) | "a" | "b" |
Uses union of literal strings |
| enum (numbers) | number |
Luau cannot represent numeric literal types |
Complex Type Mapping
| JSON Schema | Luau Output | Description |
|---|---|---|
array |
{ T } |
General array type |
| object map | { [string]: T } |
Objects with additionalProperties or no properties |
anyOf / oneOf |
union (A | B) |
oneOf exclusivity cannot be enforced in Luau |
allOf |
intersection (A & B) or merged object |
Intersection for standalone allOf, otherwise merged into parent |
$ref, $defs |
export type Name = ... |
Always exports named types; never inlines |
🧩 Composition Handling
anyOf / oneOf (Union)
Both are converted to a Luau union type, as Luau does not enforce the exclusivity of oneOf.
export type T = A | B
allOf (Intersection / Merging)
- If the parent schema defines properties,
allOfmembers are merged into the parent object type. - Otherwise, it is converted to a Luau intersection:
export type T = A & B.
📝 Examples
Object with Constraints
export type Root =
Arrays
--- @minItems 1
--- @maxItems 10
export type Root =
Enum
export type Root = "red" | "green" | "blue"
Definitions ($ref / $defs)
export type Root =
export type Person =
Note: Referenced types like Person are always exported as named types.
🛑 Limitations
Luau has a simpler type system than JSON Schema. The following features degrade gracefully (i.e., they are ignored or simplified):
- Tuple schemas (
items: [A, B, C]) → Not supported. - Conditionals (
if/then/else) → Ignored. - Dependencies (
dependencies,dependentSchemas,dependentRequired) → Ignored. - Pattern matching (
patternProperties,propertyNames) → Ignored/Simplified. - Remote
$refresolution → Only local fragments (#/...) are supported. - Number literal enums → Collapse to
number. - Exclusive constraints → Cannot be enforced, only documented via comments.
💡 Troubleshooting & FAQ
“Why is my numeric enum turned into number?”
Luau does not support numeric literal types (e.g., 1 | 2 | 3). Numeric enums from JSON Schema must degrade to the base type number.
“Why does my object turn into { [string]: any }?”
This typically happens when the schema is an object that allows arbitrary properties but does not explicitly declare any of its own (properties is absent or empty, and additionalProperties is the default true).
“Why is a type inlined instead of exported?”
Only types resolved via a $ref to a root-level definition (#/definitions/Name or #/$defs/Name) are exported as named types. All other complex types (like nested objects or arrays) are intentionally inlined for conciseness.
📦 API Reference (Rust)
convert_schema(&str) -> Result<String>
The simplest function. Parses the JSON Schema string and returns the resulting Luau type definitions.
SchemaConverter
For advanced usage (e.g., reusing definitions across multiple calls):
let mut converter = new;
let luau = converter.convert?;
let luau = converter.convert_with_name?;
⚠️ Performance Notes
- The converter is designed for codegeneration, not high-frequency runtime use.
$refresolution is single-pass and only supports local fragments.
📄 License
MIT License