Open-source project
kgiszczak/shale avatar
kgiszczak/shale

Shale: a Ruby object mapper that spans six data formats

Shale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML. It allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures, as well as serialize data structures into JSON, YAML, TOML, CSV or XML.

672 stars26 forksRubyMIT

At a glance

What is it?
Shale is a MIT licensed Ruby library that maps one object model onto JSON, YAML, TOML, CSV, XML and x-www-form-urlencoded in both directions, and can generate or compile JSON and XML Schema. The design is deliberately schema-first, and TOML is the outlier because it needs an adapter chosen before it works.
Who is it for?
Shale fits a Ruby team that wants one object model driving several serialisation formats, including the awkward ones like CSV and XML, and that would rather declare attributes in a class than write a converter per format.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 86 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One model, six formats, both directions

Shale is described as a Ruby object mapper and serializer for JSON, YAML, TOML, CSV, XML and x-www-form-urlencoded, and the feature list is symmetric: convert each of those formats to a Ruby data model, and convert a Ruby data model to each of them. That symmetry is the whole point. The alternative in a typical Ruby service is one gem per format, and a hand-written conversion layer that has to be kept in step with each of them. Shale instead asks you to describe the shape of your data once, as a class, and then derives the format-specific behaviour from that description. The formats are not all first class in the same way, and the README is honest about that: out of the box there is support for JSON, YAML, Tomlib, toml-rb, CSV, Nokogiri, REXML, Ox and the Rack x-www-form-urlencoded parsers, plus support for custom adapters. The audience is Ruby developers with data that has to travel in more than one shape, and the repository topics match that list, with json-mapping, yaml-mapping, toml-mapping, csv-mapping and xml-mapping variants of the same idea.

Attributes declare the schema, and Shale fills in the rest

The model is defined with a class that inherits from Shale::Mapper and declares attributes with a type, and optional modifiers.

ruby
require 'shale'

class Address < Shale::Mapper
  attribute :city, :string
  attribute :street, :string
  attribute :zip, :string
end

class Person < Shale::Mapper
  attribute :first_name, :string
  attribute :last_name, :string
  attribute :age, :integer
  attribute :married, :boolean, default: -> { false }
  attribute :hobbies, :string, collection: true
  attribute :address, Address
end

Two modifiers do most of the work. A default value is given as a proc that returns the value, so attribute :married, :boolean, default: -> { false } means the attribute exists even when the incoming data omits it. The collection flag marks an attribute as a list rather than a single value, which is what lets a single hobbies array survive a round trip through CSV, where there is no native list type. A nested mapper is declared by passing the class itself, as with attribute :address, Address, and Shale then knows to build that object recursively. Types are declared as symbols such as :string, :integer and :boolean, and the README has a supported types section and a section on writing your own type for anything outside that set.

Parsing and serializing are class methods on the mapper

Once the class exists, each format gets a from_ and a to_ method on it, and the parsing side accepts the document as a heredoc string.

ruby
person = Person.from_json(<<~DATA)
{
  "first_name": "John",
  "last_name": "Doe",
  "age": 50,
  "married": false,
  "hobbies": ["Singing", "Dancing"],
  "address": {
    "city": "London",
    "street": "Oxford Street",
    "zip": "E1 6AN"
  }
}
DATA

The output direction is a plain method call on the instance, and to_json returns the document as a string.

ruby
person.to_json

YAML works the same way through from_yaml and to_yaml, and TOML through from_toml and to_toml, and XML through its own pair, and urlencoded through its own pair, and a Hash through from_hash and to_hash. The README also documents sections for converting collections, mapping keys or columns to object attributes per format, using XML namespaces, rendering nil values, using methods to extract and generate data, delegating fields to child attributes, additional options, overriding attribute methods, and using custom models. That list is the practical surface of the library: it is not a general-purpose mapping framework, it is a set of documented conventions for how one model meets six formats.

TOML is the one format you must configure first

TOML is handled differently from everything else, and this is the detail that catches people out. The README says that to use TOML with Shale you have to set the adapter you want to use, and that Shale comes with adapters for Tomlib and toml-rb. The Tomlib gem has to be installed first.

bash
$ gem install tomlib

Then the adapter is assigned on the module, either with Tomlib or with the toml-rb backend.

ruby
Shale.toml_adapter = Shale::Adapter::Tomlib
ruby
Shale.toml_adapter = Shale::Adapter::TomlRB

Once that is done, from_toml and to_toml behave like the other formats. The reason for the design is visible in the file layout: Shale does not bundle a TOML parser, so the dependency is yours to choose, and the choice is global rather than per object. Two consequences follow. Your Gemfile has to carry the TOML gem, and if an application uses two TOML backends in different parts, they are on the same global setting. The README's own example line for requiring the adapter carries a typo in the path, so check the require line against the gem name rather than copying it blindly.

