JsonGuard
Enterprise-grade JSON Schema validation for Rails applications with beautiful error messages, context-aware rules, and production-ready monitoring.
π Features
- π― Rails-Native Syntax - Clean, ActiveRecord-like schema definitions
- π Multiple Message Types - Simple, dynamic, i18n, and template-based error messages
- β‘ Performance Optimized - Built-in caching and lazy loading
- π¨ Context-Aware - Different validation rules per context (create/update/api)
- π Production Ready - Monitoring, analytics, and error tracking
- π§ Developer Tools - CLI generators and migration helpers
- π Internationalization - Full i18n support for error messages
- π Extensible - Custom validators and formatters
π¦ Installation
Add this line to your application's Gemfile:
gem 'json-guard'
And then execute:
bundle install
Or install it yourself as:
gem install json-guard
Requirements
- Ruby 2.7.0 or higher
- Rails 6.0 or higher
- ActiveSupport 6.0 or higher
β‘ Quick Start
1. Define Your Schema
class UserProfileSchema < JsonGuard::Schema
name :string, required: true, min_length: 2
email :string, required: true, format: :email
age :integer, minimum: 18, maximum: 120
preferences do
theme :string, enum: %w[light dark], message: "Theme must be light or dark"
notifications :boolean, required: true
language :string, required: true, enum: %w[en es fr de]
end
settings do
timezone :string, format: :timezone
currency :string, enum: %w[USD EUR GBP]
end
end
2. Add to Your Model
class User < ApplicationRecord
validates_json_schema :profile, schema: UserProfileSchema
end
3. Validate Your Data
user = User.new(profile: { preferences: { theme: "purple" } })
user.valid? # => false
user.errors.full_messages
# => ["Theme must be light or dark"]
π§ Configuration
Global Configuration
Create an initializer file config/initializers/json_guard.rb
:
JsonGuard.configure do |config|
# Performance settings
config.cache_schemas = true
config.cache_validators = true
config.max_cache_size = 1000
# Error handling
config.raise_on_validation_error = false
config.detailed_error_messages = true
config.include_error_paths = true
# Internationalization
config.i18n_scope = 'json_guard.errors'
config.default_locale = :en
# Custom formats
config.custom_formats = {
phone: /^\+?[\d\s\-\(\)]+$/,
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
hex_color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
version: /^\d+\.\d+\.\d+$/
}
# Performance monitoring
config.performance_threshold = 100 # milliseconds
config.log_validation_errors = Rails.env.development?
config.log_performance_metrics = Rails.env.production?
end
Environment-Specific Configuration
Development
# config/environments/development.rb
JsonGuard.configure do |config|
config.include_suggestions = true
config.detailed_errors = true
config.cache_schemas = false # Reload schemas in development
end
Production
# config/environments/production.rb
JsonGuard.configure do |config|
config.include_suggestions = false
config.cache_schemas = true
config.performance_monitoring = true
end
Test
# config/environments/test.rb
JsonGuard.configure do |config|
config.detailed_errors = true
config.cache_schemas = false
config.raise_on_validation_failure = true
end
π Usage Guide
Basic Schema Types
class BasicSchema < JsonGuard::Schema
# String validations
title :string, required: true, min_length: 3, max_length: 100
slug :string, format: :slug
# Number validations
price :number, minimum: 0, maximum: 1000000
quantity :integer, minimum: 1
# Boolean validations
active :boolean, required: true
# Array validations
tags :array, items: { type: :string }, max_items: 10
# Enum validations
status :string, enum: %w[draft published archived]
end
Nested Objects
class OrderSchema < JsonGuard::Schema
order_id :string, required: true
# Nested object
customer do
name :string, required: true
email :string, required: true, format: :email
# Deeply nested
address do
street :string, required: true
city :string, required: true
postal_code :string, required: true
end
end
# Array of objects
items :array, items: {
type: :object,
properties: {
product_id: { type: :string, required: true },
quantity: { type: :integer, minimum: 1 },
price: { type: :number, minimum: 0 }
}
}
end
Context-Aware Validation
class UserSchema < JsonGuard::Schema
email :string, required: true, format: :email
# Different validation rules based on context
case_when context: :create do
password :string, required: true, min_length: 8
end
case_when context: :update do
password :string, min_length: 8 # Optional for updates
end
# Role-based validation
case_when "user.role": "admin" do
permissions :array, required: true
end
end
Custom Error Messages
class ProductSchema < JsonGuard::Schema
name :string, required: true,
message: "Product name is required"
price :number, minimum: 0,
messages: {
required: "Price is required",
invalid_type: "Price must be a number",
too_small: "Price must be at least $0"
}
# Dynamic messages with interpolation
discount :number, minimum: 0, maximum: 100,
message: "Discount must be between 0% and 100%"
end
Rails Integration
ActiveRecord Models
class User < ApplicationRecord
validates_json_schema :profile, schema: UserProfileSchema
validates_json_schema :preferences,
schema: UserPreferencesSchema,
context: :user_preferences
end
API Controllers
class Api::V1::UsersController < ApplicationController
before_action :validate_request, only: [:create, :update]
def create
@user = User.new(user_params)
if @user.save
render json: { success: true, user: @user }, status: :created
else
render json: {
success: false,
errors: @user.errors.full_messages
}, status: :unprocessable_entity
end
end
private
def validate_request
validator = JsonGuard::Validator.new(CreateUserSchema)
unless validator.validate(request_json)
render json: {
success: false,
errors: validator.errors.full_messages,
details: validator.errors.detailed_messages
}, status: :bad_request
end
end
def request_json
@request_json ||= JSON.parse(request.body.read)
rescue JSON::ParserError
{}
end
end
Direct Validation
# Validate data directly
validator = JsonGuard::Validator.new(UserSchema)
result = validator.validate(user_data)
if result.valid?
puts "Data is valid!"
else
puts "Errors: #{result.errors.full_messages}"
end
π§ͺ Testing
RSpec Integration
# spec/schemas/user_profile_schema_spec.rb
RSpec.describe UserProfileSchema do
describe "validation" do
it "validates valid data" do
valid_data = {
name: "John Doe",
email: "john@example.com",
age: 30,
preferences: {
theme: "dark",
notifications: true,
language: "en"
}
}
validator = JsonGuard::Validator.new(UserProfileSchema)
expect(validator.validate(valid_data)).to be_truthy
end
it "rejects invalid data" do
invalid_data = {
name: "",
email: "invalid-email",
age: 15,
preferences: {
theme: "rainbow"
}
}
validator = JsonGuard::Validator.new(UserProfileSchema)
expect(validator.validate(invalid_data)).to be_falsy
expect(validator.errors.full_messages).to include("Theme must be light or dark")
end
end
end
Model Testing
# spec/models/user_spec.rb
RSpec.describe User do
describe "profile validation" do
it "validates correct profile data" do
user = User.new(
email: "test@example.com",
profile: {
name: "Test User",
preferences: {
theme: "light",
notifications: true,
language: "en"
}
}
)
expect(user).to be_valid
end
it "rejects invalid profile data" do
user = User.new(
email: "test@example.com",
profile: {
preferences: {
theme: "invalid"
}
}
)
expect(user).not_to be_valid
expect(user.errors[:profile]).to be_present
end
end
end
Test Helpers
# spec/support/json_guard_helpers.rb
module JsonGuardHelpers
def expect_schema_validation(schema, data, to_be_valid: true)
validator = JsonGuard::Validator.new(schema)
result = validator.validate(data)
if to_be_valid
expect(result).to be_truthy,
"Expected data to be valid, but got errors: #{validator.errors.full_messages}"
else
expect(result).to be_falsy,
"Expected data to be invalid"
end
end
end
RSpec.configure do |config|
config.include JsonGuardHelpers
end
π Internationalization
Setting up I18n
# config/locales/json_guard.en.yml
en:
json_guard:
errors:
required: "is required"
invalid_type: "must be a %{expected_type}"
too_short: "must be at least %{minimum} characters"
too_long: "must be at most %{maximum} characters"
invalid_format: "has an invalid format"
invalid_enum: "must be one of: %{allowed_values}"
too_small: "must be greater than or equal to %{minimum}"
too_large: "must be less than or equal to %{maximum}"
# config/locales/json_guard.es.yml
es:
json_guard:
errors:
required: "es requerido"
invalid_type: "debe ser un %{expected_type}"
too_short: "debe tener al menos %{minimum} caracteres"
too_long: "debe tener como mΓ‘ximo %{maximum} caracteres"
invalid_format: "tiene un formato invΓ‘lido"
invalid_enum: "debe ser uno de: %{allowed_values}"
too_small: "debe ser mayor o igual a %{minimum}"
too_large: "debe ser menor o igual a %{maximum}"
Using I18n in Schemas
class UserSchema < JsonGuard::Schema
name :string, required: true,
message: I18n.t('json_guard.errors.name_required')
email :string, required: true, format: :email,
messages: {
required: I18n.t('json_guard.errors.email_required'),
invalid_format: I18n.t('json_guard.errors.email_invalid')
}
end
π Performance Optimization
Schema Caching
# Enable caching for frequently used schemas
class CachedSchema < JsonGuard::Schema
cache_schema true
compile_validations true
# Your schema definition
end
Batch Validation
# Validate multiple records efficiently
validator = JsonGuard::BatchValidator.new(UserSchema)
results = validator.validate_batch([user1_data, user2_data, user3_data])
results.each_with_index do |result, index|
if result[:valid]
puts "Record #{index} is valid"
else
puts "Record #{index} has errors: #{result[:errors]}"
end
end
Performance Monitoring
# Monitor validation performance
JsonGuard.configure do |config|
config.performance_threshold = 50 # milliseconds
config.slow_validation_callback = proc do |schema, data, duration|
Rails.logger.warn "Slow validation: #{schema.name} took #{duration}ms"
# Send to monitoring service
StatsD.increment('json_guard.slow_validation')
end
end
π Debugging
Verbose Error Messages
JsonGuard.configure do |config|
config.detailed_error_messages = true
config.include_error_paths = true
end
Logging
# Enable logging in development
JsonGuard.configure do |config|
config.log_validation_errors = true
config.logger = Rails.logger
end
Error Inspection
validator = JsonGuard::Validator.new(UserSchema)
validator.validate(invalid_data)
# Get detailed error information
validator.errors.each do |error|
puts "Field: #{error.field}"
puts "Message: #{error.message}"
puts "Code: #{error.code}"
puts "Path: #{error.path}"
puts "Value: #{error.value}"
end
π Examples
Comprehensive examples are available in the examples directory:
- Basic Usage - Get started with core concepts
- User Profile - Real-world nested validation
- Custom Messages - User-friendly error messages
- Rails Integration - ActiveRecord integration
- API Controllers - API validation patterns
- Context-Aware - Dynamic validation rules
- E-commerce Schema - Complex business schemas
- Advanced Features - Custom validators
- Performance - Optimization techniques
Each example is fully documented with explanations and test cases.
π€ Contributing
Welcome contributions! Please see Contributing Guide for details.
Development Setup
git clone https://github.com/zaid-4/json-guard.git
cd json-guard
bundle install
Running Tests
# Run all tests
bundle exec rspec
# Run specific test file
bundle exec rspec spec/json_guard/schema_spec.rb
# Run with coverage
bundle exec rspec --format documentation
Code Style
RuboCop is being used for code style enforcement:
# Check code style
bundle exec rubocop
# Auto-fix issues
bundle exec rubocop -a
π Changelog
See CHANGELOG.md for details about changes in each version.
π License
The gem is available as open source under the terms of the MIT License.
π₯ Credits
Created and maintained by Zaid Saeed.
π Support
- π Documentation
- π Issues
- π¬ Discussions
- π§ Email
If you find this gem useful, please consider giving it a βοΈ on GitHub!