JSON Model
A Ruby DSL for building JSON Schema definitions with a clean, declarative syntax. Define your schemas in Ruby and generate complete, standards-compliant JSON Schema documents.
Installation
Add this line to your application's Gemfile:
gem 'json_model'And then execute:
bundle installOr install it yourself as:
gem install json_modelQuick Start
require 'json_model'
class User
include JsonModel::Schema
schema_id "https://example.com/schemas/user.json"
title "User"
description "A registered user in the system"
property :name, type: String
property :email, type: String, format: :email
property :age, type: Integer, minimum: 0, maximum: 120, optional: true
end
# Generate the JSON Schema
puts JSON.pretty_generate(User.as_schema)Output:
{
"$id": "https://example.com/schemas/user.json",
"additionalProperties": false,
"title": "User",
"description": "A registered user in the system",
"properties": {
"age": {
"type": "integer",
"minimum": 0,
"maximum": 120
},
"email": {
"type": "string",
"format": "email"
},
"name": {
"type": "string"
}
},
"required": [
{
"json_class": "Symbol",
"s": "email"
},
{
"json_class": "Symbol",
"s": "name"
}
],
"type": "object"
}Schema Metadata
You can set top-level schema metadata properties directly in your schema class:
class Product
include JsonModel::Schema
# Schema metadata
schema_id "https://api.example.com/schemas/product.json"
schema_version :draft_2020_12
title "Product"
description "A product available in the catalog"
# Properties
property :id, type: String
property :name, type: String
property :price, type: T::Float[minimum: 0]
property :available, type: T::Boolean, default: true, optional: true
endAvailable Metadata Keywords
-
schema_id- Sets the$id(unique URI identifier for the schema) -
schema_version- Sets the$schema(JSON Schema version) -
title- Human-readable title for the schema -
description- Detailed explanation of the schema's purpose -
additional_properties- Whether additional properties are allowed (default:false)
Data Types
String Type
class StringExample
include JsonModel::Schema
# Basic string
property :simple_string, type: String
# String with length constraints
property :username, type: T::String[min_length: 3, max_length: 20]
# String with pattern (regex)
property :product_code, type: T::String[pattern: /\A[A-Z]{3}-\d{4}\z/]
# String with format
property :email, type: T::String[format: :email]
property :uri, type: T::String[format: :uri]
property :hostname, type: T::String[format: :hostname]
property :ipv4, type: T::String[format: :ipv4]
property :ipv6, type: T::String[format: :ipv6]
property :uuid, type: T::String[format: :uuid]
property :date, type: T::String[format: :date]
property :time, type: T::String[format: :time]
property :datetime, type: T::String[format: :date_time]
property :duration, type: T::String[format: :duration]
# String with enum
property :status, T::Enum["draft", "published", "archived"]
# String with const
property :api_version, T::Const["v1"]
# Optional string
property :nickname, type: String, optional: true
end
# Generate the JSON Schema
puts JSON.pretty_generate(StringExample.as_schema)Output:
{
"additionalProperties": false,
"properties": {
"date": {
"type": "string",
"format": "date"
},
"datetime": {
"type": "string",
"format": "date-time"
},
"duration": {
"type": "string",
"format": "duration"
},
"email": {
"type": "string",
"format": "email"
},
"hostname": {
"type": "string",
"format": "hostname"
},
"ipv4": {
"type": "string",
"format": "ipv4"
},
"ipv6": {
"type": "string",
"format": "ipv6"
},
"product_code": {
"type": "string",
"pattern": "\\A[A-Z]{3}-\\d{4}\\z"
},
"simple_string": {
"type": "string"
},
"time": {
"type": "string",
"format": "time"
},
"uri": {
"type": "string",
"format": "uri"
},
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20
},
"uuid": {
"type": "string",
"format": "uuid"
}
},
"required": [
"date",
"datetime",
"duration",
"email",
"hostname",
"ipv4",
"ipv6",
"product_code",
"simple_string",
"time",
"uri",
"username",
"uuid"
],
"type": "object"
}Number and Integer Types
class NumericExample
include JsonModel::Schema
# Integer
property :count, type: Integer
# Integer with range
property :port, type: T::Integer[minimum: 1024, maximum: 65535]
# Integer with exclusive bounds
property :positive_int, type: T::Integer[exclusive_minimum: 0]
# Number (float/double)
property :price, type: T::Number[minimum: 0]
# Number with multiple_of
property :quantity, type: T::Integer[multiple_of: 10]
# Number with precision
property :temperature, type: T::Number[minimum: -273.15, maximum: 1000.0]
# Optional number
property :discount, type: Float, optional: true
end
# Generate the JSON Schema
puts JSON.pretty_generate(NumericExample.as_schema)Output:
{
"additionalProperties": false,
"properties": {
"count": {
"type": "integer"
},
"discount": {
"type": "number"
},
"port": {
"type": "integer",
"minimum": 1024,
"maximum": 65535
},
"positive_int": {
"type": "integer",
"exclusiveMinimum": 0
},
"price": {
"type": "number",
"minimum": 0
},
"quantity": {
"type": "integer",
"multipleOf": 10
},
"temperature": {
"type": "number",
"minimum": -273.15,
"maximum": 1000.0
}
},
"required": [
"count",
"port",
"positive_int",
"price",
"quantity",
"temperature"
],
"type": "object"
}Boolean Type
class BooleanExample
include JsonModel::Schema
property :is_active, type: T::Boolean
property :has_agreed, type: T::Boolean, default: false
property :enabled, type: T::Boolean, optional: true
end
# Generate the JSON Schema
puts JSON.pretty_generate(BooleanExample.as_schema)Output:
{
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"has_agreed": {
"type": "boolean",
"default": false
},
"is_active": {
"type": "boolean"
}
},
"required": [
"has_agreed",
"is_active"
],
"type": "object"
}Array Type
class ArrayExample
include JsonModel::Schema
# Simple array
property :tags, type: T::Array[String]
# Array with constraints
property :numbers, type: T::Array[Integer, min_items: 1, max_items: 10, unique_items: true]
end
# Generate the JSON Schema
puts JSON.pretty_generate(ArrayExample.as_schema)Output:
{
"additionalProperties": false,
"properties": {
"numbers": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"numbers",
"tags"
],
"type": "object"
}Schema Composition
JSON Model supports powerful schema composition using T::AllOf, T::AnyOf, and T::OneOf:
AllOf - Must Match All Schemas
Use T::AllOf when a value must validate against all provided schemas (intersection/combining schemas):
class PersonBase
include JsonModel::Schema
property :name, type: String
property :age, type: T::Integer[minimum: 0], optional: true
end
class EmployeeDetails
include JsonModel::Schema
property :employee_id, type: T::String[pattern: /\AE-\d{4}\z/]
property :department, type: String
property :salary, type: T::Number[minimum: 0], optional: true
end
class Employee
include JsonModel::Schema
title "Employee"
description "Combines person and employee-specific properties"
property :employee, type: T::AllOf[PersonBase, EmployeeDetails]
end
# Generate the JSON Schema
puts JSON.pretty_generate(Employee.as_schema)Output:
{
"additionalProperties": false,
"title": "Employee",
"description": "Combines person and employee-specific properties",
"properties": {
"employee": {
"allOf": [
{
"additionalProperties": false,
"properties": {
"age": {
"type": "integer",
"minimum": 0
},
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"department": {
"type": "string"
},
"employee_id": {
"type": "string",
"pattern": "\\AE-\\d{4}\\z"
},
"salary": {
"type": "number",
"minimum": 0
}
},
"required": [
"department",
"employee_id"
],
"type": "object"
}
]
}
},
"required": [
"employee"
],
"type": "object"
}AnyOf - Must Match At Least One Schema
Use T::AnyOf when a value must validate against one or more schemas (union/alternatives):
class EmailContact
include JsonModel::Schema
property :email, type: String, format: :email
end
class PhoneContact
include JsonModel::Schema
property :phone, type: T::String[pattern: /\A\+?[1-9]\\d{1,14}\z/]
end
class AddressContact
include JsonModel::Schema
property :street, type: String
property :city, type: String
end
class Contact
include JsonModel::Schema
title "Contact Method"
description "Must provide at least one contact method"
property :contact, type: T::AnyOf[EmailContact, PhoneContact, AddressContact]
end
# Generate the JSON Schema
puts JSON.pretty_generate(Contact.as_schema)Output:
{
"additionalProperties": false,
"title": "Contact Method",
"description": "Must provide at least one contact method",
"properties": {
"contact": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"email": {
"type": "string",
"format": "email"
}
},
"required": [
"email"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"phone": {
"type": "string",
"pattern": "\\A\\+?[1-9]\\\\d{1,14}\\z"
}
},
"required": [
"phone"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"city": {
"type": "string"
},
"street": {
"type": "string"
}
},
"required": [
"city",
"street"
],
"type": "object"
}
]
}
},
"required": [
"contact"
],
"type": "object"
}OneOf - Must Match Exactly One Schema
Use T::OneOf when a value must validate against exactly one schema (exclusive alternatives):
class CreditCardPayment
include JsonModel::Schema
property :payment_type, T::Const["credit_card"]
property :card_number, type: T::String[pattern: /\A\d{16}\z/]
property :cvv, type: T::String[pattern: /\A\d{3,4}\z/]
property :expiry, type: T::String[pattern: /\A\d{2}\/\d{2}\z/]
end
class PayPalPayment
include JsonModel::Schema
property :payment_type, T::Const["paypal"]
property :paypal_email, type: T::String[format: :email]
end
class BankTransferPayment
include JsonModel::Schema
property :payment_type, type: T::Const["bank_transfer"]
property :iban, type: T::String[pattern: "^[A-Z]{2}\\d{2}[A-Z0-9]+$"]
property :swift, type: String, optional: true
end
class PaymentMethod
include JsonModel::Schema
title "Payment Method"
description "Must specify exactly one payment method"
property :payment, type: T::OneOf[CreditCardPayment, PayPalPayment, BankTransferPayment], discriminator: :payment_type
end
# Generate the JSON Schema
puts JSON.pretty_generate(PaymentMethod.as_schema)Output:
{
"additionalProperties": false,
"title": "Payment Method",
"description": "Must specify exactly one payment method",
"properties": {
"payment": {
"oneOf": [
{
"additionalProperties": false,
"type": "object"
},
{
"additionalProperties": false,
"type": "object"
},
{
"additionalProperties": false,
"type": "object"
}
]
}
},
"required": [
"payment"
],
"type": "object"
}Use Cases
- API Documentation: Generate JSON schemas for API request/response validation
- Configuration Files: Define and validate application configuration schemas
- Data Validation: Validate incoming data against defined schemas
- Code Generation: Use schemas to generate code in other languages
- OpenAPI/Swagger: Generate OpenAPI schema definitions for your APIs
- Form Generation: Generate forms from schema definitions
Resources
License
The gem is available as open source under the terms of the MIT License.
Credits
Developed and maintained by gillesbergerp.