ata-validator-crystal
Version, currently 0.2.03 versions
- 0.2.0latestAug 1, 2026
- 0.1.1not indexedAug 1, 2026
- 0.1.0not indexedAug 1, 2026
github.com/GroophyLifefor/ata-validator-crystal
Crystal FFI bindings for ata-validator, the C++20 JSON Schema validator
Installation
# Add this to your shard.yml
dependencies:
ata-validator-crystal:
github: GroophyLifefor/ata-validator-crystal
version: ~> 0.2.0Then run:
shards installshard.yml
- Crystal
>= 1.15.0- License
- MIT
- Author
- Murat Kirazkaya
Dependencies
Development Dependencies
- athena-validator~> 0.5.0github: athena-framework/validatordev
README
ata-validator-crystal
Crystal bindings for ata-validator — a high-performance C++20 JSON Schema validator.
The C++ core uses simdjson and the RE2 regex engine; this shard only wraps its pure C API (ata_c.h). Struct layouts and function signatures match ata_c.h exactly.
Install
Add to shard.yml:
dependencies:
ata-validator-crystal:
github: groophylifefor/ata-validator-crystal
version: ~> 0.1.0
shards install
Native library
Two parts are required:
ata.lib(orlibata.a) — at link time, passed tocrystal buildvia/LIBPATH(Windows/MSVC) or-L(Unix)ata.dll(orlibata.so) — at runtime, next to the executable, onPATH, or pointed to byATA_VALIDATOR_LIB
To build the library into libata/ (the ata-validator source must be a sibling directory):
crystal run scripts/build_native.cr
This script compiles the shared library in ata-validator with CMake and copies libata/ata.dll + libata/ata.lib into the package root. (Default source path is ../../ata-validator; override with the ATA_VALIDATOR_SRC env var.)
Build
Windows (MSVC linker):
crystal build src/main.cr --link-flags "/LIBPATH:ata-validator-crystal/libata"
Linux/macOS:
crystal build src/main.cr --link-flags "-Lata-validator-crystal/libata -lata"
Usage
require "ata-validator-crystal"
schema = <<-JSON
{
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name"]
}
JSON
puts "ata v#{AtaValidator.version}"
# Reuse a compiled schema
validator = AtaValidator::Validator.new(schema)
result = validator.validate(%({"name": "Mert", "age": 28}))
puts result.valid # => true
result = validator.validate(%({"age": -1}))
puts result.valid # => false
result.errors.each do |e|
puts "#{e.path}: #{e.message}" # => /age: value -1.000000 < minimum 0.000000
end
validator.valid?(%({"name": "Mert"})) # => true (quick boolean check)
validator.close
# One-shot (compiles the schema on every call)
AtaValidator.validate(schema, %({"name": "Mert"})).valid
Schema DSL
Ata.object is a compile-time macro: it reads the block's AST and generates a real struct with class methods schema_json, validate, valid? and from_json, plus a typed getter for every field. Every field is required unless optional: true is passed.
Ata.object User do
string :name, min: 3, max: 10
int :age, gt: 0, lte: 120
string :email, format: "email", optional: true
bool :active
end
User.valid?(%({"name": "Mert", "age": 28, "active": true})) # => true
User.valid?(%({"name": "Me", "age": 28, "active": true})) # => false (minLength)
User.valid?(%({"name": "Mert", "age": 0, "active": true})) # => false (exclusiveMinimum)
u = User.from_json(%({"name": "Mert", "age": 28, "active": true}))
u.name # => "Mert" (typed getter)
u.email # => nil (optional field)
puts User.schema_json
# {"type":"object","properties":{"name":{"type":"string","minLength":3,"maxLength":10},
# "age":{"type":"integer","exclusiveMinimum":0,"maximum":120},...},"required":["name","age","active"]}
| Method | Arguments | JSON Schema |
|---|---|---|
string | min / max → minLength / maxLength, pattern, format, values: → enum | {"type": "string", ...} |
int | gt / lt → exclusiveMinimum / exclusiveMaximum, gte / lte → minimum / maximum | {"type": "integer", ...} |
float | same as int | {"type": "number", ...} |
bool | — | {"type": "boolean"} |
any | — | {} (any value accepted) |
array | of: :string/:int/:float/:bool/:any or a nested Ata.object schema; min_items / max_items | {"type": "array", "items": {...}} |
object | of: a nested Ata.object schema | embeds the schema as-is |
Nested schemas compose:
Ata.object Address do
string :city, min: 1
int :zip, gte: 0
end
Ata.object Person do
string :name, min: 3
object :address, of: Address
array :tags, of: :string, min_items: 1
end
gt:/lt:emit the draft-06/07 boolean-independent form ("exclusiveMinimum": 0), which is what the native validator implements.
API
AtaValidator.version : StringAtaValidator::Validator.new(schema_json)— compiles the schema oncevalidate(json) : ValidationResult(valid+errors)valid?(json) : Boolclose/finalize— frees the compiled schema
AtaValidator.validate(schema_json, json) : ValidationResult— one-shotAta.object Name do ... end— macro DSL (see above)Name.valid?(json) : Bool,Name.validate(json) : ValidationResult,Name.schema_json : String,Name.from_json(json) : Name
- Error types:
CompileError(invalid schema),ValidationError(path,message)
Test
crystal run scripts/build_native.cr
crystal spec --link-flags "/LIBPATH:libata"
Benchmarks
bench/ compares ata-validator-crystal against JSON::Serializable and Athena::Validator on five scenarios: parse valid JSON, invalid JSON, nested objects, 100k bulk validation and per-operation allocation.
Results (2026-08-01, Windows 11 / MSVC, Crystal 1.15.0, --release --no-debug)
Higher is better for throughput, lower is better for allocation.
| Scenario | Metric | ata-validator-crystal | JSON::Serializable | Athena::Validator | × vs JSON::Serializable | × vs Athena::Validator |
|---|---|---|---|---|---|---|
| Parse valid JSON | ops/s | 750 968 | 449 970 | 299 640 | 1.67× | 2.51× |
| Parse valid JSON | bytes/op | 32 | 816 | 1 232 | 25.5× | 38.5× |
| Invalid JSON (malformed) | ops/s | 641 287 | 1 138 | 1 090 | 563.5× | 588.3× |
| Invalid JSON (malformed) | bytes/op | 112 | 5 504 | 5 504 | 49.1× | 49.1× |
| Nested object | ops/s | 338 270 | 304 255 | 190 074 | 1.11× | 1.78× |
| Nested object | bytes/op | 32 | 1 025 | 1 632 | 32.0× | 51.0× |
| 100.000 validation | ops/s | 844 921 | 407 730 | 274 917 | 2.07× | 3.07× |
| 100.000 validation | bytes/op | 32 | 816 | 1 232 | 25.5× | 38.5× |
| Allocation | bytes/op | 32 | 816 | 1 232 | 25.5× | 38.5× |
Reading the ratios: for
ops/srows,N×= ata-validator-crystal is N× faster; forbytes/oprows,N×= ata-validator-crystal allocates N× less memory.
Note on "Invalid JSON":
JSON::SerializableandAthena::Validatorraise aJSON::ParseExceptionon malformed JSON, so each iteration pays exception-handling cost (hence the ~1 000 ops/s and 5.5 kB/op).ata-validator-crystalreturnsvalid=falsewith a structured error list instead of throwing, which is why it stays fast.
These numbers are machine- and schema-specific — rerun locally with crystal build bench/bench.cr --release --no-debug to reproduce.
# build with the shared fixtures (requires the dev dependency: shards install)
crystal build bench/bench.cr -o bin/bench.exe --release --no-debug --link-flags "/LIBPATH:libata"
# run everything (100k / 20k iterations per scenario)
$env:PATH = "$PWD\libata;$env:PATH"
.\bin\bench.exe
# run a subset, or override iterations
.\bin\bench.exe nested
.\bin\bench.exe 10000
Each scenario prints:
- correctness — whether each target accepts/rejects each fixture
- throughput — total ms and ops/s over N iterations
- allocation — heap bytes per operation (
GC.statsdelta)
Adding a scenario
Drop a new file into bench/scenarios/ — it is auto-required. Register a row with Bench.register:
require "../framework"
Bench.register("My scenario", description: "...", order: 6) do |s|
s.fixture("valid", %({"name": "Mert", "age": 28}))
s.fixture("invalid", %({"age": -1}))
s.target("my-tool") { |json| my_tool_valid?(json) }
end
Reuse the shared types (Person, PersonWithAddress, Bench::SCHEMA_*, Bench.person_workloads) from bench/support.cr, or define your own. The first fixture is used for the throughput/allocation runs.
License
MIT. The C++ core comes from ata-validator, MIT licensed (original copyright preserved in LICENSE).
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.2.0- Tagged
- Aug 1, 2026
- Commit
b0aa950a9bf2- Crystal
>= 1.15.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/GroophyLifefor/ata-validator-crystal
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 15, 2026
- Synced
- Aug 15, 2026
- Versions
- 3