ata-validator-crystal
Version, currently 0.1.13 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
Nothing has been indexed for 0.1.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
ata-validator-crystal:
github: GroophyLifefor/ata-validator-crystal
version: ~> 0.1.1Then run:
shards installshard.yml
No shard.yml has been indexed for 0.1.1. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
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.1.1- Tagged
- Aug 1, 2026
- Commit
f93ad3c849b1- Indexed
- not yet
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