ruby/json parses and generates JSON under strict control
JSON implementation for Ruby
At a glance
- What is it?
- The json gem is Ruby's own JSON implementation, following RFC 7159, with a coder API, strict mode and fragment splicing for people who need more control than JSON.generate gives. It is a good default for most Ruby services and a poor fit for anyone who needs the library to stay silent about non-native objects.
- Who is it for?
- Adopt ruby/json if your service parses untrusted JSON, generates API responses with custom types, or caches rendered JSON fragments, since the coder, strict mode and fragment APIs cover exactly those cases. Do not adopt it for a project that expects to_json to be a safe default everywhere, because the fallback to #to_s is global and surprising, and switch to strict: true there.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 3 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 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A parser written for RFC 7159, shipped with Ruby itself
The json gem is the JSON implementation that ships inside Ruby, and it exists as a separate repository because it can be updated without a new Ruby release. The project states that it implements the JSON specification of RFC 7159, and links the RFC alongside. That framing matters: this is not a thin wrapper over another language's parser, it is the reference-ish implementation that other Ruby code is expected to agree with.
The library covers the full round trip. JSON.parse turns a document into a Ruby data structure, JSON.generate turns a Ruby data structure into a document, and JSON.pretty_generate does the same with human readable formatting. The generator emits UTF-8 character sequences by default. Pass an ascii_only option set to true and it escapes all non-ASCII and control characters with \uXXXX sequences, and it supports UTF-16 surrogate pairs so the whole range of unicode code points can be represented. On the Ruby side, anything encoded as a JSON string is expected to be a UTF-8 byte sequence.
The repository layout matches a real C extension: there is an ext/ directory, a java/ directory, a benchmark/ directory and a references/ directory, alongside lib/, test/, json.gemspec and a Rakefile.
bundle add json gets you a require and two entry points
Installation is a one-liner. With bundler managing dependencies, the gem is added to the application's Gemfile by executing:
$ bundle add jsonIf bundler is not in use, the gem can be installed on its own:
$ gem install jsonFrom there the library is a normal require. Once JSON is loaded you can parse a document into a Ruby data structure:
require 'json'
JSON.parse(document)and generate a document from a data structure:
JSON.generate(data)Two documentation locations are given: the online API documentation at docs.ruby-lang.org, and the per-version API pages at rubydoc.info. The gem itself is downloaded from rubygems.org. Everything here is part of the standard library path, which is the point of the project. There is no separate configuration file, no initializer and no environment variable to set.
to_json is global, so JSON::Coder exists to localise the rules
JSON documents can only support hashes, arrays, strings, integers and floats. By default, when you ask JSON.generate to serialize something else, it searches for a #to_json method on that object. A Struct can define one:
Position = Struct.new(:latitude, :longitude) do
def to_json(state = nil, *)
JSON::State.from_state(state).generate({
latitude: latitude,
longitude: longitude,
})
end
endIf no #to_json is defined, the generator falls back to calling #to_s, which is how JSON.generate(Object.new) ends up producing a string like "#<Object:0x000000011e768b98>". That fallback is the kind of quiet behaviour that ships an internal object identifier into an API response.
Because #to_json methods are global, the newer JSON::Coder API covers cases where a given type has to serialize differently in different places:
module MyApp
API_JSON_CODER = JSON::Coder.new do |object, is_object_key|
case object
when Time
object.iso8601(3)
else
object
end
end
end
puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"The block receives the object and a flag telling you whether the value is being used as a hash key. It is called for objects with no native JSON equivalent, and also for objects that do have one but sit in hash key position, such as { 1 => 2 }, and for strings that are not valid UTF-8, where the coder above falls back to Base64 encoding.
strict: true turns silent coercion into a GeneratorError
Both fallback behaviours, the custom to_json method and the to_s fallback, can be switched off with the strict: true option. Under strict mode the generator refuses anything without a native JSON equivalent and raises JSON::GeneratorError:
JSON.generate(Object.new, strict: true) # => Object not allowed in JSON (JSON::GeneratorError)
JSON.generate(Position.new(1, 2), strict: true) # => Position not allowed in JSON (JSON::GeneratorError)The second line is worth pausing on, because it shows the cost of strict mode. A type that has opted in through a well-behaved #to_json method, like the Position struct above, is still rejected once strict mode is on, because the strict path does not consult the to_json hook. The rule is that JSON documents can only support hashes, arrays, strings, integers and floats, and strict mode holds you to exactly that list rather than letting your own extensions in.
That makes strict mode a good default for a public API where the response shape is a contract, and a poor fit for an internal service that relies on custom types. If you want both, JSON::Coder is the path, since the block it takes is the place where you decide what a Time or a Symbol becomes.
Note that this strictness applies to generation. The README does not describe a matching strict option for parsing.
JSON::Fragment splices cached documents without reparsing them
A common server-side pattern is to cache rendered JSON per record and then assemble a larger document at request time. Re-parsing each cached fragment just to embed it is wasteful, and JSON::Fragment exists for exactly this. Cached post fragments can be collected and then embedded:
posts_json = cache.fetch_multi(post_ids) do |post_id|
JSON.generate(Post.find(post_id))
end
posts_json.map! { |post_json| JSON::Fragment.new(post_json) }
JSON.generate({ posts: posts_json, count: posts_json.count })The fragment holds an already valid JSON string, so the outer generate writes it into the result without decoding it first. The cost of that approach is that the cached fragment has to be valid JSON at the time it is created, since nothing re-checks it later. A cached fragment generated with the loose to_s fallback would be embedded as-is, which is a good reason to combine fragment caching with strict generation rather than treating them as separate features.
pretty_generate expands Range into a json_class object
JSON.generate always produces the shortest possible string representation on one line, which suits storage and network protocols but not human eyes. JSON.pretty_generate formats the same data more readably:
puts JSON.pretty_generate([1, 2, {"a"=>3.141}, false, true, nil, 4..10])The output is not simply the same structure indented. The range 4..10 is expanded into an object with a json_class key and a data array:
{
"json_class": "Range",
"data": [
4,
10,
false
]
}That is a useful reminder for anyone planning to pretty print a payload and then feed it to another system: Range is not a JSON type, so the library encodes it as a tagged object that a plain JSON parser on the other side will read as unknown data rather than as a range.
Parser and generator options must never come from user input
The security guidance is short but pointed. When parsing or serializing untrusted input, parser and generator options should never be user controlled. The example given is deliberately labelled dangerous:
# Dangerous, DO NOT DO THIS.
JSON.generate(params[:data], params[:options])The danger is that the options argument can change how the generator behaves, including the strict mode and the ascii_only escaping described above, so a caller who controls that hash controls the semantics of your output. The reporting policy is explicit too: security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes, which tells you the project considers this a usage error on the caller's side rather than a defect to be patched here.
For a service that accepts JSON from clients, the practical reading is that the second argument of JSON.parse and JSON.generate must be a literal or a value your code controls, never params[:options] or anything derived from the request.
Three releases in 2026 and a licence that is not MIT
The maintenance picture is healthy on the release side. The release history lists v3.0.0 on 2026-09-07, a v3.0.0.rc1 candidate on 2026-08-11, and v2.21.2 on 2026-07-31, which is a substantial change in the major version inside one year. The last push was on 2026-10-19.
The licence deserves attention because it is not what most Ruby gems use. The project points to the Ruby Licence at ruby-lang.org, and the repository carries BSDL, COPYING and LEGAL files rather than a single LICENSE file, which is why GitHub cannot classify it. Any team with a licence scanner in its dependency pipeline will need a manual exception for this gem.
Contributing to it means working in the repository itself. The development instructions are to clone the repository, install dependencies with bundle install, and run the full test suite with:
bundle exec rake testReleases update lib/json/version.rb and then build and push the gem, with separate builds for MRI and for JRuby, so the two runtimes are versioned and published separately.
Editorial conclusion
Adopt ruby/json if your service parses untrusted JSON, generates API responses with custom types, or caches rendered JSON fragments, since the coder, strict mode and fragment APIs cover exactly those cases. Do not adopt it for a project that expects to_json to be a safe default everywhere, because the fallback to #to_s is global and surprising, and switch to strict: true there. Before you commit, verify how your own types respond to the coder block, run bundle exec rake test if you plan to patch the gem, and read the Ruby Licence terms, which differ from the MIT and BSD terms used by most gems.
Frequently asked questions
How do I install the json gem in a Ruby project?
With bundler, the README says to run bundle add json to add it to the application's Gemfile. Without bundler, the gem can be installed with gem install json. In both cases you then require it with require 'json'.
How do I stop JSON.generate from serializing arbitrary objects?
Pass strict: true. The generator then raises JSON::GeneratorError for objects that have no native JSON equivalent instead of falling back to a to_json method or to #to_s.
What is JSON::Coder for in the json gem?
Because #to_json methods are global, JSON::Coder lets you attach serialization rules to one place instead. Its block is called for objects with no native JSON equivalent, for objects used as hash keys, and for strings that are not valid UTF-8.
How do I embed cached JSON in a larger response with ruby/json?
Wrap each cached fragment in JSON::Fragment.new and generate the outer document as usual, as the README shows with cached post documents. The fragment is written into the output without being parsed again.
What licence is the json gem released under?
The README points to the Ruby Licence hosted at ruby-lang.org rather than MIT or BSD, and the repository ships BSDL, COPYING and LEGAL files, which is why GitHub cannot classify the licence automatically.
Official sources
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.
[](https://hysenlabs.com/projects/ruby-json)