Hi everyone,
I’ve just released Schematics, a modern data validation library for Crystal, and I wanted to share it here in case it’s useful to others.
Repository: github.com/qequ/schematics
Latest release: v0.3.0
## What is it?
Schematics is a data validation library inspired by Python’s Pydantic. It provides a declarative way to define data models with automatic validation, type safety, and JSON serialization — all with zero runtime overhead thanks to Crystal’s macros.
Whether you’re building APIs, validating user input, or working with JSON data, Schematics makes it easy to define your data structures once and get validation, serialization, and type safety for free.
While building Schematics, I discovered just how powerful Crystal’s macro system is for type validation, unlike runtime validation in most languages.
## Key Features
-
Pydantic-style Models – Declarative field definitions with automatic validation
-
Type Safe – Full compile-time type checking leveraging Crystal’s type system
-
Rich Validators – Built-in validators for strings (length, format, patterns) and numbers (ranges, comparisons)
-
Custom Validation – Override
validate_modelfor complex cross-field validation logic -
JSON Serialization – Automatic
to_jsonandfrom_jsonmethods -
High Performance – ~2μs per validation using compile-time macros
-
Schema API – Alternative schema-based validation for simpler use cases
## Quick Example
require "schematics"
class User < Schematics::Model
field email, String,
required: true,
validators: [
Schematics.min_length(5),
Schematics.format(/@/),
]
field username, String,
required: true,
validators: [
Schematics.min_length(3),
Schematics.max_length(20),
Schematics.matches(/^[a-zA-Z0-9_]+$/),
]
field age, Int32?,
validators: [
Schematics.gte(0),
Schematics.lte(120),
]
field role, String,
default: "user",
validators: [Schematics.one_of(["admin", "user", "guest"])]
# Custom validation for complex rules
def validate_model
if age_val = age
if age_val < 18 && email.ends_with?(".gov")
add_error(:email, "Government emails require age 18+")
end
end
end
end
# Create and validate
user = User.new(
email: "john@example.com",
username: "john_doe",
age: 25,
role: "user"
)
user.valid? # => true
user.errors # => {}
# Automatic JSON serialization
json = user.to_json
# => {"email":"john@example.com","username":"john_doe","age":25,"role":"user"}
# Automatic JSON deserialization
user = User.from_json(json)
# Type-safe property access
email : String = user.email
age : Int32? = user.age
## Status
This is v0.3.0 with a stable API. The library is fully tested and ready for production use. The Model DSL is complete, and I’m looking to iterate based on community feedback.
Some ideas for future development:
- Struct support in Model DSL
- Type coercion
- JSON Schema export
- Async validation
Any thoughts, suggestions, or feedback are very welcome!
Thanks for reading!