@paralov/rojo-json-validator
v1.0.0
Published
Validates Rojo project JSON files using Zod schemas with TypeScript support and AI-friendly error messages.
Maintainers
Readme
Rojo JSON Validator
A comprehensive TypeScript validator for Rojo project JSON files using Zod schemas. This library validates the structure, properties, and relationships in Rojo project files, providing detailed, AI-friendly error messages.
Features
- ✅ Complete Rojo Support: Validates all Rojo project JSON structures based on rojo-rbx/rojo
- ✅ Type Safety: Full TypeScript support with inferred types
- ✅ Detailed Errors: Zod-powered validation with clear, actionable error messages
- ✅ AI-Ready: Error format designed for consumption by AI agents
- ✅ Test-Driven: Developed using TDD with comprehensive test coverage
Installation
pnpm add @paralov/rojo-json-validator
# or
npm install @paralov/rojo-json-validator
# or
yarn add @paralov/rojo-json-validatorUsage
Basic Validation
import { validateRojoProject } from '@paralov/rojo-json-validator';
const projectData = {
name: 'MyGame',
tree: {
$className: 'DataModel',
ServerScriptService: {
$path: 'src/server',
},
ReplicatedStorage: {
$path: 'src/shared',
},
},
};
const result = validateRojoProject(projectData);
if (result.success) {
console.log('✅ Valid project:', result.data);
} else {
console.error('❌ Validation errors:', result.error.issues);
}Strict Validation (Throws on Error)
import { validateRojoProjectStrict } from '@paralov/rojo-json-validator';
try {
const project = validateRojoProjectStrict(projectData);
console.log('Valid project:', project);
} catch (error) {
if (error instanceof z.ZodError) {
console.error('Validation errors:', error.issues);
}
}Validating Individual Nodes
import { validateProjectNode } from '@paralov/rojo-json-validator';
const node = {
$className: 'Model',
$properties: {
PrimaryPart: null,
},
Part1: {
$className: 'Part',
},
};
const result = validateProjectNode(node);Supported Project Structure
The validator supports all Rojo project features:
Top-Level Project Fields
$schema- Optional schema URI for IDE supportname- Project name (optional, can be inferred from filename)tree- Project instance tree (required)servePort- Default port forrojo serveservePlaceIds- Array of compatible place IDsblockedPlaceIds- Array of incompatible place IDsplaceId- Place ID to set when connectinggameId- Game/Universe ID to set when connectingserveAddress- IP address for the serveremitLegacyScripts- Whether to emit legacy Scripts/LocalScriptsglobIgnorePaths- Array of glob patterns to ignoresyncRules- Custom sync rules for file types
ProjectNode Fields
$className- Roblox instance class name$id- ID for referent properties$path- Path to file or folder (string or{ optional: string })$properties- Instance properties$attributes- Instance attributes$ignoreUnknownInstances- Live sync behavior for unknown instances- Child nodes (any key not starting with
$)
Property Types
The validator supports both shorthand and explicit property syntax:
### Property Type Syntax
Rojo supports both shorthand and explicit type syntax for properties:
```typescript
// Shorthand (inferred types)
{
$properties: {
Name: "MyPart",
Position: [0, 10, 0],
Size: [4, 1, 2],
Transparency: 0.5
}
}
// Explicit type syntax
{
$properties: {
Name: { String: "MyPart" },
Position: { Vector3: [0, 10, 0] },
Size: { Vector3: [4, 1, 2] },
Color: { Color3: [1, 0, 0] }
}
}Special Property Types
The validator supports all of Rojo's special property types that have unique XML serialization:
CFrame
// 12-element array: [x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22]
CFrame: [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]
// Or explicit object syntax
CFrame: {
CFrame: {
position: [0, 0, 0],
orientation: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
}
}NumberSequence / ColorSequence
// NumberSequence (e.g., for Beam Transparency)
Transparency: {
NumberSequence: {
keypoints: [
{ time: 0, value: 0, envelope: 0 },
{ time: 1, value: 1, envelope: 0 }
]
}
}
// ColorSequence (e.g., for Beam Color)
Color: {
ColorSequence: {
keypoints: [
{ time: 0, color: [1, 0, 0] },
{ time: 1, color: [0, 0, 1] }
]
}
}PhysicalProperties
// Default material properties
CustomPhysicalProperties: {
PhysicalProperties: "Default"
}
// Custom values
CustomPhysicalProperties: {
PhysicalProperties: {
density: 0.7,
friction: 0.3,
elasticity: 0.5,
frictionWeight: 1.0,
elasticityWeight: 1.0
}
}Font
FontFace: {
family: "rbxasset://fonts/families/RobotoMono.json",
weight: "Thin", // Thin, ExtraLight, Light, Regular, Medium, SemiBold, Bold, ExtraBold, Heavy
style: "Normal" // Normal, Italic
}Other Special Types
// Rect (e.g., ImageLabel SliceCenter)
SliceCenter: {
Rect: {
min: [0, 0],
max: [100, 100]
}
}
// Ray
Value: {
Ray: {
origin: [0, 10, 0],
direction: [0, -1, 0]
}
}
// BrickColor
BrickColor: { BrickColor: "Bright red" }
// Content/ContentId
SoundId: { Content: "rbxassetid://1234567" }
Texture: { ContentId: "rbxassetid://7654321" }
// Tags (array of strings)
Tags: { Tags: ["Enemy", "NPC", "Damageable"] }
// Enum
Material: { Enum: "Plastic" }
// NumberRange
Lifetime: { NumberRange: [0.5, 2.0] }
// UDim / UDim2
AnchorPoint: { UDim: [0.5, 0] }
Size: { UDim2: [[0.5, 0], [1, -20]] }
// Color3uint8 (RGB 0-255)
Color: { Color3uint8: [255, 128, 64] }
// Vector2int16
CustomProperty: { Vector2int16: [100, 200] }
// MaterialColors (Terrain only)
MaterialColors: {
Grass: [10, 20, 30],
Asphalt: [40, 50, 60]
}
## Examples
### Minimal Project
```json
{
"name": "MyProject",
"tree": {
"$className": "DataModel"
}
}Full Game Project
{
"name": "MyGame",
"servePort": 34872,
"servePlaceIds": [123456],
"emitLegacyScripts": false,
"tree": {
"$className": "DataModel",
"ServerScriptService": {
"$path": "src/server"
},
"ReplicatedStorage": {
"$path": "src/shared"
},
"StarterPlayer": {
"StarterPlayerScripts": {
"$path": "src/client"
}
}
}
}Model with Properties
{
"name": "MyModel",
"tree": {
"$className": "Model",
"$properties": {
"PrimaryPart": null
},
"Part1": {
"$className": "Part",
"$properties": {
"Position": [0, 5, 0],
"Size": [4, 1, 2],
"BrickColor": {
"BrickColor": "Bright red"
}
}
}
}
}Using Ref Properties
{
"name": "RefExample",
"tree": {
"$className": "Folder",
"Target": {
"$className": "Part",
"$id": "my-target"
},
"Pointer": {
"$className": "ObjectValue",
"$attributes": {
"Rojo_Target_Value": "my-target"
}
}
}
}Error Handling
The validator provides detailed, structured errors:
const result = validateRojoProject(invalidData);
if (!result.success) {
result.error.issues.forEach((issue) => {
console.log({
path: issue.path.join('.'),
message: issue.message,
code: issue.code,
});
});
}Example error output:
{
path: "tree.Child.GrandChild",
message: "ProjectNode must have either $className, $path, or be a known service/special instance",
code: "custom"
}API Reference
validateRojoProject(data: unknown)
Validates a Rojo project JSON object.
Returns: SafeParseReturnType<RojoProject>
validateRojoProjectStrict(data: unknown)
Validates a Rojo project JSON object and throws if invalid.
Returns: RojoProject
Throws: ZodError if validation fails
validateProjectNode(data: unknown)
Validates a ProjectNode independently.
Returns: SafeParseReturnType<ProjectNode>
Type Exports
RojoProject- The validated project typeProjectNode- A project tree node typePathNode- Path specification (string or{ optional: string })SyncRule- Sync rule configuration
Schema Exports
import { schemas } from '@paralov/rojo-json-validator';
// Access individual schemas for advanced usage
schemas.project;
schemas.projectNode;
schemas.pathNode;
schemas.syncRule;
schemas.unresolvedValue;Development
This project was developed using test-driven development (TDD):
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests in watch mode
pnpm test --watchReferences
License
MIT License - see LICENSE file for details.
Contributing
Issues and pull requests are welcome! Please ensure all tests pass before submitting.
Changelog
1.0.0
- Initial release
- Complete Rojo project JSON validation
- Support for all project fields and node types
- Comprehensive test coverage
- AI-friendly error messages
