Low commit activity in last 3 years
Define typed hash schemas with per-key type declarations, optional coercion functions, default values, strict mode for unknown keys, nested schemas, pick/omit, JSON serialization, freeze, diff, and validation error collection.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-typed_hash

Tests Gem Version Last updated

Hash with per-key type declarations, coercion, validation, nested schemas, and JSON serialization

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-typed_hash"

Or install directly:

gem install philiprehberger-typed_hash

Usage

require "philiprehberger/typed_hash"

UserSchema = Philiprehberger::TypedHash.define do
  key :name, String
  key :age, Integer
  key :email, String
end

user = UserSchema.new(name: 'Alice', age: 30, email: 'alice@example.com')
user[:name]   # => 'Alice'
user.valid?   # => true

Default Values

schema = Philiprehberger::TypedHash.define do
  key :name, String
  key :role, String, default: 'user'
end

instance = schema.new(name: 'Alice')
instance[:role]  # => 'user'

Optional Keys

schema = Philiprehberger::TypedHash.define do
  key :name, String
  key :nickname, String, optional: true
end

schema.new(name: 'Alice').valid?  # => true

Coercion

schema = Philiprehberger::TypedHash.define do
  key :count, Integer, coerce: ->(v) { Integer(v) }
  key :active, TrueClass, coerce: ->(v) { v == 'true' }
end

instance = schema.new(count: '42', active: 'true')
instance[:count]  # => 42

Strict Mode

schema = Philiprehberger::TypedHash.define(strict: true) do
  key :name, String
end

instance = schema.new(name: 'Alice', extra: 'value')
instance.valid?   # => false
instance.errors   # => ['unknown key: extra']

Nested Schemas

schema = Philiprehberger::TypedHash.define do
  key :name, String
  nested :address do
    key :street, String
    key :city, String
  end
end

user = schema.new(name: 'Alice', address: { street: '123 Main St', city: 'Springfield' })
user[:address][:street]  # => '123 Main St'

Pick and Omit

full = schema.new(name: 'Alice', age: 30, email: 'alice@example.com')
picked = full.pick(:name, :email)   # only :name and :email
omitted = full.omit(:email)         # everything except :email

JSON Serialization

instance = schema.new(name: 'Alice', age: 30)
json = instance.to_json              # => '{"name":"Alice","age":30}'
restored = schema.from_json(json)    # => Instance

Freeze

instance = schema.new(name: 'Alice', age: 30)
instance.freeze
instance[:name] = 'Bob'  # => raises Philiprehberger::TypedHash::FrozenError

Diff

a = schema.new(name: 'Alice', age: 30)
b = schema.new(name: 'Alice', age: 35)
a.diff(b)  # => { age: { old: 30, new: 35 } }

Merging

base = schema.new(name: 'Alice', age: 25)
updated = base.merge(age: 30)
updated[:age]  # => 30

Key Membership

schema = Philiprehberger::TypedHash::Schema.new
schema.key(:name, String)
schema.key(:age, Integer, optional: true)

instance = schema.new(name: 'Alice')
instance.key?(:name)  # => true
instance.key?('name') # => true (string form works)
instance.key?(:age)   # => false
instance.key?(:foo)   # => false

Schema introspection

schema = Philiprehberger::TypedHash.define do
  key :name, String
  nested :address do
    key :street, String
  end
  key :age, Integer
end

schema.keys  # => [:name, :address, :age]

Schema#keys returns the declared top-level key names in definition order. Nested schemas contribute only their parent key — inner fields are not included.

API

TypedHash

Method Description
.define(strict:) { } Define a schema with a block DSL

Schema

Method Description
key :name, Type, opts Declare a typed key with options
nested :name, opts, &block Define a nested typed hash schema
#new(data) Create a typed hash instance
#from_json(str) Deserialize a JSON string into a typed hash instance
#keys Return the declared top-level key names in definition order

Instance

Method Description
#[key] Access a value by key
#[key] = value Set a value by key (raises if frozen)
#key?(key) True when key is present in the instance data (accepts Symbol or String)
#valid? Check if the instance passes validation
#errors Return validation error messages
#to_h Convert to a plain hash
#to_json Serialize to a JSON string
#merge(other) Merge with another hash or instance
#pick(*keys) Return new instance with only the specified keys
#omit(*keys) Return new instance without the specified keys
#freeze Make the instance immutable
#frozen? Check if the instance is frozen
#diff(other) Return hash of changed keys with old and new values

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT