module JSON - Documentation for Ruby 3.3 (original) (raw)

JavaScript Object Notation (JSON)

JSON is a lightweight data-interchange format.

A JSON value is one of the following:

A JSON array or object may contain nested arrays, objects, and scalars to any depth:

{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]} [{"foo": 0, "bar": 1}, ["baz", 2]]

Using Module JSON

To make module JSON available in your code, begin with:

require 'json'

All examples here assume that this has been done.

Parsing JSON

You can parse a String containing JSON data using either of two methods:

where

The difference between the two methods is that JSON.parse! omits some checks and may not be safe for some source data; use it only for data from trusted sources. Use the safer method JSON.parse for less trusted sources.

Parsing JSON Arrays

When source is a JSON array, JSON.parse by default returns a Ruby Array:

json = '["foo", 1, 1.0, 2.0e2, true, false, null]' ruby = JSON.parse(json) ruby ruby.class

The JSON array may contain nested arrays, objects, and scalars to any depth:

json = '[{"foo": 0, "bar": 1}, ["baz", 2]]' JSON.parse(json)

Parsing JSON Objects

When the source is a JSON object, JSON.parse by default returns a Ruby Hash:

json = '{"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}' ruby = JSON.parse(json) ruby ruby.class

The JSON object may contain nested arrays, objects, and scalars to any depth:

json = '{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}' JSON.parse(json)

Parsing JSON Scalars

When the source is a JSON scalar (not an array or object), JSON.parse returns a Ruby scalar.

String:

ruby = JSON.parse('"foo"') ruby ruby.class

Integer:

ruby = JSON.parse('1') ruby ruby.class

Float:

ruby = JSON.parse('1.0') ruby ruby.class ruby = JSON.parse('2.0e2') ruby ruby.class

Boolean:

ruby = JSON.parse('true') ruby ruby.class ruby = JSON.parse('false') ruby ruby.class

Null:

ruby = JSON.parse('null') ruby ruby.class

Parsing Options

Input Options

Option max_nesting (Integer) specifies the maximum nesting depth allowed; defaults to 100; specify false to disable depth checking.

With the default, false:

source = '[0, [1, [2, [3]]]]' ruby = JSON.parse(source) ruby

Too deep:

JSON.parse(source, {max_nesting: 1})

Bad value:

JSON.parse(source, {max_nesting: :foo})


Option allow_nan (boolean) specifies whether to allow NaN, Infinity, and MinusInfinity in source; defaults to false.

With the default, false:

JSON.parse('[NaN]')

JSON.parse('[Infinity]')

JSON.parse('[-Infinity]')

Allow:

source = '[NaN, Infinity, -Infinity]' ruby = JSON.parse(source, {allow_nan: true}) ruby

Output Options

Option symbolize_names (boolean) specifies whether returned Hash keys should be Symbols; defaults to false (use Strings).

With the default, false:

source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}' ruby = JSON.parse(source) ruby

Use Symbols:

ruby = JSON.parse(source, {symbolize_names: true}) ruby


Option object_class (Class) specifies the Ruby class to be used for each JSON object; defaults to Hash.

With the default, Hash:

source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}' ruby = JSON.parse(source) ruby.class

Use class OpenStruct:

ruby = JSON.parse(source, {object_class: OpenStruct}) ruby


Option array_class (Class) specifies the Ruby class to be used for each JSON array; defaults to Array.

With the default, Array:

source = '["foo", 1.0, true, false, null]' ruby = JSON.parse(source) ruby.class

Use class Set:

ruby = JSON.parse(source, {array_class: Set}) ruby


Option create_additions (boolean) specifies whether to use JSON additions in parsing. See JSON Additions.

Generating JSON

To generate a Ruby String containing JSON data, use method JSON.generate(source, opts), where

Generating JSON from Arrays

When the source is a Ruby Array, JSON.generate returns a String containing a JSON array:

ruby = [0, 's', :foo] json = JSON.generate(ruby) json

The Ruby Array array may contain nested arrays, hashes, and scalars to any depth:

ruby = [0, [1, 2], {foo: 3, bar: 4}] json = JSON.generate(ruby) json

Generating JSON from Hashes

When the source is a Ruby Hash, JSON.generate returns a String containing a JSON object:

ruby = {foo: 0, bar: 's', baz: :bat} json = JSON.generate(ruby) json

The Ruby Hash array may contain nested arrays, hashes, and scalars to any depth:

ruby = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad} json = JSON.generate(ruby) json

Generating JSON from Other Objects

When the source is neither an Array nor a Hash, the generated JSON data depends on the class of the source.

When the source is a Ruby Integer or Float, JSON.generate returns a String containing a JSON number:

JSON.generate(42) JSON.generate(0.42)

When the source is a Ruby String, JSON.generate returns a String containing a JSON string (with double-quotes):

JSON.generate('A string')

When the source is true, false or nil, JSON.generate returns a String containing the corresponding JSON token:

JSON.generate(true) JSON.generate(false) JSON.generate(nil)

When the source is none of the above, JSON.generate returns a String containing a JSON string representation of the source:

JSON.generate(:foo) JSON.generate(Complex(0, 0)) JSON.generate(Dir.new('.'))

Generating Options

Input Options

Option allow_nan (boolean) specifies whether NaN, Infinity, and -Infinity may be generated; defaults to false.

With the default, false:

JSON.generate(JSON::NaN)

JSON.generate(JSON::Infinity)

JSON.generate(JSON::MinusInfinity)

Allow:

ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity] JSON.generate(ruby, allow_nan: true)


Option max_nesting (Integer) specifies the maximum nesting depth in obj; defaults to 100.

With the default, 100:

obj = [[[[[[0]]]]]] JSON.generate(obj)

Too deep:

JSON.generate(obj, max_nesting: 2)

Escaping Options

Options script_safe (boolean) specifies wether '\u2028', '\u2029' and '/' should be escaped as to make the JSON object safe to interpolate in script tags.

Options ascii_only (boolean) specifies wether all characters outside the ASCII range should be escaped.

Output Options

The default formatting options generate the most compact JSON data, all on one line and with no whitespace.

You can use these formatting options to generate JSON data in a more open format, using whitespace. See also JSON.pretty_generate.

In this example, obj is used first to generate the shortest JSON data (no whitespace), then again with all formatting options specified:

obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}} json = JSON.generate(obj) puts 'Compact:', json opts = { array_nl: "\n", object_nl: "\n", indent: ' ', space_before: ' ', space: ' ' } puts 'Open:', JSON.generate(obj, opts)

Output:

Compact: {"foo":["bar","baz"],"bat":{"bam":0,"bad":1}} Open: { "foo" : [ "bar", "baz" ], "bat" : { "bam" : 0, "bad" : 1 } }

JSON Additions

When you “round trip” a non-String object from Ruby to JSON and back, you have a new String, instead of the object you began with:

ruby0 = Range.new(0, 2) json = JSON.generate(ruby0) json ruby1 = JSON.parse(json) ruby1 ruby1.class

You can use JSON additions to preserve the original object. The addition is an extension of a ruby class, so that:

This example shows a Range being generated into JSON and parsed back into Ruby, both without and with the addition for Range:

ruby = Range.new(0, 2)

json0 = JSON.generate(ruby) ruby0 = JSON.parse(json0)

require 'json/add/range' json1 = JSON.generate(ruby) ruby1 = JSON.parse(json1, create_additions: true)

display = <<EOT Generated JSON: Without addition: #{json0} (#{json0.class}) With addition: #{json1} (#{json1.class}) Parsed JSON: Without addition: #{ruby0.inspect} (#{ruby0.class}) With addition: #{ruby1.inspect} (#{ruby1.class}) EOT puts display