Schema generation and schema compilation are the underrated half

Beyond mapping, Shale can generate JSON and XML Schema from Ruby models, and can compile JSON and XML Schema into Ruby models. That is a different capability from serialization, and it is the part that changes how a team works. Generating a schema from the model means the contract with other services is derived from the same declaration that drives the parsing, so the two cannot drift apart silently. Compiling a schema into a model goes the other way and lets you start from a contract someone else publishes, which is how a service that has to accept a third party's format avoids hand-writing an attribute list. The README has separate sections for generating JSON Schema, compiling JSON Schema into a Shale model, generating XML Schema and compiling XML Schema into a Shale model. For a team maintaining an API, this is the argument for choosing Shale over a set of format-specific gems, since the schema is not a separate artefact to maintain.

Installing the gem and parsing your first document

Shale supports Ruby (MRI) 3.0 and newer, and installation is a Gemfile entry followed by a bundle install.

ruby
gem 'shale'
bash
$ bundle install

Installing it outside a bundle is a single gem command.

bash
$ gem install shale

From there the workflow is the same for every format. Define your mapper class, parse the document with the from_ method for that format, and let Shale handle the type conversion and the nested structure. The repository layout backs this up: lib/ holds the library, spec/ the specs with a .rspec file and .rubocop.yml for style, and a benchmarks/ directory sits at the top level next to bin/, exe/, the Rakefile, the Gemfile and the shale.gemspec. The presence of both a benchmarks directory and a rubocop configuration tells you the project treats performance and style as part of the contract, though the README does not publish any benchmark numbers.

What the format coverage does not tell you

Two gaps are worth naming before you commit. The first is that the mapping is structural, not semantic. Shale will put a string into a :string attribute and an integer into an :integer attribute, and it will report a problem when a value does not fit, but the README does not describe a validation layer, a coercion policy for a JSON number arriving where a string was expected, or a set of custom validators on attributes. The documented extensions are types, adapters, and schema generation, which is a different list. If your requirement is that incoming data is checked against a contract, that is the JSON Schema path rather than the mapper path, and it is a separate step.

The second is CSV. A flat, text-only format has no way to express a nested object or a typed list, so the mapping has to flatten or encode it, and the README's section on mapping CSV columns to object attributes and on collections is where that behaviour is described, but the format itself forces compromises that a JSON or YAML payload never has to make. If your data is mostly flat and tabular, CSV support is a convenience. If it is deeply nested, the effort belongs in JSON or YAML, and Shale's value there is that the same model serves both.

Editorial conclusion

Shale fits a Ruby team that wants one object model driving several serialisation formats, including the awkward ones like CSV and XML, and that would rather declare attributes in a class than write a converter per format. It does not fit a project that needs TOML without extra setup, since Shale ships no TOML parser of its own and you must install Tomlib or toml-rb and assign the adapter, and it does not fit a team that wants the mapping to be validated against a live schema at runtime, because validation is not something the README describes. Before you adopt it, read the Adapters section and decide your TOML backend, and check the release history, since v1.2.2 dates from 2025-03-13 and the last push was on 2026-07-29.

Frequently asked questions

What formats does Shale support?

JSON, YAML, TOML, CSV, XML and x-www-form-urlencoded, in both directions: each format can be parsed into a Ruby data model, and a Ruby data model can be serialized back out to it. The README also lists support for custom adapters.

How do I install Shale?

Add gem 'shale' to the Gemfile and run bundle install, or install it directly with gem install shale. Shale supports Ruby (MRI) 3.0 and newer.

Why does Shale need an adapter for TOML?

Shale does not bundle a TOML parser, so you install a backend such as Tomlib or toml-rb and assign the adapter with Shale.toml_adapter, either Shale::Adapter::Tomlib or Shale::Adapter::TomlRB. The setting is global, so the gem choice affects the whole application.

Can Shale generate a schema from my Ruby classes?

Yes. The README documents generating JSON Schema and XML Schema from Ruby models, and separately compiling JSON Schema or XML Schema into a Shale model, so you can start from a published contract instead of hand-writing attribute lists.

How do I declare a default value or a list attribute in Shale?

A default is passed as a proc that returns the value, for example attribute :married, :boolean, default: -> { false }, and a list is marked with collection: true, for example attribute :hobbies, :string, collection: true.

Official sources

  1. Issues
  2. kgiszczak/shale on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kgiszczak-shale.svg)](https://hysenlabs.com/projects/kgiszczak-shale)

Was this useful?