diff options
Diffstat (limited to 'ext/json/lib/json.rb')
-rw-r--r-- | ext/json/lib/json.rb | 441 |
1 files changed, 379 insertions, 62 deletions
diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb index 151d9c2f26..a96f4bc612 100644 --- a/ext/json/lib/json.rb +++ b/ext/json/lib/json.rb @@ -2,88 +2,405 @@ require 'json/common' ## -# = JavaScript Object Notation (JSON) +# = JavaScript \Object Notation (\JSON) # -# JSON is a lightweight data-interchange format. It is easy for us -# humans to read and write. Plus, equally simple for machines to generate or parse. -# JSON is completely language agnostic, making it the ideal interchange format. +# \JSON is a lightweight data-interchange format. # -# Built on two universally available structures: +# A \JSON value is one of the following: +# - Double-quoted text: <tt>"foo"</tt>. +# - Number: +1+, +1.0+, +2.0e2+. +# - Boolean: +true+, +false+. +# - Null: +null+. +# - \Array: an ordered list of values, enclosed by square brackets: +# ["foo", 1, 1.0, 2.0e2, true, false, null] # -# 1. A collection of name/value pairs. Often referred to as an _object_, hash table, -# record, struct, keyed list, or associative array. -# 2. An ordered list of values. More commonly called an _array_, vector, sequence or -# list. +# - \Object: a collection of name/value pairs, enclosed by curly braces; +# each name is double-quoted text; +# the values may be any \JSON values: +# {"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null} # -# To read more about JSON visit: http://json.org +# 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]] # -# == Parsing JSON -# -# To parse a JSON string received by another application or generated within -# your existing application: +# == Using \Module \JSON # +# To make module \JSON available in your code, begin with: # require 'json' # -# my_hash = JSON.parse('{"hello": "goodbye"}') -# puts my_hash["hello"] # => "goodbye" +# All examples here assume that this has been done. # -# Notice the extra quotes <tt>''</tt> around the hash notation. Ruby expects -# the argument to be a string and can't convert objects like a hash or array. +# === Parsing \JSON # -# Ruby converts your string into a hash +# You can parse a \String containing \JSON data using +# either of two methods: +# - <tt>JSON.parse(source, opts)</tt> +# - <tt>JSON.parse!(source, opts)</tt> # -# == Generating JSON +# where +# - +source+ is a Ruby object. +# - +opts+ is a \Hash object containing options +# that control both input allowed and output formatting. # -# Creating a JSON string for communication or serialization is -# just as simple. +# 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. # -# require 'json' +# ==== Parsing \JSON Arrays # -# my_hash = {:hello => "goodbye"} -# puts JSON.generate(my_hash) # => "{\"hello\":\"goodbye\"}" +# 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 # => ["foo", 1, 1.0, 200.0, true, false, nil] +# ruby.class # => Array # -# Or an alternative way: +# The \JSON array may contain nested arrays, objects, and scalars +# to any depth: +# json = '[{"foo": 0, "bar": 1}, ["baz", 2]]' +# JSON.parse(json) # => [{"foo"=>0, "bar"=>1}, ["baz", 2]] # -# require 'json' -# puts({:hello => "goodbye"}.to_json) # => "{\"hello\":\"goodbye\"}" +# ==== 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 # => {"a"=>"foo", "b"=>1, "c"=>1.0, "d"=>200.0, "e"=>true, "f"=>false, "g"=>nil} +# ruby.class # => Hash +# +# 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) # => {"foo"=>{"bar"=>1, "baz"=>2}, "bat"=>[0, 1, 2]} +# +# ==== 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 # => "foo" +# ruby.class # => String +# \Integer: +# ruby = JSON.parse('1') +# ruby # => 1 +# ruby.class # => Integer +# \Float: +# ruby = JSON.parse('1.0') +# ruby # => 1.0 +# ruby.class # => Float +# ruby = JSON.parse('2.0e2') +# ruby # => 200 +# ruby.class # => Float +# Boolean: +# ruby = JSON.parse('true') +# ruby # => true +# ruby.class # => TrueClass +# ruby = JSON.parse('false') +# ruby # => false +# ruby.class # => FalseClass +# Null: +# ruby = JSON.parse('null') +# ruby # => nil +# ruby.class # => NilClass +# +# === Generating \JSON +# +# To generate a Ruby \String containing \JSON data, +# use method <tt>JSON.generate(source, opts)</tt>, where +# - +source+ is a Ruby object. +# - +opts+ is a \Hash object containing options +# that control both input allowed and output formatting. +# +# ==== 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 # => "[0,\"s\",\"foo\"]" +# +# 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 # => "[0,[1,2],{\"foo\":3,\"bar\":4}]" +# +# ==== 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 # => "{\"foo\":0,\"bar\":\"s\",\"baz\":\"bat\"}" +# +# 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 # => "{\"foo\":[0,1],\"bar\":{\"baz\":2,\"bat\":3},\"bam\":\"bad\"}" +# +# ==== 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(Integer(0)) # => "0"" +# JSON.generate(Float(1.0)) # => "1.0" +# +# When the source is a Ruby \String, JSON.generate returns +# a \String containing a \JSON string (with double-quotes): +# JSON.generate('A string') # => "\"A string\"" +# +# When the source is +true+, +false+ or +nil+, JSON.generate returns +# a \String containing the corresponding \JSON token: +# JSON.generate(true) # => "true" +# JSON.generate(false) # => "false" +# JSON.generate(nil) # => "null" +# +# When the source is none of the above, JSON.generate returns +# a \String containing a \JSON string representation of the source: +# JSON.generate(:foo) # => "\"foo\"" +# JSON.generate(Complex(0, 0)) # => "\"0+0i\"" +# JSON.generate(Dir.new('.')) # => "\"#<Dir:0x0000000006bb30b8>\"" +# +# == \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 # => "\"0..2\"" +# ruby1 = JSON.parse(json) +# ruby1 # => "0..2" +# ruby1.class # => String +# +# You can use \JSON _additions_ to preserve the original object. +# The addition is an extension of a ruby class, so that: +# - \JSON.generate stores more information in the \JSON string. +# - \JSON.parse, called with option +create_additions+, +# uses that information to create a proper Ruby object. # -# <tt>JSON.generate</tt> only allows objects or arrays to be converted -# to JSON syntax. <tt>to_json</tt>, however, accepts many Ruby classes -# even though it acts only as a method for serialization: +# 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) +# # This passage does not use the addition for Range. +# json0 = JSON.generate(ruby) +# ruby0 = JSON.parse(json0) +# # This passage uses the addition for Range. +# require 'json/add/range' +# json1 = JSON.generate(ruby) +# ruby1 = JSON.parse(json1, create_additions: true) +# # Make a nice display. +# 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}[#module-JSON-label-Custom+JSON+Additions]. +# +# === Built-in Additions +# +# The \JSON module includes additions for certain classes. +# To use an addition, +require+ its source: +# - BigDecimal: <tt>require 'json/add/bigdecimal'</tt> +# - Complex: <tt>require 'json/add/complex'</tt> +# - Date: <tt>require 'json/add/date'</tt> +# - DateTime: <tt>require 'json/add/date_time'</tt> +# - Exception: <tt>require 'json/add/exception'</tt> +# - OpenStruct: <tt>require 'json/add/ostruct'</tt> +# - Range: <tt>require 'json/add/range'</tt> +# - Rational: <tt>require 'json/add/rational'</tt> +# - Regexp: <tt>require 'json/add/regexp'</tt> +# - Set: <tt>require 'json/add/set'</tt> +# - Struct: <tt>require 'json/add/struct'</tt> +# - Symbol: <tt>require 'json/add/symbol'</tt> +# - Time: <tt>require 'json/add/time'</tt> +# +# 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) # 0.0 +# json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"} +# ruby1 = JSON.parse(json, create_additions: true) # 0.0 +# ruby1.class # => BigDecimal +# +# \Complex: +# require 'json/add/complex' +# ruby0 = Complex(1+0i) # 1+0i +# json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0} +# ruby1 = JSON.parse(json, create_additions: true) # 1+0i +# ruby1.class # Complex +# +# \Date: +# require 'json/add/date' +# ruby0 = Date.today # 2020-05-02 +# json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0} +# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 +# ruby1.class # Date +# +# \DateTime: +# require 'json/add/date_time' +# ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00 +# json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0} +# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00 +# ruby1.class # DateTime +# +# \Exception (and its subclasses including \RuntimeError): +# require 'json/add/exception' +# ruby0 = Exception.new('A message') # A message +# json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null} +# ruby1 = JSON.parse(json, create_additions: true) # A message +# ruby1.class # Exception +# ruby0 = RuntimeError.new('Another message') # Another message +# json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null} +# ruby1 = JSON.parse(json, create_additions: true) # Another message +# ruby1.class # RuntimeError +# +# \OpenStruct: +# require 'json/add/ostruct' +# ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # #<OpenStruct name="Matz", language="Ruby"> +# json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}} +# ruby1 = JSON.parse(json, create_additions: true) # #<OpenStruct name="Matz", language="Ruby"> +# ruby1.class # OpenStruct +# +# \Range: +# require 'json/add/range' +# ruby0 = Range.new(0, 2) # 0..2 +# json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]} +# ruby1 = JSON.parse(json, create_additions: true) # 0..2 +# ruby1.class # Range +# +# \Rational: +# require 'json/add/rational' +# ruby0 = Rational(1, 3) # 1/3 +# json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3} +# ruby1 = JSON.parse(json, create_additions: true) # 1/3 +# ruby1.class # Rational +# +# \Regexp: +# require 'json/add/regexp' +# ruby0 = Regexp.new('foo') # (?-mix:foo) +# json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"} +# ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo) +# ruby1.class # Regexp +# +# \Set: +# require 'json/add/set' +# ruby0 = Set.new([0, 1, 2]) # #<Set: {0, 1, 2}> +# json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]} +# ruby1 = JSON.parse(json, create_additions: true) # #<Set: {0, 1, 2}> +# ruby1.class # Set +# +# \Struct: +# require 'json/add/struct' +# Customer = Struct.new(:name, :address) # Customer +# ruby0 = Customer.new("Dave", "123 Main") # #<struct Customer name="Dave", address="123 Main"> +# json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]} +# ruby1 = JSON.parse(json, create_additions: true) # #<struct Customer name="Dave", address="123 Main"> +# ruby1.class # Customer + # +# \Symbol: +# require 'json/add/symbol' +# ruby0 = :foo # foo +# json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"} +# ruby1 = JSON.parse(json, create_additions: true) # foo +# ruby1.class # Symbol +# +# \Time: +# require 'json/add/time' +# ruby0 = Time.now # 2020-05-02 11:28:26 -0500 +# json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000} +# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500 +# ruby1.class # Time +# +# +# === 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: +# # Extend class Foo with JSON addition. +# class Foo +# # Serialize Foo object with its class name and arguments +# def to_json(*args) +# { +# JSON.create_id => self.class.name, +# 'a' => [ bar, baz ] +# }.to_json(*args) +# end +# # Deserialize JSON string by constructing new Foo object with arguments. +# def self.json_create(object) +# new(*object['a']) +# end +# end +# +# Demonstration: # require 'json' +# # This Foo object has no custom addition. +# foo0 = Foo.new(0, 1) +# json0 = JSON.generate(foo0) +# obj0 = JSON.parse(json0) +# # Lood the custom addition. +# require_relative 'foo_addition' +# # This foo has the custom addition. +# foo1 = Foo.new(0, 1) +# json1 = JSON.generate(foo1) +# obj1 = JSON.parse(json1, create_additions: true) +# # Make a nice display. +# 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) # -# 1.to_json # => "1" -# -# The {#generate}[rdoc-ref:JSON#generate] method accepts a variety of options -# to set the formatting of string output and defining what input is accepteable. -# There are also shortcut methods pretty_generate (with a set of options to -# generate human-readable multiline JSON) and fast_generate (with a set of -# options to generate JSON faster at the price of disabling some checks). -# -# == Extended rendering and loading of Ruby objects -# -# JSON library provides optional _additions_ allowing to serialize and -# deserialize Ruby classes without loosing their type. -# -# # without additions -# require "json" -# json = JSON.generate({range: 1..3, regex: /test/}) -# # => '{"range":"1..3","regex":"(?-mix:test)"}' -# JSON.parse(json) -# # => {"range"=>"1..3", "regex"=>"(?-mix:test)"} -# -# # with additions -# require "json/add/range" -# require "json/add/regexp" -# json = JSON.generate({range: 1..3, regex: /test/}) -# # => '{"range":{"json_class":"Range","a":[1,3,false]},"regex":{"json_class":"Regexp","o":0,"s":"test"}}' -# JSON.parse(json) -# # => {"range"=>{"json_class"=>"Range", "a"=>[1, 3, false]}, "regex"=>{"json_class"=>"Regexp", "o"=>0, "s"=>"test"}} -# JSON.load(json) -# # => {"range"=>1..3, "regex"=>/test/} -# -# See JSON.load for details. module JSON require 'json/version' |