This output shows the different results:

Generated JSON: Without addition: "0..2" (String) With addition: {"json_class":"Range","a":[0,2,false]} (String) Parsed JSON: Without addition: "0..2" (String) With addition: 0..2 (Range)

The JSON module includes additions for certain classes. You can also craft custom additions. See Custom JSON Additions.

Built-in Additions

The JSON module includes additions for certain classes. To use an addition, require its source:

To reduce punctuation clutter, the examples below show the generated JSON via puts, rather than the usual inspect,

BigDecimal:

require 'json/add/bigdecimal' ruby0 = BigDecimal(0) json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Complex:

require 'json/add/complex' ruby0 = Complex(1+0i) json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Date:

require 'json/add/date' ruby0 = Date.today json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

DateTime:

require 'json/add/date_time' ruby0 = DateTime.now json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Exception (and its subclasses including RuntimeError):

require 'json/add/exception' ruby0 = Exception.new('A message') json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class ruby0 = RuntimeError.new('Another message') json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

OpenStruct:

require 'json/add/ostruct' ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Range:

require 'json/add/range' ruby0 = Range.new(0, 2) json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Rational:

require 'json/add/rational' ruby0 = Rational(1, 3) json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Regexp:

require 'json/add/regexp' ruby0 = Regexp.new('foo') json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Set:

require 'json/add/set' ruby0 = Set.new([0, 1, 2]) json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Struct:

require 'json/add/struct' Customer = Struct.new(:name, :address) ruby0 = Customer.new("Dave", "123 Main") json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Symbol:

require 'json/add/symbol' ruby0 = :foo json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Time:

require 'json/add/time' ruby0 = Time.now json = JSON.generate(ruby0) ruby1 = JSON.parse(json, create_additions: true) ruby1.class

Custom JSON Additions

In addition to the JSON additions provided, you can craft JSON additions of your own, either for Ruby built-in classes or for user-defined classes.

Here’s a user-defined class Foo:

class Foo attr_accessor :bar, :baz def initialize(bar, baz) self.bar = bar self.baz = baz end end

Here’s the JSON addition for it:

class Foo

def to_json(*args) { JSON.create_id => self.class.name, 'a' => [ bar, baz ] }.to_json(*args) end

def self.json_create(object) new(*object['a']) end end

Demonstration:

require 'json'

foo0 = Foo.new(0, 1) json0 = JSON.generate(foo0) obj0 = JSON.parse(json0)

require_relative 'foo_addition'

foo1 = Foo.new(0, 1) json1 = JSON.generate(foo1) obj1 = JSON.parse(json1, create_additions: true)

display = <<EOT Generated JSON: Without custom addition: #{json0} (#{json0.class}) With custom addition: #{json1} (#{json1.class}) Parsed JSON: Without custom addition: #{obj0.inspect} (#{obj0.class}) With custom addition: #{obj1.inspect} (#{obj1.class}) EOT puts display

Output:

Generated JSON: Without custom addition: "#Foo:0x0000000006534e80" (String) With custom addition: {"json_class":"Foo","a":[0,1]} (String) Parsed JSON: Without custom addition: "#Foo:0x0000000006534e80" (String) With custom addition: #<Foo:0x0000000006473bb8 @bar=0, @baz=1> (Foo)

Constants

CREATE_ID_TLS_KEY

DEFAULT_CREATE_ID

Infinity

JSON_LOADED

MinusInfinity

NOT_SET

NaN

VERSION

JSON version

Attributes

dump_default_options[RW]

Sets or returns the default options for the JSON.dump method. Initially:

opts = JSON.dump_default_options opts

load_default_options[RW]

Sets or returns default options for the JSON.load method. Initially:

opts = JSON.load_default_options opts

Public Class Methods

If object is a String, calls JSON.parse with object and opts (see method parse):

json = '[0, 1, null]' JSON[json]

Otherwise, calls JSON.generate with object and opts (see method generate):

ruby = [0, 1, nil] JSON[ruby]

def [](object, opts = {}) if object.respond_to? :to_str JSON.parse(object.to_str, opts) else JSON.generate(object, opts) end end

def create_fast_state State.new( :indent => '', :space => '', :object_nl => "", :array_nl => "", :max_nesting => false ) end

Returns the current create identifier. See also JSON.create_id=.

def self.create_id Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID end

Sets create identifier, which is used to decide if the json_create hook of a class should be called; initial value is json_class:

JSON.create_id

def self.create_id=(new_value) Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze end

def create_pretty_state State.new( :indent => ' ', :space => ' ', :object_nl => "\n", :array_nl => "\n" ) end

Encodes string using String.encode.

def self.iconv(to, from, string) string.encode(to, from) end

Public Instance Methods

Dumps obj as a JSON string, i.e. calls generate on the object and returns the result.

The default options can be changed via method JSON.dump_default_options.


When argument io is not given, returns the JSON String generated from obj:

obj = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad} json = JSON.dump(obj) json

When argument io is given, writes the JSON String to io and returns io:

path = 't.json' File.open(path, 'w') do |file| JSON.dump(obj, file) end puts File.read(path)

Output:

{"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}

def dump(obj, anIO = nil, limit = nil, kwargs = nil) io_limit_opt = [anIO, limit, kwargs].compact kwargs = io_limit_opt.pop if io_limit_opt.last.is_a?(Hash) anIO, limit = io_limit_opt if anIO.respond_to?(:to_io) anIO = anIO.to_io elsif limit.nil? && !anIO.respond_to?(:write) anIO, limit = nil, anIO end opts = JSON.dump_default_options opts = opts.merge(:max_nesting => limit) if limit opts = merge_dump_options(opts, **kwargs) if kwargs result = generate(obj, opts) if anIO anIO.write result anIO else result end rescue JSON::NestingError raise ArgumentError, "exceed depth limit" end

Arguments obj and opts here are the same as arguments obj and opts in JSON.generate.

By default, generates JSON data without checking for circular references in obj (option max_nesting set to false, disabled).

Raises an exception if obj contains circular references:

a = []; b = []; a.push(b); b.push(a)

JSON.fast_generate(a)

def fast_generate(obj, opts = nil) if State === opts state = opts else state = JSON.create_fast_state.configure(opts) end state.generate(obj) end

Returns a String containing the generated JSON data.

See also JSON.fast_generate, JSON.pretty_generate.

Argument obj is the Ruby object to be converted to JSON.

Argument opts, if given, contains a Hash of options for the generation. See Generating Options.


When obj is an Array, returns a String containing a JSON array:

obj = ["foo", 1.0, true, false, nil] json = JSON.generate(obj) json

When obj is a Hash, returns a String containing a JSON object:

obj = {foo: 0, bar: 's', baz: :bat} json = JSON.generate(obj) json

For examples of generating from other Ruby objects, see Generating JSON from Other Objects.


Raises an exception if any formatting option is not a String.

Raises an exception if obj contains circular references:

a = []; b = []; a.push(b); b.push(a)

JSON.generate(a)

def generate(obj, opts = nil) if State === opts state = opts else state = State.new(opts) end state.generate(obj) end

Returns the Ruby objects created by parsing the given source.


When no proc is given, modifies source as above and returns the result of parse(source, opts); see parse.

Source for following examples:

source = <<-EOT { "name": "Dave", "age" :40, "hats": [ "Cattleman's", "Panama", "Tophat" ] } EOT

Load a String:

ruby = JSON.load(source) ruby

Load an IO object:

require 'stringio' object = JSON.load(StringIO.new(source)) object

Load a File object:

path = 't.json' File.write(path, source) File.open(path) do |file| JSON.load(file) end


When proc is given:

Example:

require 'json'

class Base def initialize(attributes) @attributes = attributes end end class User < Base; end class Account < Base; end class Admin < Base; end

json = <<-EOF { "users": [ {"type": "User", "username": "jane", "email": "jane@example.com"}, {"type": "User", "username": "john", "email": "john@example.com"} ], "accounts": [ {"account": {"type": "Account", "paid": true, "account_id": "1234"}}, {"account": {"type": "Account", "paid": false, "account_id": "1235"}} ], "admins": {"type": "Admin", "password": "0wn3d"} } EOF

def deserialize_obj(obj, safe_types = %w(User Account Admin)) type = obj.is_a?(Hash) && obj["type"] safe_types.include?(type) ? Object.const_get(type).new(obj) : obj end

ruby = JSON.load(json, proc {|obj| case obj when Hash obj.each {|k, v| obj[k] = deserialize_obj v } when Array obj.map! {|v| deserialize_obj v } end }) pp ruby

Output:

{"users"=> [#<User:0x00000000064c4c98 @attributes= {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>, #<User:0x00000000064c4bd0 @attributes= {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>], "accounts"=> [{"account"=> #<Account:0x00000000064c4928 @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>}, {"account"=> #<Account:0x00000000064c4680 @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}], "admins"=> #<Admin:0x00000000064c41f8 @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}

def load(source, proc = nil, options = {}) opts = load_default_options.merge options if source.respond_to? :to_str source = source.to_str elsif source.respond_to? :to_io source = source.to_io.read elsif source.respond_to?(:read) source = source.read end if opts[:allow_blank] && (source.nil? || source.empty?) source = 'null' end result = parse(source, opts) recurse_proc(result, &proc) if proc result end

Calls:

parse(File.read(path), opts)

See method parse.

def load_file(filespec, opts = {}) parse(File.read(filespec), opts) end

Calls:

JSON.parse!(File.read(path, opts))

See method parse!

def load_file!(filespec, opts = {}) parse!(File.read(filespec), opts) end

def merge_dump_options(opts, strict: NOT_SET) opts = opts.merge(strict: strict) if NOT_SET != strict opts end

Returns the Ruby objects created by parsing the given source.

Argument source contains the String to be parsed.

Argument opts, if given, contains a Hash of options for the parsing. See Parsing Options.


When source is a JSON array, returns a Ruby Array:

source = '["foo", 1.0, true, false, null]' ruby = JSON.parse(source) ruby ruby.class

When source is a JSON object, returns a Ruby Hash:

source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}' ruby = JSON.parse(source) ruby ruby.class

For examples of parsing for all JSON data types, see Parsing JSON.

Parses nested JSON objects:

source = <<-EOT { "name": "Dave", "age" :40, "hats": [ "Cattleman's", "Panama", "Tophat" ] } EOT ruby = JSON.parse(source) ruby


Raises an exception if source is not valid JSON:

JSON.parse('')

def parse(source, opts = {}) Parser.new(source, **(opts||{})).parse end

Calls

parse(source, opts)

with source and possibly modified opts.

Differences from JSON.parse:

def parse!(source, opts = {}) opts = { :max_nesting => false, :allow_nan => true }.merge(opts) Parser.new(source, **(opts||{})).parse end

Arguments obj and opts here are the same as arguments obj and opts in JSON.generate.

Default options are:

{ indent: ' ',
space: ' ',
array_nl: "\n", object_nl: "\n" }

Example:

obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}} json = JSON.pretty_generate(obj) puts json

Output:

{ "foo": [ "bar", "baz" ], "bat": { "bam": 0, "bad": 1 } }

def pretty_generate(obj, opts = nil) if State === opts state, opts = opts, nil else state = JSON.create_pretty_state end if opts if opts.respond_to? :to_hash opts = opts.to_hash elsif opts.respond_to? :to_h opts = opts.to_h else raise TypeError, "can't convert #{opts.class} into Hash" end state.configure(opts) end state.generate(obj) end

Private Instance Methods