oj-3.13.9/0000755000004100000410000000000014136373754012264 5ustar www-datawww-dataoj-3.13.9/test/0000755000004100000410000000000014136373754013243 5ustar www-datawww-dataoj-3.13.9/test/zoo.rb0000755000004100000410000000027114136373754014402 0ustar www-datawww-data#!/usr/bin/env ruby #require 'json' $: << File.dirname(__FILE__) require 'helper' require 'oj' Oj.mimic_JSON puts "\u3074" puts JSON.dump(["\u3074"]) puts JSON.generate(["\u3074"]) oj-3.13.9/test/perf_file.rb0000755000004100000410000000353014136373754015527 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 $: << '.' $: << '../lib' $: << '../ext' if __FILE__ == $0 if (i = ARGV.index('-I')) x,path = ARGV.slice!(i, 2) $: << path end end require 'optparse' require 'oj' require 'perf' $indent = 0 $iter = 1 $size = 1 opts = OptionParser.new opts.on("-r", "read") { do_read = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indent") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size in Mbytes") { |s| $size = s } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, #_bignum 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } json = Oj.dump($obj, :indent => $indent) cnt = ($size * 1024 * 1024 + json.size) / json.size cnt = 1 if 0 == $size filename = 'tmp.json' File.open(filename, "w") { |f| cnt.times do Oj.to_stream(f, $obj, :indent => $indent) end } Oj.default_options = { :mode => :strict, :indent => $indent } puts '-' * 80 puts "Read from #{cnt * json.size / (1024 * 1024)} Mb file Performance" perf = Perf.new() perf.add('Oj.load_file', '') { Oj.load_file(filename) } perf.add('Oj.load(string)', '') { Oj.load(File.read(filename)) } perf.add('Oj.load(file)', '') { File.open(filename, 'r') { |f| Oj.load(f) } } perf.run($iter) oj-3.13.9/test/perf.rb0000644000004100000410000000463414136373754014533 0ustar www-datawww-data class Perf def initialize() @items = [] end def add(title, op, &blk) @items << Item.new(title, op, &blk) end def before(title, &blk) @items.each do |i| if title == i.title i.set_before(&blk) break end end end def run(iter) base = Item.new(nil, nil) { } base.run(iter, 0.0) @items.each do |i| i.run(iter, base.duration) if i.error.nil? puts "#{i.title}.#{i.op} #{iter} times in %0.3f seconds or %0.3f #{i.op}/sec." % [i.duration, iter / i.duration] else puts "***** #{i.title}.#{i.op} failed! #{i.error}" end end summary() end def summary() fastest = nil slowest = nil width = 6 @items.each do |i| next if i.duration.nil? width = i.title.size if width < i.title.size end iva = @items.clone iva.delete_if { |i| i.duration.nil? } iva = iva.sort_by { |i| i.duration } puts puts "Summary:" puts "%*s time (secs) rate (ops/sec)" % [width, 'System'] puts "#{'-' * width} ----------- --------------" iva.each do |i| if i.duration.nil? else puts "%*s %11.3f %14.3f" % [width, i.title, i.duration, i.rate ] end end puts puts "Comparison Matrix\n(performance factor, 2.0 means row is twice as fast as column)" puts ([' ' * width] + iva.map { |i| "%*s" % [width, i.title] }).join(' ') puts (['-' * width] + iva.map { |i| '-' * width }).join(' ') iva.each do |i| line = ["%*s" % [width, i.title]] iva.each do |o| line << "%*.2f" % [width, o.duration / i.duration] end puts line.join(' ') end puts end class Item attr_accessor :title attr_accessor :op attr_accessor :blk attr_accessor :duration attr_accessor :rate attr_accessor :error def initialize(title, op, &blk) @title = title @blk = blk @op = op @duration = nil @rate = nil @error = nil @before = nil end def set_before(&blk) @before = blk end def run(iter, base) begin GC.start @before.call unless @before.nil? start = Time.now iter.times { @blk.call } @duration = Time.now - start - base @duration = 0.0 if @duration < 0.0 @rate = iter / @duration rescue Exception => e @error = "#{e.class}: #{e.message}" end end end # Item end # Perf oj-3.13.9/test/test_file.rb0000755000004100000410000001340414136373754015553 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' class FileJuice < Minitest::Test class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end# Jam class Jeez < Jam def initialize(x, y) super end def to_json() %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end# Jeez class Orange < Jam def initialize(x, y) super end def as_json() { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float mode = Oj.default_options()[:mode] Oj.default_options = {:mode => :object} dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) dump_and_load(1/0.0, false) Oj.default_options = {:mode => mode} end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) assert_equal("a\u0000a", dump_and_load("a\u0000a", false)) end def test_string_object dump_and_load('abc', false) dump_and_load(':abc', false) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end # Symbol def test_symbol_object Oj.default_options = { :mode => :object } #dump_and_load(''.to_sym, false) dump_and_load(:abc, false) dump_and_load(':xyz'.to_sym, false) end # Time def test_time_object t = Time.now() Oj.default_options = { :mode => :object, :time_format => :unix_zone } dump_and_load(t, false) end def test_time_object_early # Windows does not support dates before 1970. return if RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ t = Time.xmlschema("1954-01-05T00:00:00.123456") Oj.default_options = { :mode => :object, :time_format => :unix_zone } dump_and_load(t, false) end # Hash def test_hash Oj.default_options = { :mode => :strict } dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end # Object with to_json() def test_json_object_compat Oj.default_options = { :mode => :compat, :use_to_json => true, :create_additions => true } obj = Jeez.new(true, 58) json = Oj.dump(obj, :indent => 2) assert(%{{"json_class":"FileJuice::Jeez","x":true,"y":58} } == json || %{{"json_class":"FileJuice::Jeez","y":58,"x":true} } == json) dump_and_load(obj, false) Oj.default_options = { :mode => :compat, :use_to_json => false } end # Range def test_range_object Oj.default_options = { :mode => :object } json = Oj.dump(1..7, :mode => :object, :indent => 0) if 'rubinius' == $ruby assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json) elsif 'jruby' == $ruby assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json) else assert_equal(%{{"^u":["Range",1,7,false]}}, json) end dump_and_load(1..7, false) dump_and_load(1..1, false) dump_and_load(1...7, false) end # BigNum def test_bignum_object Oj.default_options = { :mode => :compat } dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_strict mode = Oj.default_options[:mode] Oj.default_options = {:mode => :strict} dump_and_load(BigDecimal('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} end def test_bigdecimal_null mode = Oj.default_options[:mode] Oj.default_options = {:mode => :null} dump_and_load(BigDecimal('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} end def test_bigdecimal_object Oj.default_options = {:mode => :object} dump_and_load(BigDecimal('3.14159265358979323846'), false) end # Date def test_date_object Oj.default_options = { :mode => :object } dump_and_load(Date.new(2012, 6, 19), false) end # DateTime def test_datetime_object Oj.default_options = { :mode => :object } dump_and_load(DateTime.new(2012, 6, 19), false) end def dump_and_load(obj, trace=false) filename = File.join(File.dirname(__FILE__), 'file_test.json') File.open(filename, "w") { |f| Oj.to_stream(f, obj, :indent => 2) } puts "\n*** file: '#{File.read(filename)}'" if trace loaded = Oj.load_file(filename) if obj.is_a?(Time) && loaded.is_a?(Time) assert_equal(obj.tv_sec, loaded.tv_sec) if obj.respond_to?(:tv_nsec) assert_equal(obj.tv_nsec, loaded.tv_nsec) else assert_equal(obj.tv_usec, loaded.tv_usec) end assert_equal(obj.utc?, loaded.utc?) assert_equal(obj.utc_offset, loaded.utc_offset) elsif obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/test_writer.rb0000755000004100000410000002126314136373754016152 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) require 'helper' class OjWriter < Minitest::Test def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_string_writer_empty_array w = Oj::StringWriter.new(:indent => 0) w.push_array() w.pop() assert_equal("[]\n", w.to_s) end def test_string_writer_nested_array w = Oj::StringWriter.new(:indent => 0) w.push_array() w.push_array() w.pop() w.push_array() w.push_array() w.pop() w.pop() w.push_array() w.pop() w.pop() assert_equal("[[],[[]],[]]\n", w.to_s) end def test_string_writer_empty_object w = Oj::StringWriter.new(:indent => 0) w.push_object() w.pop() assert_equal("{}\n", w.to_s) end def test_string_writer_push_null_value_with_key w = Oj::StringWriter.new(:indent => 0, :mode => :compat) w.push_object() w.push_value(nil, 'nothing') w.pop() assert_equal(%|{"nothing":null}\n|, w.to_s) end def test_string_writer_nested_object w = Oj::StringWriter.new(:indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_object("b") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() assert_equal(%|{"a1":{},"a2":{"b":{}},"a3":{}}\n|, w.to_s) end def test_string_writer_nested_key w = Oj::StringWriter.new(:indent => 0) w.push_object() w.push_key('a1') w.push_object() w.pop() w.push_key('a2') w.push_object('x') w.push_object('b') w.pop() w.pop() w.push_key('a3') w.push_object() w.push_key('a4') w.push_value(37) w.pop() w.pop() assert_equal(%|{"a1":{},"a2":{"b":{}},"a3":{"a4":37}}\n|, w.to_s) end def test_string_writer_value_array w = Oj::StringWriter.new(:indent => 2) w.push_array() w.push_value(7) w.push_value(7.3) w.push_value(true) w.push_value(nil) w.push_value("a string") w.push_value({'a' => 65}) w.push_value([1,2]) w.pop() assert_equal(%|[ 7, 7.3, true, null, "a string", { "a":65 }, [ 1, 2 ] ] |, w.to_s) end def test_string_writer_block w = Oj::StringWriter.new(:indent => 0) w.push_object() { w.push_object("a1") { w.push_value(7, 'a7') } w.push_array("a2") { w.push_value('x') w.push_value(3) } } assert_equal(%|{"a1":{"a7":7},"a2":["x",3]}\n|, w.to_s) end def test_string_writer_json w = Oj::StringWriter.new(:indent => 0) w.push_array() w.push_json('7') w.push_json('true') w.push_json(%|"a string"|) w.push_object() w.push_json('{"a":65}', 'x') w.pop() w.pop() assert_equal(%|[7,true,"a string",{"x":{"a":65}}]\n|, w.to_s) end def test_string_writer_pop_excess w = Oj::StringWriter.new(:indent => 0) begin w.push_object() { w.push_key('key') } rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_array_key w = Oj::StringWriter.new(:indent => 0) begin w.push_array() { w.push_key('key') w.push_value(7) } rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_pop_with_key w = Oj::StringWriter.new(:indent => 0) begin w.pop() rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_obj_no_key w = Oj::StringWriter.new(:indent => 0) w.push_object() begin w.push_value(59) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_deep cnt = 1000 w = Oj::StringWriter.new(:indent => 0) cnt.times do w.push_array() end cnt.times do w.pop() end # if no exception then passed assert(true) end def test_string_writer_pop_all w = Oj::StringWriter.new(:indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_array("a2") w.push_value(3) w.push_array() w.pop_all() assert_equal(%|{"a1":{},"a2":[3,[]]}\n|, w.to_s) end def test_string_writer_reset w = Oj::StringWriter.new(:indent => 0) w.push_array() w.pop() w.reset() assert_equal('', w.to_s) end # Stream Writer class SString < ::String alias :write :concat end def test_stream_writer_encoding output = SString.new.force_encoding(::Encoding::UTF_8) w = Oj::StreamWriter.new(output, :indent => 0) # Oddly enough, when pushing ASCII characters with UTF-8 encoding or even # ASCII-8BIT does not change the output encoding. Pushing any non-ASCII no # matter what the encoding changes the output encoding to ASCII-8BIT. x = "香港" # Hong Kong x = x.force_encoding('ASCII-8BIT') w.push_value(x) assert_equal(::Encoding::UTF_8, output.encoding) end def test_stream_writer_empty_array output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_array() w.pop() assert_equal("[]\n", output.string()) end def test_stream_writer_mixed_stringio output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_array("b") w.push_value(7) w.push_value(true) w.push_value("string") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() result = output.string() assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, result) end def test_stream_writer_mixed_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, "w") do |f| w = Oj::StreamWriter.new(f, :indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_array("b") w.push_value(7) w.push_value(true) w.push_value("string") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() end content = File.read(filename) assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, content) end def test_stream_writer_nested_key_object output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_object() w.push_key('a1') w.push_object() w.pop() w.push_key('a2') w.push_object('x') w.push_object('b') w.pop() w.pop() w.push_key('a3') w.push_object() w.push_key('a4') w.push_value(37) w.pop() w.pop() assert_equal(%|{"a1":{},"a2":{"b":{}},"a3":{"a4":37}}\n|, output.string()) end def push_stuff(w, pop_all=true) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_array("b") w.push_value(7) w.push_value(true) w.push_value("string") w.pop() w.pop() w.push_object("a3") if pop_all w.pop_all() else w.pop() w.pop() end end def test_stream_writer_buf_small output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0, :buffer_size => 20) push_stuff(w) assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, output.string()) end def test_stream_writer_buf_large output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0, :buffer_size => 16000) push_stuff(w) assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, output.string()) end def test_stream_writer_buf_flush output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0, :buffer_size => 4096) push_stuff(w, false) # no flush so nothing should be in the output yet assert_equal("", output.string()) w.flush() assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, output.string()) end def test_stream_writer_buf_flush_small output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0, :buffer_size => 30) push_stuff(w, false) # flush should be called at 30 bytes but since the writes are chunky flush # is called after adding "string". assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"|, output.string()) w.flush() assert_equal(%|{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}\n|, output.string()) end def test_stream_writer_push_null_value_with_key output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_object() w.push_value(nil, 'nothing') w.pop() assert_equal(%|{"nothing":null}\n|, output.string()) end end # OjWriter oj-3.13.9/test/sample_json.rb0000755000004100000410000000133214136373754016104 0ustar www-datawww-data#!/usr/bin/env ruby if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'oj' def sample_json(size=3) colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ] container = [] size.times do |i| box = { 'color' => colors[i % colors.size], 'fragile' => (0 == (i % 2)), 'width' => i, 'height' => i, 'depth' => i, 'weight' => i * 1.3, 'address' => { 'street' => "#{i} Main Street", 'city' => 'Sity', 'state' => nil } } container << box end container end if $0 == __FILE__ File.open('sample.json', "w") { |f| f.write(Oj.dump(sample_json(3), :indent => 2)) } end oj-3.13.9/test/sample.rb0000644000004100000410000000244014136373754015051 0ustar www-datawww-data#!/usr/bin/env ruby -wW2 if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'sample/doc' def sample_doc(size=3) colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ] d = ::Sample::Doc.new('Sample') # add some history (0..size * 10).each do |i| d.add_change("Changed at t+#{i}.") end # add some layers (1..size).each do |i| layer = ::Sample::Layer.new("Layer-#{i}") (1..size).each do |j| g = ::Sample::Group.new (1..size).each do |k| g2 = ::Sample::Group.new r = ::Sample::Rect.new(j * 40 + 10.0, i * 10.0, 10.123456 / k, 10.0 / k, colors[(i + j + k) % colors.size]) r.add_prop(:part_of, layer.name) g2 << r g2 << ::Sample::Text.new("#{k} in #{j}", r.left, r.top, r.width, r.height) g << g2 end g2 = ::Sample::Group.new (1..size).each do |k| o = ::Sample::Oval.new(j * 40 + 12.0, i * 10.0 + 2.0, 6.0 / k, 6.0 / k, colors[(i + j + k) % colors.size]) o.add_prop(:inside, true) g << o end g << g2 layer << g end d.layers[layer.name] = layer end # some properties d.add_prop(:purpose, 'an example') d end oj-3.13.9/test/test_null.rb0000755000004100000410000002111114136373754015600 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class NullJuice < Minitest::Test module TestModule end class Jeez attr_accessor :x, :y def initialize(x, y) @x = x @y = y end end def setup @default_options = Oj.default_options # in null mode other options other than the number formats are not used. Oj.default_options = { :mode => :null } end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_nan_dump assert_equal('null', Oj.dump(0/0.0, :nan => :null)) assert_equal('3.3e14159265358979323846', Oj.dump(0/0.0, :nan => :huge)) begin Oj.dump(0/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_infinity_dump assert_equal('null', Oj.dump(1/0.0, :nan => :null)) assert_equal('3.0e14159265358979323846', Oj.dump(1/0.0, :nan => :huge)) begin Oj.dump(1/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_neg_infinity_dump assert_equal('null', Oj.dump(-1/0.0, :nan => :null)) assert_equal('-3.0e14159265358979323846', Oj.dump(-1/0.0, :nan => :huge)) begin Oj.dump(-1/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_unicode_long # tests buffer overflow json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e #{'x' * 2000}"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.strict_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_non_str_hash begin Oj.dump({ 1 => true, 0 => false }) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_strict Oj.default_options = { :bigdecimal_load => true} dump_and_load(BigDecimal('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal('80.51') json = Oj.dump(orig, :mode => :strict, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :strict, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end def test_json_object obj = Jeez.new(true, 58) json = Oj.dump(obj) assert_equal('null', json) end def test_range json = Oj.dump(1..7) assert_equal('null', json) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.strict_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.strict_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_symbol json = Oj.dump(:abc) assert_equal('"abc"', json) end def test_time t = Time.local(2012, 1, 5, 23, 58, 7) json = Oj.dump(t) assert_equal('null', json) end def test_class json = Oj.dump(NullJuice) assert_equal('null', json) end def test_module json = Oj.dump(TestModule) assert_equal('null', json) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.strict_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end def test_symbol_keys_safe json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.safe_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_double json = %{{ "x": 1}{ "y": 2}} results = [] Oj.load(json, :mode => :strict) { |x| results << x } assert_equal([{ 'x' => 1 }, { 'y' => 2 }], results) end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :indent => 2, :circular => true, :mode => :null) assert_equal(%|{ "a":7, "b":null } |, json) end def test_omit_nil json = Oj.dump({'x' => {'a' => 1, 'b' => nil }, 'y' => nil}, :omit_nil => true) assert_equal(%|{"x":{"a":1}}|, json) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) puts json if trace loaded = Oj.strict_load(json); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/test_wab.rb0000755000004100000410000001564714136373754015420 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'uri' require 'oj' # Simple version of WAB::UUID for testing. module WAB class UUID attr_reader :id def initialize(id) @id = id.downcase raise Exception.new("Invalid UUID format.") if /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.match(@id).nil? end def to_s @id end def ==(other) other.is_a?(self.class) && @id == other.id end end # UUID end # WAB class WabJuice < Minitest::Test module TestModule end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_nan_dump assert_raises() { Oj.dump(0/0.0, mode: :wab) } end def test_infinity_dump assert_raises() { Oj.dump(1/0.0, mode: :wab) } end def test_neg_infinity_dump assert_raises() { Oj.dump(-1/0.0, mode: :wab) } end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode dump_and_load("ぴーたー", false) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end def test_deep_nest begin n = 10000 Oj.wab_load('[' * n + ']' * n) rescue Exception => e assert(false, e.message) end end # Hash def test_hash dump_and_load({}, false) dump_and_load({ true: true, false: false}, false) dump_and_load({ true: true, array: [], hash: { }}, false) end def test_hash_non_sym_keys assert_raises() { Oj.dump({ 'true' => true}, mode: :wab) } end def test_hash_deep dump_and_load({x1: { x2: { x3: { x4: { x5: { x6: { x7: { x8: { x9: { x10: { x11: { x12: { x13: { x14: { x15: { x16: { x17: { x18: { x19: { x20: {}}}}}}}}}}}}}}}}}}}}}, false) end def test_non_str_hash assert_raises() { Oj.dump({ 1 => true, 0 => false }, mode: :wab) } end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_wab dump_and_load(BigDecimal('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal('80.51') json = Oj.dump(orig, mode: :wab) bg = Oj.load(json, :mode => :wab, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end def test_range assert_raises() { Oj.dump(1..7, mode: :wab) } end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.wab_load(input) assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end def test_io_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.wab_load(f) f.close() assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end def test_symbol json = Oj.dump(:abc, mode: :wab) assert_equal('"abc"', json) end def test_time t = Time.gm(2017, 1, 5, 23, 58, 7, 123456.789) json = Oj.dump(t, mode: :wab) assert_equal('"2017-01-05T23:58:07.123456789Z"', json) # must load and convert to json as the Time#== does not match identical # times due to the way it tracks fractional seconds. loaded = Oj.wab_load(json); assert_equal(json, Oj.dump(loaded, mode: :wab), "json mismatch after load") end def test_uuid u = ::WAB::UUID.new('123e4567-e89b-12d3-a456-426655440000') json = Oj.dump(u, mode: :wab) assert_equal('"123e4567-e89b-12d3-a456-426655440000"', json) dump_and_load(u, false) end def test_uri u = URI('http://opo.technology/sample') json = Oj.dump(u, mode: :wab) assert_equal('"http://opo.technology/sample"', json) dump_and_load(u, false) end def test_class assert_raises() { Oj.dump(WabJuice, mode: :wab) } end def test_module assert_raises() { Oj.dump(TestModule, mode: :wab) } end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.wab_load(json, :symbol_keys => true) assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.wab_load(json) assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.wab_load(json) assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.wab_load(json) assert_equal({ x: true, y: 58, z: [1, 2, 3]}, obj) end def test_double json = %{{ "x": 1}{ "y": 2}} results = [] Oj.load(json, :mode => :wab) { |x| results << x } assert_equal([{ x: 1 }, { y: 2 }], results) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, mode: :wab, indent: 2) puts json if trace loaded = Oj.wab_load(json); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/test_fast.rb0000755000004100000410000003145514136373754015577 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true $: << File.dirname(__FILE__) require 'helper' class DocTest < Minitest::Test def setup @default_options = Oj.default_options @json1 = %|{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }| end def teardown Oj.default_options = @default_options end def test_nil json = %{null} Oj::Doc.open(json) do |doc| assert_equal(NilClass, doc.type) assert_nil(doc.fetch()) end end def test_leaf_of_existing_path json = %{{"foo": 1, "fizz": true}} Oj::Doc.open(json) do |doc| %w(/foo/bar /fizz/bar).each do |path| assert_nil(doc.fetch(path)) assert_equal(:default, doc.fetch(path, :default)) refute(doc.exists?(path)) end end end def test_true json = %{true} Oj::Doc.open(json) do |doc| assert_equal(TrueClass, doc.type) assert_equal(true, doc.fetch()) end end def test_false json = %{false} Oj::Doc.open(json) do |doc| assert_equal(FalseClass, doc.type) assert_equal(false, doc.fetch()) end end def test_string json = %{"a string"} Oj::Doc.open(json) do |doc| assert_equal(String, doc.type) assert_equal('a string', doc.fetch()) end end def test_encoding json = %{"ぴーたー"} Oj::Doc.open(json) do |doc| assert_equal(String, doc.type) assert_equal("ぴーたー", doc.fetch()) end end def test_encoding_escaped json = %{"\\u3074\\u30fc\\u305f\\u30fc"} Oj::Doc.open(json) do |doc| assert_equal(String, doc.type) assert_equal("ぴーたー", doc.fetch()) end end def test_fixnum json = %{12345} Oj::Doc.open(json) do |doc| if '2.4.0' <= RUBY_VERSION assert_equal(Integer, doc.type) else assert_equal(Fixnum, doc.type) end assert_equal(12345, doc.fetch()) end end def test_float json = %{12345.6789} Oj::Doc.open(json) do |doc| assert_equal(Float, doc.type) assert_equal(12345.6789, doc.fetch()) end end def test_float_exp json = %{12345.6789e7} Oj::Doc.open(json) do |doc| assert_equal(Float, doc.type) #assert_equal(12345.6789e7, doc.fetch()) assert_equal(12345.6789e7.to_i, doc.fetch().to_i) end end def test_array_empty json = %{[]} Oj::Doc.open(json) do |doc| assert_equal(Array, doc.type) assert_equal([], doc.fetch()) end end def test_array json = %{[true,false]} Oj::Doc.open(json) do |doc| assert_equal(Array, doc.type) assert_equal([true, false], doc.fetch()) end end def test_hash_empty json = %{{}} Oj::Doc.open(json) do |doc| assert_equal(Hash, doc.type) assert_equal({}, doc.fetch()) end end def test_hash json = %{{"one":true,"two":false}} Oj::Doc.open(json) do |doc| assert_equal(Hash, doc.type) assert_equal({'one' => true, 'two' => false}, doc.fetch()) end end # move() and where?() def test_move_hash json = %{{"one":{"two":false}}} Oj::Doc.open(json) do |doc| doc.move('/one') assert_equal('/one', doc.where?) doc.move('/one/two') assert_equal('/one/two', doc.where?) end end def test_move_array json = %{[1,[2,true]]} Oj::Doc.open(json) do |doc| doc.move('/1') assert_equal('/1', doc.where?) doc.move('/2/1') assert_equal('/2/1', doc.where?) end end def test_move Oj::Doc.open(@json1) do |doc| [ '/', '/array', '/boolean', '/array/1/hash/h2/a/3', ].each do |p| doc.move(p) assert_equal(p, doc.where?) end begin doc.move('/array/x') rescue Exception assert_equal('/', doc.where?) assert(true) end end end def test_move_slash Oj::Doc.open(%|{"top":{"a/b":3}}|) do |doc| doc.move('top/a\/b') assert_equal('/top/a\/b', doc.where?) end end def test_fetch_slash Oj::Doc.open(%|{"a/b":3}|) do |doc| x = doc.fetch('a\/b') assert_equal(3, x) end end def test_move_relative Oj::Doc.open(@json1) do |doc| [['/', 'array', '/array'], ['/array', '1/num', '/array/1/num'], ['/array/1/hash', 'h2/a', '/array/1/hash/h2/a'], ['/array/1', 'hash/h2/a/2', '/array/1/hash/h2/a/2'], ['/array/1/hash', '../string', '/array/1/string'], ['/array/1/hash', '..', '/array/1'], ].each do |start,path,where| doc.move(start) doc.move(path) assert_equal(where, doc.where?) end end end def test_type if '2.4.0' <= RUBY_VERSION num_class = Integer else num_class = Fixnum end Oj::Doc.open(@json1) do |doc| [['/', Hash], ['/array', Array], ['/array/1', Hash], ['/array/1/num', num_class], ['/array/1/string', String], ['/array/1/hash/h2/a', Array], ['/array/1/hash/../num', num_class], ['/array/1/hash/../..', Array], ].each do |path,type| assert_equal(type, doc.type(path)) end end end def test_local_key Oj::Doc.open(@json1) do |doc| [['/', nil], ['/array', 'array'], ['/array/1', 1], ['/array/1/num', 'num'], ['/array/1/string', 'string'], ['/array/1/hash/h2/a', 'a'], ['/array/1/hash/../num', 'num'], ['/array/1/hash/..', 1], ['/array/1/hash/../..', 'array'], ].each do |path,key| doc.move(path) if key.nil? assert_nil(doc.local_key()) else assert_equal(key, doc.local_key()) end end end end def test_fetch_move Oj::Doc.open(@json1) do |doc| [['/array/1/num', 3], ['/array/1/string', 'message'], ['/array/1/hash/h2/a', [1, 2, 3]], ['/array/1/hash/../num', 3], ['/array/1/hash/..', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array/1/hash/../..', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/array/1', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/', {'array' => [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], 'boolean' => true}], ].each do |path,val| doc.move(path) assert_equal(val, doc.fetch()) end end end def test_fetch_path Oj::Doc.open(@json1) do |doc| [['/array/1/num', 3], ['/array/1/string', 'message'], ['/array/1/hash/h2/a', [1, 2, 3]], ['/array/1/hash/../num', 3], ['/array/1/hash/..', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array/1/hash/../..', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/array/1', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/', {'array' => [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], 'boolean' => true}], ['/nothing', nil], ['/array/10', nil], ].each do |path,val| if val.nil? assert_nil(doc.fetch(path)) else assert_equal(val, doc.fetch(path)) end end end # verify empty hash and arrays return nil when a member is requested Oj::Doc.open('{}') do |doc| assert_nil(doc.fetch('/x')) assert_nil(doc.fetch('/0')) end Oj::Doc.open('[]') do |doc| assert_nil(doc.fetch('/x')) assert_nil(doc.fetch('/0')) end end def test_move_fetch_path Oj::Doc.open(@json1) do |doc| [['/array/1', 'num', 3], ['/array/1', 'string', 'message'], ['/array/1/hash', 'h2/a', [1, 2, 3]], ].each do |path,fetch_path,val| doc.move(path) assert_equal(val, doc.fetch(fetch_path)) end end end def test_exists Oj::Doc.open(@json1) do |doc| [['/array/1', true], ['/array/1', true], ['/array/1/hash', true], ['/array/1/dash', false], ['/array/3', false], ['/nothing', false], ].each do |path,val| assert_equal(val, doc.exists?(path), "failed for #{path.inspect}") end end end def test_home Oj::Doc.open(@json1) do |doc| doc.move('/array/1/num') doc.home() assert_equal('/', doc.where?) end end def test_each_value_root Oj::Doc.open(@json1) do |doc| values = [] doc.each_value() { |v| values << v.to_s } assert_equal(['1', '2', '3', '3', 'message', 'true'], values.sort) end end def test_each_value_move Oj::Doc.open(@json1) do |doc| doc.move('/array/1/hash') values = [] doc.each_value() { |v| values << v.to_s } assert_equal(['1', '2', '3'], values.sort) end end def test_each_value_path Oj::Doc.open(@json1) do |doc| values = [] doc.each_value('/array/1/hash') { |v| values << v.to_s } assert_equal(['1', '2', '3'], values.sort) end end def test_each_child_move Oj::Doc.open(@json1) do |doc| locations = [] doc.move('/array/1/hash/h2/a') doc.each_child() { |d| locations << d.where? } assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations) locations = [] doc.move('/array/1') doc.each_child() { |d| locations << d.where? } assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations) end end def test_each_child_path Oj::Doc.open(@json1) do |doc| locations = [] doc.each_child('/array/1/hash/h2/a') { |d| locations << d.where? } assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations) locations = [] doc.each_child('/array/1') { |d| locations << d.where? } assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations) end end def test_size Oj::Doc.open('[1,2,3]') do |doc| assert_equal(4, doc.size) end Oj::Doc.open('{"a":[1,2,3]}') do |doc| assert_equal(5, doc.size) end end def test_open_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') } Oj::Doc.open_file(filename) do |doc| assert_equal(5, doc.size) end end def test_open_close json = %{{"a":[1,2,3]}} doc = Oj::Doc.open(json) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc.close() begin doc.home() rescue Exception assert(true) end end def test_file_open_close filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') } doc = Oj::Doc.open_file(filename) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc.close() begin doc.home() rescue Exception assert(true) end end def test_open_no_close json = %{{"a":[1,2,3]}} doc = Oj::Doc.open(json) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc = nil GC.start # a print statement confirms close is called end def test_dump Oj::Doc.open('[1,[2,3]]') do |doc| assert_equal('[1,[2,3]]', doc.dump()) end Oj::Doc.open('[1,[2,3]]') do |doc| assert_equal('[2,3]', doc.dump('/2')) end end def test_each_leaf results = Oj::Doc.open('[1,[2,3]]') do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/1' => 1, '/2/1' => 2, '/2/2' => 3}, results) end def test_each_leaf_hash results = Oj::Doc.open('{"a":{"x":2},"b":{"y":4}}') do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/a/x' => 2, '/b/y' => 4}, results) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } results = Oj::Doc.open(json) do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/x' => true, '/y' => 58, '/z/1' => 1, '/z/2' => 2, '/z/3' => 3}, results) end end # DocTest oj-3.13.9/test/test_generate.rb0000755000004100000410000000056714136373754016434 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'oj' class Generator < Minitest::Test def test_before json = Oj.generate({}) assert_equal("{}", json) end end oj-3.13.9/test/helper.rb0000644000004100000410000000174614136373754015057 0ustar www-datawww-data# frozen_string_literal: true # # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true %w(lib ext test).each do |dir| $LOAD_PATH.unshift File.expand_path("../../#{dir}", __FILE__) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'pp' require 'oj' if defined?(GC.verify_compaction_references) == 'method' # This method was added in Ruby 3.0.0. Calling it this way asks the GC to # move objects around, helping to find object movement bugs. GC.verify_compaction_references(double_heap: true, toward: :empty) end $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') class Range def to_hash() { 'begin' => self.begin, 'end' => self.end, 'exclude_end' => self.exclude_end? } end end oj-3.13.9/test/mem.rb0000755000004100000410000000112714136373754014352 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' Oj.default_options = { mode: :rails, cache_keys: false, cache_str: -1 } def mem `ps -o rss= -p #{$$}`.to_i end ('a'..'z').each { |a| ('a'..'z').each { |b| ('a'..'z').each { |c| ('a'..'z').each { |d| ('a'..'z').each { |e| ('a'..'z').each { |f| key = "#{a}#{b}#{c}#{d}#{e}#{f}" x = Oj.load(%|{ "#{key}": 101}|) #Oj.dump(x) } } } } puts "#{a}#{b} #{mem}" } } Oj::Parser.new(:usual) oj-3.13.9/test/test_rails.rb0000755000004100000410000000144114136373754015744 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) require 'helper' class RailsJuice < Minitest::Test def test_bigdecimal_dump orig = Oj.default_options Oj.default_options = { mode: :rails, bigdecimal_as_decimal: true } bd = BigDecimal('123') json = Oj.dump(bd) Oj.default_options = orig assert_equal('0.123e3', json.downcase) json = Oj.dump(bd, mode: :rails, bigdecimal_as_decimal: false) assert_equal('"0.123e3"', json.downcase) json = Oj.dump(bd, mode: :rails, bigdecimal_as_decimal: true) assert_equal('0.123e3', json.downcase) end def test_invalid_encoding assert_raises(EncodingError) { Oj.dump("\"\xf3j", mode: :rails) } assert_raises(EncodingError) { Oj.dump("\xf3j", mode: :rails) } end end oj-3.13.9/test/_test_mimic_rails.rb0000755000004100000410000000553214136373754017266 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' #Oj.mimic_JSON require 'rails/all' require 'active_model' require 'active_model_serializers' require 'active_support/json' require 'active_support/time' require 'active_support/all' require 'oj/active_support_helper' Oj.mimic_JSON class Category include ActiveModel::Model include ActiveModel::SerializerSupport attr_accessor :id, :name def initialize(id, name) @id = id @name = name end end class CategorySerializer < ActiveModel::Serializer attributes :id, :name end class MimicRails < Minitest::Test def test_mimic_exception begin ActiveSupport::JSON.decode("{") puts "Failed" rescue ActiveSupport::JSON.parse_error assert(true) rescue Exception assert(false, 'Expected a JSON::ParserError') end end def test_dump_string Oj.default_options= {:indent => 2} json = ActiveSupport::JSON.encode([1, true, nil]) assert_equal(%{[ 1, true, null ] }, json) end def test_dump_rational Oj.default_options= {:indent => 2} json = ActiveSupport::JSON.encode([1, true, Rational(1)]) assert_equal(%{[ 1, true, "1/1" ] }, json) end def test_dump_range Oj.default_options= {:indent => 2} json = ActiveSupport::JSON.encode([1, true, '01'..'12']) assert_equal(%{[ 1, true, "01..12" ] }, json) end def test_dump_object Oj.default_options= {:indent => 2} category = Category.new(1, 'test') serializer = CategorySerializer.new(category) json = serializer.to_json() puts "*** serializer.to_json() #{serializer.to_json()}" json = serializer.as_json() puts "*** serializer.as_json() #{serializer.as_json()}" json = JSON.dump(serializer) puts "*** JSON.dump(serializer) #{JSON.dump(serializer)}" puts "*** category.to_json() #{category.to_json()}" puts "*** category.as_json() #{category.as_json()}" puts "*** JSON.dump(serializer) #{JSON.dump(category)}" puts "*** Oj.dump(serializer) #{Oj.dump(category)}" end def test_dump_object_array Oj.default_options= {:indent => 2} cat1 = Category.new(1, 'test') cat2 = Category.new(2, 'test') a = Array.wrap([cat1, cat2]) #serializer = CategorySerializer.new(a) puts "*** a.to_json() #{a.to_json()}" puts "*** a.as_json() #{a.as_json()}" puts "*** JSON.dump(a) #{JSON.dump(a)}" puts "*** Oj.dump(a) #{Oj.dump(a)}" end def test_dump_time Oj.default_options= {:indent => 2} now = ActiveSupport::TimeZone['America/Chicago'].parse("2014-11-01 13:20:47") json = Oj.dump(now, mode: :object, time_format: :xmlschema) #puts "*** json: #{json}" oj_dump = Oj.load(json, mode: :object, time_format: :xmlschema) #puts "Now: #{now}\n Oj: #{oj_dump}" assert_equal("2014-11-01T13:20:47-05:00", oj_dump.xmlschema) end end # MimicRails oj-3.13.9/test/isolated/0000755000004100000410000000000014136373754015047 5ustar www-datawww-dataoj-3.13.9/test/isolated/test_mimic_redefine.rb0000755000004100000410000000047314136373754021401 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' class MimicRedefine < Minitest::Test def test_mimic_redefine require 'json' parser_error = JSON::ParserError Oj.mimic_JSON assert_equal(parser_error, JSON::ParserError) end end # MimicSingle oj-3.13.9/test/isolated/test_mimic_rails_after.rb0000755000004100000410000000054714136373754022115 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' begin require 'rails/all' rescue LoadError => e puts "Rails are not in the gemfile, skipping tests" Process.exit end Oj.mimic_JSON require 'isolated/shared' $rails_monkey = true class MimicRailsAfter < SharedMimicRailsTest end # MimicRailsAfter oj-3.13.9/test/isolated/test_mimic_define.rb0000755000004100000410000000107314136373754021047 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' class MimicDefine < Minitest::Test def test_mimic_define assert(defined?(JSON).nil?) Oj.mimic_JSON # Test constants assert(!defined?(JSON).nil?) assert(!defined?(JSON::ParserError).nil?) assert(Object.respond_to?(:to_json)) # Test loaded features assert(!require('json')) begin require('json_spec') assert(false, '** should raise LoadError') rescue LoadError assert(true) end end end # MimicSingle oj-3.13.9/test/isolated/test_mimic_alone.rb0000755000004100000410000000030514136373754020710 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' require 'isolated/shared' Oj.mimic_JSON class MimicAlone < SharedMimicTest end # MimicAlone oj-3.13.9/test/isolated/test_mimic_before.rb0000755000004100000410000000032614136373754021057 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' require 'isolated/shared' Oj.mimic_JSON require 'json' class MimicBefore < SharedMimicTest end # MimicBefore oj-3.13.9/test/isolated/test_mimic_as_json.rb0000755000004100000410000000167114136373754021255 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' require 'oj' class ObjectFolder < Minitest::Test class Raccoon attr_accessor :name def initialize(name) @name = name end def as_json(options={}) {:name => @name}.merge(options) end end def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_as_json_options Oj.mimic_JSON() raccoon = Raccoon.new('Rocket') json = raccoon.to_json() assert_equal(json, '{"name":"Rocket"}') json = raccoon.to_json(:occupation => 'bounty hunter') # depending on the ruby version the order of the hash members maybe different. if (json.start_with?('{"name')) assert_equal(json, '{"name":"Rocket","occupation":"bounty hunter"}') else assert_equal(json, '{"occupation":"bounty hunter","name":"Rocket"}') end end end oj-3.13.9/test/isolated/test_mimic_rails_before.rb0000755000004100000410000000055014136373754022250 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' Oj.mimic_JSON begin require 'rails/all' rescue LoadError => e puts "Rails are not in the gemfile, skipping tests" Process.exit end require 'isolated/shared' $rails_monkey = true class MimicRailsBefore < SharedMimicRailsTest end # MimicRailsBefore oj-3.13.9/test/isolated/test_mimic_after.rb0000755000004100000410000000032414136373754020714 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), '..') require 'helper' require 'isolated/shared' require 'json' Oj.mimic_JSON class MimicAfter < SharedMimicTest end # MimicAfter oj-3.13.9/test/isolated/shared.rb0000644000004100000410000001720214136373754016644 0ustar www-datawww-data# encoding: UTF-8 # The rails tests set this to true. Both Rails and the JSON gem monkey patch the # as_json methods on several base classes. Depending on which one replaces the # method last the behavior will be different. Oj.mimic_JSON abides by the same # conflicting behavior and the tests reflect that. $rails_monkey = false unless defined?($rails_monkey) class SharedMimicTest < Minitest::Test class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def as_json() {"json_class" => self.class.to_s,"x" => @x,"y" => @y} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jam def setup @default_options = Oj.default_options @time = Time.at(1400000000).utc @expected_time_string = if defined?(Rails) %{"2014-05-13T16:53:20.000Z"} else %{"2014-05-13 16:53:20 UTC"} end end def teardown Oj.default_options = @default_options end # exception def test_exception begin JSON.parse("{") puts "Failed" rescue JSON::ParserError assert(true) rescue Exception assert(false, 'Expected a JSON::ParserError') end end # dump def test_dump_string json = JSON.dump([1, true, nil, @time]) if $rails_monkey assert_equal(%{[1,true,null,#{@expected_time_string}]}, json) else assert_equal(%{[1,true,null,{"json_class":"Time","s":1400000000,"n":0}]}, json) end end def test_dump_with_options Oj.default_options= {:indent => 2} # JSON this will not change anything json = JSON.dump([1, true, nil, @time]) if $rails_monkey assert_equal(%{[ 1, true, null, #{@expected_time_string} ] }, json) else assert_equal(%{[ 1, true, null, { "json_class":"Time", "s":1400000000, "n\":0 } ] }, json) end end def test_dump_io s = StringIO.new() json = JSON.dump([1, true, nil, @time], s) assert_equal(s, json) if $rails_monkey assert_equal(%{[1,true,null,#{@expected_time_string}]}, s.string) else assert_equal(%{[1,true,null,{"json_class":"Time","s":1400000000,"n":0}]}, s.string) end end # TBD options def test_dump_struct # anonymous Struct not supported by json so name it if Object.const_defined?("Struct::Abc") s = Struct::Abc else s = Struct.new("Abc", :a, :b, :c) end o = s.new(1, 'two', [true, false]) json = JSON.dump(o) if o.respond_to?(:as_json) if $rails_monkey assert_equal(%|{"a":1,"b":"two","c":[true,false]}|, json) else assert_equal(%|{"json_class":"Struct::Abc","v":[1,"two",[true,false]]}|, json) end else j = '"' + o.to_s.gsub('"', '\\"') + '"' assert_equal(j, json) end end # load def test_load_string json = %{{"a":1,"b":[true,false]}} obj = JSON.load(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_load_io json = %{{"a":1,"b":[true,false]}} obj = JSON.load(StringIO.new(json)) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_load_proc Oj.mimic_JSON children = [] json = %{{"a":1,"b":[true,false]}} if 'rubinius' == $ruby obj = JSON.load(json) {|x| children << x } else p = Proc.new {|x| children << x } obj = JSON.load(json, p) end assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) assert([1, true, false, [true, false], { 'a' => 1, 'b' => [true, false]}] == children || [true, false, [true, false], 1, { 'a' => 1, 'b' => [true, false]}] == children, "children don't match") end def test_parse_with_quirks_mode json = %{null} assert_equal(nil, JSON.parse(json, :quirks_mode => true)) assert_raises(JSON::ParserError) { JSON.parse(json, :quirks_mode => false) } assert_raises(JSON::ParserError) { JSON.parse(json) } end def test_parse_with_empty_string Oj.mimic_JSON assert_raises(JSON::ParserError) { JSON.parse(' ') } assert_raises(JSON::ParserError) { JSON.parse("\t\t\n ") } end # [] def test_bracket_load json = %{{"a":1,"b":[true,false]}} obj = JSON[json] assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_bracket_dump json = JSON[[1, true, nil]] assert_equal(%{[1,true,null]}, json) end # generate def test_generate json = JSON.generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{"a":1,"b":[true,false]}} == json || %{{"b":[true,false],"a":1}} == json) end def test_generate_options json = JSON.generate({ 'a' => 1, 'b' => [true, false]}, :indent => "--", :array_nl => "\n", :object_nl => "#\n", :space => "*", :space_before => "~") assert(%{{# --"a"~:*1,# --"b"~:*[ ----true, ----false --]# }} == json || %{{# --"b"~:*[ ----true, ----false --],# --"a"~:*1# }} == json) end # fast_generate def test_fast_generate json = JSON.generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{"a":1,"b":[true,false]}} == json || %{{"b":[true,false],"a":1}} == json) end # pretty_generate def test_pretty_generate json = JSON.pretty_generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{ "a": 1, "b": [ true, false ] }} == json || %{{ "b": [ true, false ], "a": 1 }} == json) end # parse def test_parse json = %{{"a":1,"b":[true,false]}} obj = JSON.parse(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_parse_sym_names json = %{{"a":1,"b":[true,false]}} obj = JSON.parse(json, :symbolize_names => true) assert_equal({ :a => 1, :b => [true, false]}, obj) end def test_parse_additions jam = Jam.new(true, 58) json = Oj.dump(jam, :mode => :compat, :use_to_json => true) obj = JSON.parse(json) assert_equal(jam, obj) obj = JSON.parse(json, :create_additions => true) assert_equal(jam, obj) obj = JSON.parse(json, :create_additions => false) assert_equal({'json_class' => 'SharedMimicTest::Jam', 'x' => true, 'y' => 58}, obj) json.gsub!('json_class', 'kson_class') JSON.create_id = 'kson_class' obj = JSON.parse(json, :create_additions => true) JSON.create_id = 'json_class' assert_equal(jam, obj) end def test_parse_bang json = %{{"a":1,"b":[true,false]}} obj = JSON.parse!(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end # recurse_proc def test_recurse_proc children = [] JSON.recurse_proc({ 'a' => 1, 'b' => [true, false]}) { |x| children << x } # JRuby 1.7.0 rb_yield() is broken and converts the [true, false] array into true unless 'jruby' == $ruby assert([1, true, false, [true, false], { 'a' => 1, 'b' => [true, false]}] == children || [true, false, [true, false], 1, { 'b' => [true, false], 'a' => 1}] == children) end end # make sure to_json is defined for object. def test_mimic_to_json {'a' => 1}.to_json() Object.new().to_json() end end # SharedMimicTest if defined?(ActiveSupport) class SharedMimicRailsTest < SharedMimicTest def test_activesupport_exception begin ActiveSupport::JSON.decode("{") puts "Failed" rescue ActiveSupport::JSON.parse_error assert(true) rescue Exception assert(false, 'Expected a JSON::ParserError') end end def test_activesupport_encode Oj.default_options= {:indent => 0} json = ActiveSupport::JSON.encode([1, true, nil]) assert_equal(%{[1,true,null]}, json) end end # SharedMimicRailsTest end oj-3.13.9/test/test_object.rb0000755000004100000410000006211514136373754016105 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) require 'helper' class ObjectJuice < Minitest::Test class Jeez attr_accessor :x, :y, :_z def initialize(x, y) @x = x @y = y @_z = x.to_s end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def to_json(*a) %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jeez class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end # Jam class Jazz < Jam def initialize(x, y) super end def to_hash() { 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end # Jazz class Orange < Jam def initialize(x, y) super end def as_json() { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end module One module Two module Three class Deep def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_hash() {'json_class' => "#{self.class.name}"} end def to_json(*a) %{{"json_class":"#{self.class.name}"}} end def self.json_create(h) self.new() end end # Deep end # Three end # Two class Stuck2 < Struct.new(:a, :b) end end # One class Stuck < Struct.new(:a, :b) end class Strung < String def initialize(str, safe) super(str) @safe = safe end def safe?() @safe end def self.create(str, safe) new(str, safe) end def eql?(o) super && self.class == o.class && @safe == o.safe? end alias == eql? def inspect() return super + '(' + @safe + ')' end end class AutoStrung < String attr_accessor :safe def initialize(str, safe) super(str) @safe = safe end def eql?(o) self.class == o.class && super(o) && @safe == o.safe end alias == eql? end class AutoArray < Array attr_accessor :safe def initialize(a, safe) super(a) @safe = safe end def eql?(o) self.class == o.class && super(o) && @safe == o.safe end alias == eql? end class AutoHash < Hash attr_accessor :safe def initialize(h, safe) super(h) @safe = safe end def eql?(o) self.class == o.class && super(o) && @safe == o.safe end alias == eql? end class Raw attr_accessor :json def initialize(j) @json = j end def to_json(*a) @json end def self.create(h) h end end # Raw module Ichi module Ni def self.direct(h) h end module San class Shi attr_accessor :hash def initialize(h) @hash = h end def dump() @hash end end # Shi end # San end # Ni end # Ichi def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) dump_and_load(1/0.0, false) # NaN does not always == NaN json = Oj.dump(0/0.0, :mode => :object) assert_equal('3.3e14159265358979323846', json) loaded = Oj.load(json); assert_equal(true, loaded.nan?) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_symbol dump_and_load(:abc, false) dump_and_load(":abc", false) dump_and_load(':xyz'.to_sym, false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } dump_and_load("ぴーたー", false) Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) dump_and_load("ぴーたー", false) Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.object_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_object dump_and_load(BigDecimal('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal('80.51') json = Oj.dump(orig, :mode => :object, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :object, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) # Infinity is the same for Float and BigDecimal json = Oj.dump(BigDecimal('Infinity'), :mode => :object) assert_equal('Infinity', json) json = Oj.dump(BigDecimal('-Infinity'), :mode => :object) assert_equal('-Infinity', json) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.object_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.object_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.object_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end def test_class_object dump_and_load(ObjectJuice, false) end def test_module_object dump_and_load(One, false) end def test_non_str_hash_object json = Oj.dump({ 1 => true, :sim => nil }, :mode => :object) h = Oj.load(json, :mode => :strict) assert_equal({"^#1" => [1, true], ":sim" => nil}, h) h = Oj.load(json, :mode => :object) assert_equal({ 1 => true, :sim => nil }, h) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_json_module_object obj = One::Two::Three::Deep.new() dump_and_load(obj, false) end def test_xml_time t = Time.new(2015, 1, 5, 21, 37, 7.123456789, -8 * 3600) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :xmlschema) #puts "*** json for test_xml_time '#{json}'" loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_xml_time_utc t = Time.utc(2015, 1, 5, 21, 37, 7.123456789) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :xmlschema) loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_ruby_time t = Time.new(2015, 1, 5, 21, 37, 7.123456789, -8 * 3600) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :ruby) #puts "*** json for test_ruby_time '#{json}'" loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_ruby_time_12345 t = Time.new(2015, 1, 5, 21, 37, 7.123456789, 12345/60*60) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :ruby) #puts "*** json for test_ruby_time '#{json}'" loaded = Oj.object_load(json); #puts "*** loaded: #{loaded}" assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_ruby_time_utc t = Time.utc(2015, 1, 5, 21, 37, 7.123456789) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :ruby) #puts json loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_time_early # Windows does not support dates before 1970. return if RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ t = Time.new(1954, 1, 5, 21, 37, 7.123456789, -8 * 3600) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :unix_zone) #puts json loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_time_unix_zone t = Time.new(2015, 1, 5, 21, 37, 7.123456789, -8 * 3600) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :unix_zone) #puts json loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_time_unix_zone_12345 t = Time.new(2015, 1, 5, 21, 37, 7.123456789, 12345) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :unix_zone) #puts json loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_time_unix_zone_utc t = Time.utc(2015, 1, 5, 21, 37, 7.123456789) # The fractional seconds are not always recreated exactly which causes a # mismatch so instead the seconds, nsecs, and gmt_offset are checked # separately along with utc. json = Oj.dump(t, :mode => :object, :time_format => :unix_zone) #puts json loaded = Oj.object_load(json); assert_equal(t.tv_sec, loaded.tv_sec) if t.respond_to?(:tv_nsec) assert_equal(t.tv_nsec, loaded.tv_nsec) else assert_equal(t.tv_usec, loaded.tv_usec) end assert_equal(t.utc?, loaded.utc?) assert_equal(t.utc_offset, loaded.utc_offset) end def test_json_object obj = Jeez.new(true, 58) dump_and_load(obj, false) end def test_json_object_create_deep obj = One::Two::Three::Deep.new() dump_and_load(obj, false) end def test_json_object_bad json = %{{"^o":"Junk","x":true}} begin Oj.object_load(json) rescue Exception => e assert_equal("ArgumentError", e.class().name) return end assert(false, "*** expected an exception") end def test_json_object_not_hat_hash json = %{{"^#x":[1,2]}} h = Oj.object_load(json) assert_equal({1 => 2}, h); json = %{{"~#x":[1,2]}} h = Oj.object_load(json) assert_equal({'~#x' => [1,2]}, h); end def test_json_struct obj = Stuck.new(false, 7) dump_and_load(obj, false) end def test_json_struct2 obj = One::Stuck2.new(false, 7) dump_and_load(obj, false) end def test_json_anonymous_struct s = Struct.new(:x, :y) obj = s.new(1, 2) json = Oj.dump(obj, :indent => 2, :mode => :object) #puts json loaded = Oj.object_load(json); assert_equal(obj.members, loaded.members) assert_equal(obj.values, loaded.values) end def test_json_non_str_hash obj = { 59 => "young", false => true } dump_and_load(obj, false) end def test_mixed_hash_object Oj.default_options = { :mode => :object } json = Oj.dump({ 1 => true, 'nil' => nil, :sim => 4 }) h = Oj.object_load(json) assert_equal({ 1 => true, 'nil' => nil, :sim => 4 }, h) end def test_json_object_object obj = Jeez.new(true, 58) json = Oj.dump(obj, mode: :object, indent: 2, ignore_under: true) assert(%{{ "^o":"ObjectJuice::Jeez", "x":true, "y":58 } } == json || %{{ "^o":"ObjectJuice::Jeez", "y":58, "x":true } } == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end def test_to_hash_object_object obj = Jazz.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"ObjectJuice::Jazz", "x":true, "y":58 } } == json || %{{ "^o":"ObjectJuice::Jazz", "y":58, "x":true } } == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end def test_as_json_object_object obj = Orange.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"ObjectJuice::Orange", "x":true, "y":58 } } == json || %{{ "^o":"ObjectJuice::Orange", "y":58, "x":true } } == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end def test_object_object_no_cache obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"ObjectJuice::Jam", "x":true, "y":58 } } == json || %{{ "^o":"ObjectJuice::Jam", "y":58, "x":true } } == json) obj2 = Oj.load(json, :mode => :object, :class_cache => false) assert_equal(obj, obj2) end def test_ignore obj = Jeez.new(true, 58) json = Oj.dump({ 'a' => 7, 'b' => obj }, :mode => :object, :indent => 2, :ignore => [ Jeez ]) assert_equal(%|{ "a":7 } |, json) end def test_exception err = nil begin raise StandardError.new('A Message') rescue Exception => e err = e end json = Oj.dump(err, :mode => :object, :indent => 2) #puts "*** #{json}" e2 = Oj.load(json, :mode => :strict) assert_equal(err.class.to_s, e2['^o']) assert_equal(err.message, e2['~mesg']) assert_equal(err.backtrace, e2['~bt']) e2 = Oj.load(json, :mode => :object) if 'rubinius' == $ruby assert_equal(e.class, e2.class); assert_equal(e.message, e2.message); assert_equal(e.backtrace, e2.backtrace); else assert_equal(e, e2); end end class SubX < Exception def initialize super("sub") @xyz = 123 end end def test_exception_subclass err = nil begin raise SubX.new rescue Exception => e err = e end json = Oj.dump(err, :mode => :object, :indent => 2) #puts "*** #{json}" e2 = Oj.load(json, :mode => :strict) assert_equal(err.class.to_s, e2['^o']) assert_equal(err.message, e2['~mesg']) assert_equal(err.backtrace, e2['~bt']) e2 = Oj.load(json, :mode => :object) assert_equal(e, e2); end def test_range_object Oj.default_options = { :mode => :object } json = Oj.dump(1..7, :mode => :object, :indent => 0) if 'rubinius' == $ruby assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json) else assert_equal(%{{"^u":["Range",1,7,false]}}, json) end dump_and_load(1..7, false) dump_and_load(1..1, false) dump_and_load(1...7, false) end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :mode => :object, :indent => 2, :circular => true) h2 = Oj.object_load(json, :circular => true) assert_equal(h2['b'].__id__, h2.__id__) end def test_json_object_missing_fields json = %{{ "^u": [ "ObjectJuice::Stuck",1]}} obj = Oj.load(json, mode: :object) assert_nil(obj['b']) end def test_circular_array a = [7] a << a json = Oj.dump(a, :mode => :object, :indent => 2, :circular => true) a2 = Oj.object_load(json, :circular => true) assert_equal(a2[1].__id__, a2.__id__) end def test_circular_array2 a = [7] a << a json = Oj.dump(a, :mode => :object, :indent => 2, :circular => true) assert_equal(%{[ "^i1", 7, "^r1" ] }, json) a2 = Oj.load(json, :mode => :object, :circular => true) assert_equal(a2[1].__id__, a2.__id__) end def test_circular_array3 a = ['^r1'] json = Oj.dump(a, mode: :object, circular: true) assert_equal(%{["^i1","\\u005er1"]}, json) a2 = Oj.load(json, mode: :object, circular: true) assert_equal(a, a2) end def test_circular_hash2 h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :mode => :object, :indent => 2, :circular => true) ha = Oj.load(json, :mode => :strict) assert_equal({'^i' => 1, 'a' => 7, 'b' => '^r1'}, ha) Oj.load(json, :mode => :object, :circular => true) assert_equal(h['b'].__id__, h.__id__) end def test_circular_object obj = Jeez.new(nil, 58) obj.x = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) obj2 = Oj.object_load(json, :circular => true) assert_equal(obj2.x.__id__, obj2.__id__) end def test_circular_object2 obj = Jam.new(nil, 58) obj.x = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) assert(%{{ "^o":"ObjectJuice::Jam", "^i":1, "x":"^r1", "y":58 } } == json || %{{ "^o":"ObjectJuice::Jam", "^i":1, "y":58, "x":"^r1" } } == json) obj2 = Oj.load(json, :mode => :object, :circular => true) assert_equal(obj2.x.__id__, obj2.__id__) end def test_circular h = { 'a' => 7 } obj = Jeez.new(h, 58) obj.x['b'] = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) Oj.object_load(json, :circular => true) assert_equal(obj.x.__id__, h.__id__) assert_equal(h['b'].__id__, obj.__id__) end def test_circular2 h = { 'a' => 7 } obj = Jam.new(h, 58) obj.x['b'] = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) ha = Oj.load(json, :mode => :strict) assert_equal({'^o' => 'ObjectJuice::Jam', '^i' => 1, 'x' => { '^i' => 2, 'a' => 7, 'b' => '^r1' }, 'y' => 58 }, ha) Oj.load(json, :mode => :object, :circular => true) assert_equal(obj.x.__id__, h.__id__) assert_equal(h['b'].__id__, obj.__id__) end def test_omit_nil jam = Jam.new({'a' => 1, 'b' => nil }, nil) json = Oj.dump(jam, :omit_nil => true, :mode => :object) assert_equal(%|{"^o":"ObjectJuice::Jam","x":{"a":1}}|, json) end def test_odd_date dump_and_load(Date.new(2012, 6, 19), false) end def test_odd_datetime dump_and_load(DateTime.new(2012, 6, 19, 13, 5, Rational(4, 3)), false) dump_and_load(DateTime.new(2012, 6, 19, 13, 5, Rational(7123456789, 1000000000)), false) end def test_bag json = %{{ "^o":"ObjectJuice::Jem", "x":true, "y":58 }} obj = Oj.load(json, :mode => :object, :auto_define => true) assert_equal('ObjectJuice::Jem', obj.class.name) assert_equal(true, obj.x) assert_equal(58, obj.y) end def test_odd_string Oj.register_odd(Strung, Strung, :create, :to_s, 'safe?') s = Strung.new("Pete", true) dump_and_load(s, false) end def test_odd_date_replaced Oj.register_odd(Date, Date, :jd, :jd) json = Oj.dump(Date.new(2015, 3, 7), :mode => :object) assert_equal(%|{"^O":"Date","jd":2457089}|, json) dump_and_load(Date.new(2012, 6, 19), false) end def test_odd_raw Oj.register_odd_raw(Raw, Raw, :create, :to_json) json = Oj.dump(Raw.new(%|{"a":1}|), :mode => :object) assert_equal(%|{"^O":"ObjectJuice::Raw","to_json":{"a":1}}|, json) h = Oj.load(json, :mode => :object) assert_equal({'a' => 1}, h) end def test_odd_mod Oj.register_odd(Ichi::Ni, Ichi::Ni, :direct, :dump) json = Oj.dump(Ichi::Ni::San::Shi.new({'a' => 1}), :mode => :object) assert_equal(%|{"^O":"ObjectJuice::Ichi::Ni::San::Shi","dump":{"a":1}}|, json) h = Oj.load(json, :mode => :object) assert_equal({'a' => 1}, h) end def test_auto_string s = AutoStrung.new("Pete", true) dump_and_load(s, false) end def test_auto_array a = AutoArray.new([1, 'abc', nil], true) dump_and_load(a, false) end def test_auto_hash h = AutoHash.new(nil, true) h['a'] = 1 h['b'] = 2 dump_and_load(h, false) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2, :mode => :object) puts json if trace loaded = Oj.object_load(json); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/test_saj.rb0000755000004100000410000001052314136373754015410 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' $json = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class AllSaj < Oj::Saj attr_accessor :calls def initialize() @calls = [] end def hash_start(key) @calls << [:hash_start, key] end def hash_end(key) @calls << [:hash_end, key] end def array_start(key) @calls << [:array_start, key] end def array_end(key) @calls << [:array_end, key] end def add_value(value, key) @calls << [:add_value, value, key] end def error(message, line, column) @calls << [:error, message, line, column] end end # AllSaj class SajTest < Minitest::Test def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_nil handler = AllSaj.new() json = %{null} Oj.saj_parse(handler, json) assert_equal([[:add_value, nil, nil]], handler.calls) end def test_true handler = AllSaj.new() json = %{true} Oj.saj_parse(handler, json) assert_equal([[:add_value, true, nil]], handler.calls) end def test_false handler = AllSaj.new() json = %{false} Oj.saj_parse(handler, json) assert_equal([[:add_value, false, nil]], handler.calls) end def test_string handler = AllSaj.new() json = %{"a string"} Oj.saj_parse(handler, json) assert_equal([[:add_value, 'a string', nil]], handler.calls) end def test_fixnum handler = AllSaj.new() json = %{12345} Oj.saj_parse(handler, json) assert_equal([[:add_value, 12345, nil]], handler.calls) end def test_float handler = AllSaj.new() json = %{12345.6789} Oj.saj_parse(handler, json) assert_equal([[:add_value, 12345.6789, nil]], handler.calls) end def test_float_exp handler = AllSaj.new() json = %{12345.6789e7} Oj.saj_parse(handler, json) assert_equal(1, handler.calls.size) assert_equal(:add_value, handler.calls[0][0]) assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i) end def test_array_empty handler = AllSaj.new() json = %{[]} Oj.saj_parse(handler, json) assert_equal([[:array_start, nil], [:array_end, nil]], handler.calls) end def test_array handler = AllSaj.new() json = %{[true,false]} Oj.saj_parse(handler, json) assert_equal([[:array_start, nil], [:add_value, true, nil], [:add_value, false, nil], [:array_end, nil]], handler.calls) end def test_hash_empty handler = AllSaj.new() json = %{{}} Oj.saj_parse(handler, json) assert_equal([[:hash_start, nil], [:hash_end, nil]], handler.calls) end def test_hash handler = AllSaj.new() json = %{{"one":true,"two":false}} Oj.saj_parse(handler, json) assert_equal([[:hash_start, nil], [:add_value, true, 'one'], [:add_value, false, 'two'], [:hash_end, nil]], handler.calls) end def test_full handler = AllSaj.new() Oj.saj_parse(handler, $json) assert_equal([[:hash_start, nil], [:array_start, 'array'], [:hash_start, nil], [:add_value, 3, 'num'], [:add_value, 'message', 'string'], [:hash_start, 'hash'], [:hash_start, 'h2'], [:array_start, 'a'], [:add_value, 1, nil], [:add_value, 2, nil], [:add_value, 3, nil], [:array_end, 'a'], [:hash_end, 'h2'], [:hash_end, 'hash'], [:hash_end, nil], [:array_end, 'array'], [:add_value, true, 'boolean'], [:hash_end, nil]], handler.calls) end def test_fixnum_bad handler = AllSaj.new() json = %{12345xyz} Oj.saj_parse(handler, json) assert_equal([:add_value, 12345, nil], handler.calls.first) type, message, line, column = handler.calls.last assert_equal([:error, 1, 6], [type, line, column]) assert_match(%r{invalid format, extra characters at line 1, column 6 \[(?:[a-z\.]+/)*saj\.c:\d+\]}, message) end end oj-3.13.9/test/perf_wab.rb0000755000004100000410000001024014136373754015355 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' $verbose = false $indent = 0 $iter = 20000 $with_bignum = false $with_nums = true $size = 0 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = { a: 'Alpha', # string b: true, # boolean c: 12345, # number d: [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array e: { zero: nil, one: 1, two: 2, three: [3], four: [0, 1, 2, 3, 4] }, # hash f: nil, # nil h: { a: { b: { c: { d: {e: { f: { g: nil }}}}}}}, # deep hash, not that deep i: [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj[:g] = 12345678901234567890123456789 if $with_bignum Oj.default_options = { :indent => $indent, :mode => :wab } if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end $json = Oj.dump($obj) $obj_json = Oj.dump($obj, :mode => :object) #puts "*** size: #{$obj_json.size}" #puts "*** #{$obj_json}" $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj:wab', $obj, 'load', 'dump') { |o| Oj.wab_load(Oj.dump(o, :mode => :wab)) } capture_error('Yajl', $obj, 'encode', 'parse') { |o| require 'yajl'; Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| require 'json' require 'json/ext' JSON.generator = JSON::Ext::Generator JSON.parser = JSON::Ext::Parser JSON.parse(JSON.generate(o)) } capture_error('JSON::Pure', $obj, 'generate', 'parse') { |o| require 'json/pure' JSON.generator = JSON::Pure::Generator JSON.parser = JSON::Pure::Parser JSON.parse(JSON.generate(o)) } if $verbose puts "json:\n#{$json}\n" puts "object json:\n#{$obj_json}\n" puts "Oj loaded object:\n#{Oj.wab_load($json)}\n" puts "Yajl loaded object:\n#{Yajl::Parser.parse($json)}\n" puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n" end puts '-' * 80 puts "Wab Parse Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'parse') { JSON.parse($json) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } end unless $failed.has_key?('JSON::Pure') perf.add('JSON::Pure', 'parse') { JSON.parse($json) } perf.before('JSON::Pure') { JSON.parser = JSON::Pure::Parser } end unless $failed.has_key?('Oj:wab') perf.add('Oj:wab', 'wab_load') { Oj.wab_load($json) } end perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.run($iter) puts '-' * 80 puts "Wab Dump Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'dump') { JSON.generate($obj) } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } end unless $failed.has_key?('JSON::Pure') perf.add('JSON::Pure', 'generate') { JSON.generate($obj) } perf.before('JSON::Pure') { JSON.generator = JSON::Pure::Generator } end unless $failed.has_key?('Oj:wab') perf.add('Oj:wab', 'dump') { Oj.dump($obj, :mode => :wab) } end perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) } unless $failed.has_key?('Yajl') perf.run($iter) puts puts '-' * 80 puts unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/activesupport5/0000755000004100000410000000000014136373754016240 5ustar www-datawww-dataoj-3.13.9/test/activesupport5/encoding_test.rb0000644000004100000410000003605614136373754021424 0ustar www-datawww-data# frozen_string_literal: true require "securerandom" require_relative "abstract_unit" require "active_support/core_ext/string/inflections" require "active_support/core_ext/regexp" require "active_support/json" require "active_support/time" require_relative "time_zone_test_helpers" require_relative "encoding_test_cases" require 'oj' # Sets the ActiveSupport encoder to be Oj and also wraps the setting of # globals. Oj::Rails.set_encoder() #Oj::Rails.optimize(Hash, Array, BigDecimal, Time, Range, Regexp, ActiveSupport::TimeWithZone) Oj::Rails.optimize() class TestJSONEncoding < ActiveSupport::TestCase include TimeZoneTestHelpers # Added for testing if Oj is used. test "oj is used as an encoder" do assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder end def sorted_json(json) if json.start_with?("{") && json.end_with?("}") "{" + json[1..-2].split(",").sort.join(",") + "}" else json end end JSONTest::EncodingTestCases.constants.each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do begin prev = ActiveSupport.use_standard_json_time_format standard_class_tests = /Standard/.match?(class_tests) ActiveSupport.escape_html_entities_in_json = !standard_class_tests ActiveSupport.use_standard_json_time_format = standard_class_tests JSONTest::EncodingTestCases.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end ensure ActiveSupport.escape_html_entities_in_json = false ActiveSupport.use_standard_json_time_format = prev end end end def test_process_status rubinius_skip "https://github.com/rubinius/rubinius/issues/3334" # There doesn't seem to be a good way to get a handle on a Process::Status object without actually # creating a child process, hence this to populate $? system("not_a_real_program_#{SecureRandom.hex}") assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?) end def test_hash_encoding assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b) assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1) assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2]) assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2) assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d)) end def test_hash_keys_encoding ActiveSupport.escape_html_entities_in_json = true assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>") ensure ActiveSupport.escape_html_entities_in_json = false end def test_utf8_string_encoded_properly # The original test seems to expect that # ActiveSupport.escape_html_entities_in_json reverts to true even after # being set to false. I haven't been able to figure that out so the value is # set to true, the default, before running the test. This might be wrong but # for now it will have to do. ActiveSupport.escape_html_entities_in_json = true result = ActiveSupport::JSON.encode("€2.99") assert_equal '"€2.99"', result assert_equal(Encoding::UTF_8, result.encoding) result = ActiveSupport::JSON.encode("✎☺") assert_equal '"✎☺"', result assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes s = "二".encode("Shift_JIS") result = ActiveSupport::JSON.encode(s) assert_equal '"二"', result assert_equal Encoding::UTF_8, result.encoding end def test_wide_utf8_chars w = "𠜎" result = ActiveSupport::JSON.encode(w) assert_equal '"𠜎"', result end def test_wide_utf8_roundtrip hash = { string: "𐒑" } json = ActiveSupport::JSON.encode(hash) decoded_hash = ActiveSupport::JSON.decode(json) assert_equal "𐒑", decoded_hash["string"] end def test_hash_key_identifiers_are_always_quoted values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" } assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) end def test_hash_should_allow_key_filtering_with_only assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" }) end def test_hash_should_allow_key_filtering_with_except assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] }) end def test_time_to_json_includes_local_offset with_standard_json_time_format(true) do with_env_tz "US/Eastern" do assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10)) end end end def test_hash_with_time_to_json with_standard_json_time_format(false) do assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json end end def test_nested_hash_with_float assert_nothing_raised do hash = { "CHI" => { display_name: "chicago", latitude: 123.234 } } ActiveSupport::JSON.encode(hash) end end def test_hash_like_with_options h = JSONTest::Hashlike.new json = h.to_json only: [:foo] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_object_to_json_with_options obj = Object.new obj.instance_variable_set :@foo, "hello" obj.instance_variable_set :@bar, "world" json = obj.to_json only: ["foo"] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_struct_to_json_with_options struct = Struct.new(:foo, :bar).new struct.foo = "hello" struct.bar = "world" json = struct.to_json only: [:foo] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_hash_should_pass_encoding_options_to_children_in_as_json person = { name: "John", address: { city: "London", country: "UK" } } json = person.as_json only: [:address, :city] assert_equal({ "address" => { "city" => "London" } }, json) end def test_hash_should_pass_encoding_options_to_children_in_to_json person = { name: "John", address: { city: "London", country: "UK" } } json = person.to_json only: [:address, :city] assert_equal(%({"address":{"city":"London"}}), json) end def test_array_should_pass_encoding_options_to_children_in_as_json people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_array_should_pass_encoding_options_to_children_in_to_json people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end People = Class.new(BasicObject) do include Enumerable def initialize @people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] end def each(*, &blk) @people.each do |p| yield p if blk p end.each end end def test_enumerable_should_generate_json_with_as_json json = People.new.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_generate_json_with_to_json json = People.new.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end def test_enumerable_should_pass_encoding_options_to_children_in_as_json json = People.new.each.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_pass_encoding_options_to_children_in_to_json json = People.new.each.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end class CustomWithOptions attr_accessor :foo, :bar def as_json(options = {}) options[:only] = %w(foo bar) super(options) end end def test_hash_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } } assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" }, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json)) end def test_array_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" array = [f, { "foo" => "other_foo", "test" => "other_test" }] assert_equal([{ "foo" => "hello", "bar" => "world" }, { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json)) end class OptionsTest def as_json(options = :default) options end end def test_hash_as_json_without_options json = { foo: OptionsTest.new }.as_json assert_equal({ "foo" => :default }, json) end def test_array_as_json_without_options json = [ OptionsTest.new ].as_json assert_equal([:default], json) end def test_struct_encoding Struct.new("UserNameAndEmail", :name, :email) Struct.new("UserNameAndDate", :name, :date) Struct.new("Custom", :name, :sub) user_email = Struct::UserNameAndEmail.new "David", "sample@example.com" user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01) custom = Struct::Custom.new "David", user_birthday json_strings = "" json_string_and_date = "" json_custom = "" assert_nothing_raised do json_strings = user_email.to_json json_string_and_date = user_birthday.to_json json_custom = custom.to_json end assert_equal({ "name" => "David", "sub" => { "name" => "David", "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom)) assert_equal({ "name" => "David", "email" => "sample@example.com" }, ActiveSupport::JSON.decode(json_strings)) assert_equal({ "name" => "David", "date" => "2010-01-01" }, ActiveSupport::JSON.decode(json_string_and_date)) end def test_nil_true_and_false_represented_as_themselves assert_nil nil.as_json assert_equal true, true.as_json assert_equal false, false.as_json end class HashWithAsJson < Hash attr_accessor :as_json_called def initialize(*) super end def as_json(options = {}) @as_json_called = true super end end def test_json_gem_dump_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h) assert_nil h.as_json_called end def test_json_gem_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h) assert_nil h.as_json_called end def test_json_gem_pretty_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal < Float::INFINITY } end end def test_to_json_works_when_as_json_returns_infinite_number assert_equal '{"number":null}', InfiniteNumber.new.to_json end class NaNNumber def as_json(options = nil) { "number" => Float::NAN } end end def test_to_json_works_when_as_json_returns_NaN_number assert_equal '{"number":null}', NaNNumber.new.to_json end def test_to_json_works_on_io_objects assert_equal STDOUT.to_s.to_json, STDOUT.to_json end private def object_keys(json_object) json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort end def with_standard_json_time_format(boolean = true) old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean yield ensure ActiveSupport.use_standard_json_time_format = old end def with_time_precision(value) old_value = ActiveSupport::JSON::Encoding.time_precision ActiveSupport::JSON::Encoding.time_precision = value yield ensure ActiveSupport::JSON::Encoding.time_precision = old_value end end oj-3.13.9/test/activesupport5/time_zone_test_helpers.rb0000644000004100000410000000170714136373754023344 0ustar www-datawww-data# frozen_string_literal: true module TimeZoneTestHelpers def with_tz_default(tz = nil) old_tz = Time.zone Time.zone = tz yield ensure Time.zone = old_tz end def with_env_tz(new_tz = "US/Eastern") old_tz, ENV["TZ"] = ENV["TZ"], new_tz yield ensure old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ") end def with_preserve_timezone(value) old_preserve_tz = ActiveSupport.to_time_preserves_timezone ActiveSupport.to_time_preserves_timezone = value yield ensure ActiveSupport.to_time_preserves_timezone = old_preserve_tz end def with_tz_mappings(mappings) old_mappings = ActiveSupport::TimeZone::MAPPING.dup ActiveSupport::TimeZone.clear ActiveSupport::TimeZone::MAPPING.clear ActiveSupport::TimeZone::MAPPING.merge!(mappings) yield ensure ActiveSupport::TimeZone.clear ActiveSupport::TimeZone::MAPPING.clear ActiveSupport::TimeZone::MAPPING.merge!(old_mappings) end end oj-3.13.9/test/activesupport5/abstract_unit.rb0000644000004100000410000000240314136373754021426 0ustar www-datawww-data# frozen_string_literal: true ORIG_ARGV = ARGV.dup require "active_support/core_ext/kernel/reporting" silence_warnings do Encoding.default_internal = Encoding::UTF_8 Encoding.default_external = Encoding::UTF_8 end require "active_support/testing/autorun" require "active_support/testing/method_call_assertions" ENV["NO_RELOAD"] = "1" require "active_support" Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Default to old to_time behavior but allow running tests with new behavior ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1" # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false class ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions # Skips the current run on Rubinius using Minitest::Assertions#skip private def rubinius_skip(message = "") skip message if RUBY_ENGINE == "rbx" end # Skips the current run on JRuby using Minitest::Assertions#skip private def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end def frozen_error_class Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError end end oj-3.13.9/test/activesupport5/encoding_test_cases.rb0000644000004100000410000001012314136373754022565 0ustar www-datawww-data# frozen_string_literal: true require "bigdecimal" require "date" require "time" require "pathname" require "uri" module JSONTest class Foo def initialize(a, b) @a, @b = a, b end end class Hashlike def to_hash { foo: "hello", bar: "world" } end end class Custom def initialize(serialized) @serialized = serialized end def as_json(options = nil) @serialized end end MyStruct = Struct.new(:name, :value) do def initialize(*) @unused = "unused instance variable" super end end module EncodingTestCases TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] NumericTests = [[ 1, %(1) ], [ 2.5, %(2.5) ], [ 0.0 / 0.0, %(null) ], [ 1.0 / 0.0, %(null) ], [ -1.0 / 0.0, %(null) ], [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ], [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]] StringTests = [[ "this is the ", %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], [ "http://test.host/posts/1", %("http://test.host/posts/1")], [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ], [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ], [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]] RangeTests = [[ 1..2, %("1..2")], [ 1...2, %("1...2")], [ 1.5..2.5, %("1.5..2.5")]] SymbolTests = [[ :a, %("a") ], [ :this, %("this") ], [ :"a b", %("a b") ]] ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ], [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]] CustomTests = [[ Custom.new("custom"), '"custom"' ], [ Custom.new(nil), "null" ], [ Custom.new(:a), '"a"' ], [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]] PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]] DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]] TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]] DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]] StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]] StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]] StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]] StandardStringTests = [[ "this is the ", %("this is the ")]] end end oj-3.13.9/test/activesupport5/decoding_test.rb0000644000004100000410000001344014136373754021402 0ustar www-datawww-data# frozen_string_literal: true require_relative "abstract_unit" require "active_support/json" require "active_support/time" require_relative "time_zone_test_helpers" require 'oj' Oj::Rails.set_decoder() class TestJSONDecoding < ActiveSupport::TestCase include TimeZoneTestHelpers # Added for testing if Oj is used. test "oj is used as an encoder" do assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder end class Foo def self.json_create(object) "Foo" end end TESTS = { %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } }, %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } }, %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } }, %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] }, %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] }, %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" }, %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" }, # multibyte %({"matzue": "松江", "asakusa": "浅草"}) => { "matzue" => "松江", "asakusa" => "浅草" }, %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) }, %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) }, %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], # no time zone %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") }, # invalid date %({"a": "1089-10-40"}) => { "a" => "1089-10-40" }, # xmlschema date notation %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") }, %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) }, %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) }, %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) }, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " }, %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" }, %([]) => [], %({}) => {}, %({"a":1}) => { "a" => 1 }, %({"a": ""}) => { "a" => "" }, %({"a":"\\""}) => { "a" => "\"" }, %({"a": null}) => { "a" => nil }, %({"a": true}) => { "a" => true }, %({"a": false}) => { "a" => false }, '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" }, %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" }, %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "" }, '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" }, %q({"a": "\u003cbr /\u003e"}) => { "a" => "
" }, %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["", "", ""] }, # test combination of dates and escaped or unicode encoded data in arrays %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => [{ "d" => Date.new(1970, 1, 1), "s" => " escape" }, { "d" => Date.new(1970, 1, 1), "s" => " escape" }], %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }, { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }], # tests escaping of "\n" char with Yaml backend %q({"a":"\n"}) => { "a" => "\n" }, %q({"a":"\u000a"}) => { "a" => "\n" }, %q({"a":"Line1\u000aLine2"}) => { "a" => "Line1\nLine2" }, # prevent json unmarshalling '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class" => "TestJSONDecoding::Foo" }, # json "fragments" - these are invalid JSON, but ActionPack relies on this '"a string"' => "a string", "1.1" => 1.1, "1" => 1, "-1" => -1, "true" => true, "false" => false, "null" => nil } TESTS.each_with_index do |(json, expected), index| fail_message = "JSON decoding failed for #{json}" test "json decodes #{index}" do with_tz_default "Eastern Time (US & Canada)" do with_parse_json_times(true) do silence_warnings do if expected.nil? assert_nil ActiveSupport::JSON.decode(json), fail_message else assert_equal expected, ActiveSupport::JSON.decode(json), fail_message end end end end end end test "json decodes time json with time parsing disabled" do with_parse_json_times(false) do expected = { "a" => "2007-01-01 01:12:34 Z" } assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end end def test_failed_json_decoding assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } end def test_cannot_pass_unsupported_options assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) } end private def with_parse_json_times(value) old_value = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = value yield ensure ActiveSupport.parse_json_times = old_value end end oj-3.13.9/test/activesupport5/test_helper.rb0000644000004100000410000000501014136373754021077 0ustar www-datawww-data# frozen_string_literal: true gem "minitest" # make sure we get the gem, not stdlib require "minitest" require "active_support/testing/tagged_logging" require "active_support/testing/setup_and_teardown" require "active_support/testing/assertions" require "active_support/testing/deprecation" require "active_support/testing/declarative" require "active_support/testing/isolation" require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" module ActiveSupport class TestCase < ::Minitest::Test Assertion = Minitest::Assertion class << self # Sets the order in which test cases are run. # # ActiveSupport::TestCase.test_order = :random # => :random # # Valid values are: # * +:random+ (to run tests in random order) # * +:parallel+ (to run tests in parallel) # * +:sorted+ (to run tests alphabetically by method name) # * +:alpha+ (equivalent to +:sorted+) def test_order=(new_order) ActiveSupport.test_order = new_order end # Returns the order in which test cases are run. # # ActiveSupport::TestCase.test_order # => :random # # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. # Defaults to +:random+. def test_order ActiveSupport.test_order ||= :random end end alias_method :method_name, :name include ActiveSupport::Testing::TaggedLogging prepend ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation include ActiveSupport::Testing::TimeHelpers include ActiveSupport::Testing::FileFixtures extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods alias :assert_raise :assert_raises alias :assert_not_empty :refute_empty alias :assert_not_equal :refute_equal alias :assert_not_in_delta :refute_in_delta alias :assert_not_in_epsilon :refute_in_epsilon alias :assert_not_includes :refute_includes alias :assert_not_instance_of :refute_instance_of alias :assert_not_kind_of :refute_kind_of alias :assert_no_match :refute_match alias :assert_not_nil :refute_nil alias :assert_not_operator :refute_operator alias :assert_not_predicate :refute_predicate alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same ActiveSupport.run_load_hooks(:active_support_test_case, self) end end oj-3.13.9/test/perf_object.rb0000755000004100000410000000760214136373754016062 0ustar www-datawww-data#!/usr/bin/env ruby $: << '.' $: << '../lib' $: << '../ext' if __FILE__ == $0 if (i = ARGV.index('-I')) x,path = ARGV.slice!(i, 2) $: << path end end require 'optparse' require 'ox' require 'oj' require 'perf' require 'sample' require 'files' $circular = false $indent = 0 $allow_gc = true do_sample = false do_files = false do_load = false do_dump = false do_read = false do_write = false $iter = 1000 $mult = 1 opts = OptionParser.new opts.on("-c", "circular options") { $circular = true } opts.on("-x", "use sample instead of files") { do_sample = true } opts.on("-g", "no GC during parsing") { $allow_gc = false } opts.on("-s", "load and dump as sample Ruby object") { do_sample = true } opts.on("-f", "load and dump as files Ruby object") { do_files = true } opts.on("-l", "load") { do_load = true } opts.on("-d", "dump") { do_dump = true } opts.on("-r", "read") { do_read = true } opts.on("-w", "write") { do_write = true } opts.on("-a", "load, dump, read and write") { do_load = true; do_dump = true; do_read = true; do_write = true } opts.on("-i", "--iterations [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-m", "--multiply [Int]", Integer, "multiplier") { |i| $mult = i } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = nil $xml = nil $mars = nil $json = nil unless do_load || do_dump || do_read || do_write do_load = true do_dump = true do_read = true do_write = true end # prepare all the formats for input if files.empty? $obj = [] $mult.times do $obj << (do_sample ? sample_doc(2) : files('..')) end $mars = Marshal.dump($obj) $xml = Ox.dump($obj, :indent => $indent, :circular => $circular) $json = Oj.dump($obj, :indent => $indent, :circular => $circular, :mode => :object) File.open('sample.xml', 'w') { |f| f.write($xml) } File.open('sample.json', 'w') { |f| f.write($json) } File.open('sample.marshal', 'w') { |f| f.write($mars) } else puts "loading and parsing #{files}\n\n" data = files.map do |f| $xml = File.read(f) $obj = Ox.load($xml); $mars = Marshal.dump($obj) $json = Oj.dump($obj, :indent => $indent, :circular => $circular) end end Oj.default_options = { :mode => :object, :indent => $indent, :circular => $circular, :allow_gc => $allow_gc } #puts "json: #{$json.size}" #puts "xml: #{$xml.size}" #puts "marshal: #{$mars.size}" if do_load puts '-' * 80 puts "Load Performance" perf = Perf.new() perf.add('Oj.object', 'load') { Oj.object_load($json) } perf.add('Ox', 'load') { Ox.load($xml, :mode => :object) } perf.add('Marshal', 'load') { Marshal.load($mars) } perf.run($iter) end if do_dump puts '-' * 80 puts "Dump Performance" perf = Perf.new() perf.add('Oj', 'dump') { Oj.dump($obj) } perf.add('Ox', 'dump') { Ox.dump($obj, :indent => $indent, :circular => $circular) } perf.add('Marshal', 'dump') { Marshal.dump($obj) } perf.run($iter) end if do_read puts '-' * 80 puts "Read from file Performance" perf = Perf.new() perf.add('Oj', 'load') { Oj.load_file('sample.json') } #perf.add('Oj', 'load') { Oj.load(File.read('sample.json')) } perf.add('Ox', 'load_file') { Ox.load_file('sample.xml', :mode => :object) } perf.add('Marshal', 'load') { Marshal.load(File.new('sample.marshal')) } perf.run($iter) end if do_write puts '-' * 80 puts "Write to file Performance" perf = Perf.new() perf.add('Oj', 'to_file') { Oj.to_file('sample.json', $obj) } perf.add('Ox', 'to_file') { Ox.to_file('sample.xml', $obj, :indent => $indent, :circular => $circular) } perf.add('Marshal', 'dump') { Marshal.dump($obj, File.new('sample.marshal', 'w')) } perf.run($iter) end oj-3.13.9/test/tests.rb0000755000004100000410000000102614136373754014734 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'test_compat' require 'test_custom' require 'test_fast' require 'test_file' require 'test_gc' require 'test_hash' require 'test_null' require 'test_object' require 'test_saj' require 'test_scp' require 'test_strict' require 'test_various' require 'test_rails' require 'test_wab' require 'test_writer' require 'test_integer_range' oj-3.13.9/test/perf_simple.rb0000644000004100000410000001602614136373754016102 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'json' require 'json/pure' require 'json/ext' require 'msgpack' require 'oj' require 'ox' class Jazz def initialize() @boolean = true @number = 58 @string = "A string" @array = [true, false, nil] @hash = { 'one' => 1, 'two' => 2 } end def to_json() %{ { "boolean":#{@boolean}, "number":#{@number}, "string":#{@string}, "array":#{@array}, "hash":#{@hash}, } } end def to_hash() { 'boolean' => @boolean, 'number' => @number, 'string' => @string, 'array' => @array, 'hash' => @hash, } end def to_msgpack(out='') to_hash().to_msgpack(out) end end $indent = 2 $iter = 10000 $with_object = true $with_bignum = true $with_nums = true opts = OptionParser.new opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-o", "without objects") { $with_object = false } opts.on("-b", "without bignum") { $with_bignum = false } opts.on("-n", "without numbers") { $with_nums = false } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) if $with_nums obj = { 'a' => 'Alpha', 'b' => true, 'c' => 12345, 'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]], 'e' => { 'one' => 1, 'two' => 2 }, 'f' => nil, } obj['g'] = Jazz.new() if $with_object obj['h'] = 12345678901234567890123456789 if $with_bignum else obj = { 'a' => 'Alpha', 'b' => true, 'c' => '12345', 'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]], 'e' => { 'one' => '1', 'two' => '2' }, 'f' => nil, } end Oj.default_options = { :indent => $indent, :mode => :object } s = Oj.dump(obj) xml = Ox.dump(obj, :indent => $indent) puts # Put Oj in strict mode so it only create JSON native types instead of the # original Ruby Objects. None of the other packages other than Ox support # Object recreation so no need for Oj to do it in the performance tests. Oj.default_options = { :mode => :strict } parse_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 } start = Time.now $iter.times do Oj.load(s) end dt = Time.now - start base_dt = dt parse_results[:oj] = dt puts "%d Oj.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0] start = Time.now $iter.times do Yajl::Parser.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'Yajl' end parse_results[:yajl] = dt puts "%d Yajl::Parser.parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] begin JSON.parser = JSON::Ext::Parser start = Time.now $iter.times do JSON.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Ext' end parse_results[:ext] = dt puts "%d JSON::Ext::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "JSON::Ext failed: #{e.class}: #{e.message}" end begin JSON.parser = JSON::Pure::Parser start = Time.now $iter.times do JSON.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Pure' end parse_results[:pure] = dt puts "%d JSON::Pure::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "JSON::Pure failed: #{e.class}: #{e.message}" end begin mp = MessagePack.pack(obj) start = Time.now $iter.times do MessagePack.unpack(mp) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'MessagePack' end parse_results[:msgpack] = dt puts "%d MessagePack.unpack()s in %0.3f seconds or %0.1f packs/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "MessagePack failed: #{e.class}: #{e.message}" end start = Time.now $iter.times do Ox.load(xml) end dt = Time.now - start parse_results[:ox] = dt puts "%d Ox.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0] puts "Parser results:" puts "gem seconds parses/msec X faster than #{base_name} (higher is better)" parse_results.each do |name,dt| if 0.0 == dt puts "#{name} failed to generate JSON" next end puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt] end puts # Back to object mode for best performance when dumping. Oj.default_options = { :indent => $indent, :mode => :object } dump_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 } start = Time.now $iter.times do Oj.dump(obj) end dt = Time.now - start base_dt = dt base_name = 'Oj' parse_results[:oj] = dt puts "%d Oj.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0] start = Time.now $iter.times do Yajl::Encoder.encode(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'Yajl' end parse_results[:yajl] = dt puts "%d Yajl::Encoder.encode()s in %0.3f seconds or %0.1f encodes/msec" % [$iter, dt, $iter/dt/1000.0] begin JSON.parser = JSON::Ext::Parser start = Time.now $iter.times do JSON.generate(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Ext' end parse_results[:pure] = dt puts "%d JSON::Ext generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:ext] = 0.0 puts "JSON::Ext failed: #{e.class}: #{e.message}" end begin JSON.parser = JSON::Pure::Parser start = Time.now $iter.times do JSON.generate(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Pure' end parse_results[:pure] = dt puts "%d JSON::Pure generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:pure] = 0.0 puts "JSON::Pure failed: #{e.class}: #{e.message}" end begin start = Time.now $iter.times do MessagePack.pack(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'MessagePack' end parse_results[:msgpack] = dt puts "%d Msgpack()s in %0.3f seconds or %0.1f unpacks/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:msgpack] = 0.0 puts "MessagePack failed: #{e.class}: #{e.message}" end start = Time.now $iter.times do Ox.dump(obj) end dt = Time.now - start parse_results[:ox] = dt puts "%d Ox.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0] puts "Parser results:" puts "gem seconds dumps/msec X faster than #{base_name} (higher is better)" parse_results.each do |name,dt| if 0.0 == dt puts "#{name} failed to generate JSON" next end puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt] end puts oj-3.13.9/test/activesupport4/0000755000004100000410000000000014136373754016237 5ustar www-datawww-dataoj-3.13.9/test/activesupport4/encoding_test.rb0000644000004100000410000004254014136373754021416 0ustar www-datawww-data# encoding: utf-8 require 'activesupport4/test_helper' require 'securerandom' require 'active_support/core_ext/string/inflections' require 'active_support/json' require 'active_support/time' class TestJSONEncoding < ActiveSupport::TestCase class Foo def initialize(a, b) @a, @b = a, b end end class Hashlike def to_hash { :foo => "hello", :bar => "world" } end end class Custom def initialize(serialized) @serialized = serialized end def as_json(options = nil) @serialized end end class CustomWithOptions attr_accessor :foo, :bar def as_json(options={}) options[:only] = %w(foo bar) super(options) end end class OptionsTest def as_json(options = :default) options end end class HashWithAsJson < Hash attr_accessor :as_json_called def initialize(*) super end def as_json(options={}) @as_json_called = true super end end TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] NumericTests = [[ 1, %(1) ], [ 2.5, %(2.5) ], [ 0.0/0.0, %(null) ], [ 1.0/0.0, %(null) ], [ -1.0/0.0, %(null) ], [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]] StringTests = [[ 'this is the ', %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], [ 'http://test.host/posts/1', %("http://test.host/posts/1")], [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] RangeTests = [[ 1..2, %("1..2")], [ 1...2, %("1...2")], [ 1.5..2.5, %("1.5..2.5")]] SymbolTests = [[ :a, %("a") ], [ :this, %("this") ], [ :"a b", %("a b") ]] ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] CustomTests = [[ Custom.new("custom"), '"custom"' ], [ Custom.new(nil), 'null' ], [ Custom.new(:a), '"a"' ], [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]] TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]] StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]] StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]] StandardStringTests = [[ 'this is the ', %("this is the ")]] def sorted_json(json) return json unless json =~ /^\{.*\}$/ '{' + json[1..-2].split(',').sort.join(',') + '}' end constants.grep(/Tests$/).each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do begin prev = ActiveSupport.use_standard_json_time_format ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ self.class.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end ensure ActiveSupport.escape_html_entities_in_json = false ActiveSupport.use_standard_json_time_format = prev end end end def test_process_status # There doesn't seem to be a good way to get a handle on a Process::Status object without actually # creating a child process, hence this to populate $? system("not_a_real_program_#{SecureRandom.hex}") assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?) end def test_hash_encoding assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b) assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1) assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2]) assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2) assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d)) end def test_hash_keys_encoding ActiveSupport.escape_html_entities_in_json = true assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>") ensure ActiveSupport.escape_html_entities_in_json = false end def test_utf8_string_encoded_properly result = ActiveSupport::JSON.encode('€2.99') assert_equal '"€2.99"', result assert_equal(Encoding::UTF_8, result.encoding) result = ActiveSupport::JSON.encode('✎☺') assert_equal '"✎☺"', result assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes s = '二'.encode('Shift_JIS') result = ActiveSupport::JSON.encode(s) assert_equal '"二"', result assert_equal Encoding::UTF_8, result.encoding end def test_wide_utf8_chars w = '𠜎' result = ActiveSupport::JSON.encode(w) assert_equal '"𠜎"', result end def test_wide_utf8_roundtrip hash = { string: "𐒑" } json = ActiveSupport::JSON.encode(hash) decoded_hash = ActiveSupport::JSON.decode(json) assert_equal "𐒑", decoded_hash['string'] end def test_reading_encode_big_decimal_as_string_option assert_deprecated do assert ActiveSupport.encode_big_decimal_as_string end end def test_setting_deprecated_encode_big_decimal_as_string_option assert_raise(NotImplementedError) do ActiveSupport.encode_big_decimal_as_string = true end assert_raise(NotImplementedError) do ActiveSupport.encode_big_decimal_as_string = false end end def test_exception_raised_when_encoding_circular_reference_in_array a = [1] a << a assert_deprecated do assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } end end def test_exception_raised_when_encoding_circular_reference_in_hash a = { :name => 'foo' } a[:next] = a assert_deprecated do assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } end end def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array a = { :name => 'foo', :sub => [] } a[:sub] << a assert_deprecated do assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } end end def test_hash_key_identifiers_are_always_quoted values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"} assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) end def test_hash_should_allow_key_filtering_with_only assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a') end def test_hash_should_allow_key_filtering_with_except assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c]) end def test_time_to_json_includes_local_offset with_standard_json_time_format(true) do with_env_tz 'US/Eastern' do assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) end end end def test_hash_with_time_to_json with_standard_json_time_format(false) do assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json end end def test_nested_hash_with_float assert_nothing_raised do hash = { "CHI" => { :display_name => "chicago", :latitude => 123.234 } } ActiveSupport::JSON.encode(hash) end end def test_hash_like_with_options h = Hashlike.new json = h.to_json :only => [:foo] assert_equal({"foo"=>"hello"}, JSON.parse(json)) end def test_object_to_json_with_options obj = Object.new obj.instance_variable_set :@foo, "hello" obj.instance_variable_set :@bar, "world" json = obj.to_json :only => ["foo"] assert_equal({"foo"=>"hello"}, JSON.parse(json)) end def test_struct_to_json_with_options struct = Struct.new(:foo, :bar).new struct.foo = "hello" struct.bar = "world" json = struct.to_json :only => [:foo] assert_equal({"foo"=>"hello"}, JSON.parse(json)) end def test_hash_should_pass_encoding_options_to_children_in_as_json person = { :name => 'John', :address => { :city => 'London', :country => 'UK' } } json = person.as_json :only => [:address, :city] assert_equal({ 'address' => { 'city' => 'London' }}, json) end def test_hash_should_pass_encoding_options_to_children_in_to_json person = { :name => 'John', :address => { :city => 'London', :country => 'UK' } } json = person.to_json :only => [:address, :city] assert_equal(%({"address":{"city":"London"}}), json) end def test_array_should_pass_encoding_options_to_children_in_as_json people = [ { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} ] json = people.as_json :only => [:address, :city] expected = [ { 'address' => { 'city' => 'London' }}, { 'address' => { 'city' => 'Paris' }} ] assert_equal(expected, json) end def test_array_should_pass_encoding_options_to_children_in_to_json people = [ { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} ] json = people.to_json :only => [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end def test_enumerable_should_pass_encoding_options_to_children_in_as_json people = [ { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} ] json = people.each.as_json :only => [:address, :city] expected = [ { 'address' => { 'city' => 'London' }}, { 'address' => { 'city' => 'Paris' }} ] assert_equal(expected, json) end def test_enumerable_should_pass_encoding_options_to_children_in_to_json people = [ { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} ] json = people.each.to_json :only => [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end def test_hash_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"}, "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json)) end def test_array_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" array = [f, {"foo" => "other_foo", "test" => "other_test"}] assert_equal([{"foo"=>"hello","bar"=>"world"}, {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json)) end def test_hash_as_json_without_options json = { foo: OptionsTest.new }.as_json assert_equal({"foo" => :default}, json) end def test_array_as_json_without_options json = [ OptionsTest.new ].as_json assert_equal([:default], json) end def test_struct_encoding Struct.new('UserNameAndEmail', :name, :email) Struct.new('UserNameAndDate', :name, :date) Struct.new('Custom', :name, :sub) user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com' user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01) custom = Struct::Custom.new 'David', user_birthday json_strings = "" json_string_and_date = "" json_custom = "" assert_nothing_raised do json_strings = user_email.to_json json_string_and_date = user_birthday.to_json json_custom = custom.to_json end assert_equal({"name" => "David", "sub" => { "name" => "David", "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom)) assert_equal({"name" => "David", "email" => "sample@example.com"}, ActiveSupport::JSON.decode(json_strings)) assert_equal({"name" => "David", "date" => "2010-01-01"}, ActiveSupport::JSON.decode(json_string_and_date)) end def test_nil_true_and_false_represented_as_themselves assert_nil(nil.as_json) assert_equal true, true.as_json assert_equal false, false.as_json end def test_json_gem_dump_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h) assert_nil h.as_json_called end def test_json_gem_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h) assert_nil h.as_json_called end def test_json_gem_pretty_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal < {"returnTo" => {"/categories" => "/"}}, %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}}, %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}}, %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]}, %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]}, %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"}, %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"}, # multibyte %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"}, %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], # no time zone %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, # invalid date %({"a": "1089-10-40"}) => {'a' => "1089-10-40"}, # xmlschema date notation %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)}, %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)}, %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)}, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, %([]) => [], %({}) => {}, %({"a":1}) => {"a" => 1}, %({"a": ""}) => {"a" => ""}, %({"a":"\\""}) => {"a" => "\""}, %({"a": null}) => {"a" => nil}, %({"a": true}) => {"a" => true}, %({"a": false}) => {"a" => false}, %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""}, %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"}, %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => ""}, %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, %q({"a": "\u003cbr /\u003e"}) => {'a' => "
"}, %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["","",""]}, # test combination of dates and escaped or unicode encoded data in arrays %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}], # tests escaping of "\n" char with Yaml backend %q({"a":"\n"}) => {"a"=>"\n"}, %q({"a":"\u000a"}) => {"a"=>"\n"}, %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}, # prevent json unmarshalling %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"}, # json "fragments" - these are invalid JSON, but ActionPack relies on this %q("a string") => "a string", %q(1.1) => 1.1, %q(1) => 1, %q(-1) => -1, %q(true) => true, %q(false) => false, %q(null) => nil } TESTS.each_with_index do |(json, expected), index| test "json decodes #{index}" do prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = true silence_warnings do if expected.nil? assert_nil ActiveSupport::JSON.decode(json), "JSON decoding failed for #{json}" else assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding failed for #{json}" end end ActiveSupport.parse_json_times = prev end end test "json decodes time json with time parsing disabled" do prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = false expected = {"a" => "2007-01-01 01:12:34 Z"} assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) ActiveSupport.parse_json_times = prev end def test_failed_json_decoding assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } end def test_cannot_pass_unsupported_options assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) } end end oj-3.13.9/test/activesupport4/test_helper.rb0000644000004100000410000000243014136373754021101 0ustar www-datawww-datarequire 'active_support/testing/assertions' require 'active_support/testing/deprecation' require 'active_support/testing/declarative' require 'minitest/autorun' module ActiveSupport class TestCase < ::Minitest::Test Assertion = Minitest::Assertion alias_method :method_name, :name include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods alias :assert_raise :assert_raises alias :assert_not_empty :refute_empty alias :assert_not_equal :refute_equal alias :assert_not_in_delta :refute_in_delta alias :assert_not_in_epsilon :refute_in_epsilon alias :assert_not_includes :refute_includes alias :assert_not_instance_of :refute_instance_of alias :assert_not_kind_of :refute_kind_of alias :assert_no_match :refute_match alias :assert_not_nil :refute_nil alias :assert_not_operator :refute_operator alias :assert_not_predicate :refute_predicate alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same # Fails if the block raises an exception. # # assert_nothing_raised do # ... # end def assert_nothing_raised(*args) yield end end end oj-3.13.9/test/test_various.rb0000755000004100000410000004277214136373754016336 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) require 'helper' class Juice < Minitest::Test def gen_whitespaced_string(length = Random.new.rand(100)) whitespace_chars = [" ", "\t", "\f", "\n", "\r"] result = "" length.times { result << whitespace_chars.sample } result end module TestModule end class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end # Jam class Jeez < Jam def initialize(x, y) super end def to_json() %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jeez # contributed by sauliusg to fix as_json class Orange < Jam def initialize(x, y) super end def as_json() { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end class Melon < Jam def initialize(x, y) super end def as_json(options) "#{x} #{y}" end def self.json_create(h) self.new(h['x'], h['y']) end end class Jazz < Jam def initialize(x, y) super end def to_hash() { 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end # Jazz def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_set_options orig = Oj.default_options() alt ={ indent: " - ", second_precision: 5, circular: true, class_cache: false, auto_define: true, symbol_keys: true, bigdecimal_as_decimal: false, use_to_json: false, use_to_hash: false, use_as_json: false, use_raw_json: false, nilnil: true, empty_string: true, allow_gc: false, quirks_mode: false, allow_invalid_unicode: true, float_precision: 13, mode: :strict, escape_mode: :ascii, time_format: :unix_zone, bigdecimal_load: :float, compat_bigdecimal: true, create_id: 'classy', create_additions: true, cache_keys: false, cache_str: 5, space: 'z', array_nl: 'a', object_nl: 'o', space_before: 'b', nan: :huge, hash_class: Hash, omit_nil: false, allow_nan: true, integer_range: nil, array_class: Array, ignore: nil, ignore_under: true, trace: true, safe: true, } Oj.default_options = alt #keys = alt.keys #Oj.default_options.keys.each { |k| puts k unless keys.include? k} opts = Oj.default_options() assert_equal(alt, opts); Oj.default_options = orig # return to original # verify back to original opts = Oj.default_options() assert_equal(orig, opts); end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float_parse Oj.default_options = { :float_precision => 16, :bigdecimal_load => :auto } n = Oj.load('0.00001234567890123456') assert_equal(Float, n.class) assert_equal('1.234567890123456e-05', "%0.15e" % [n]) n = Oj.load('-0.00001234567890123456') assert_equal(Float, n.class) assert_equal('-1.234567890123456e-05', "%0.15e" % [n]) n = Oj.load('1000.0000123456789') assert_equal(BigDecimal, n.class) assert_equal('0.10000000123456789E4', n.to_s.upcase) n = Oj.load('-0.000012345678901234567') assert_equal(BigDecimal, n.class) assert_equal('-0.12345678901234567E-4', n.to_s.upcase) end =begin # TBD move to custom def test_float_dump Oj.default_options = { :float_precision => 16 } assert_equal('1405460727.723866', Oj.dump(1405460727.723866)) Oj.default_options = { :float_precision => 5 } assert_equal('1.4055', Oj.dump(1.405460727)) Oj.default_options = { :float_precision => 0 } assert_equal('1405460727.723866', Oj.dump(1405460727.723866)) Oj.default_options = { :float_precision => 15 } assert_equal('0.56', Oj.dump(0.56)) assert_equal('0.5773', Oj.dump(0.5773)) assert_equal('0.6768', Oj.dump(0.6768)) assert_equal('0.685', Oj.dump(0.685)) assert_equal('0.7032', Oj.dump(0.7032)) assert_equal('0.7051', Oj.dump(0.7051)) assert_equal('0.8274', Oj.dump(0.8274)) assert_equal('0.9149', Oj.dump(0.9149)) assert_equal('64.4', Oj.dump(64.4)) assert_equal('71.6', Oj.dump(71.6)) assert_equal('73.4', Oj.dump(73.4)) assert_equal('80.6', Oj.dump(80.6)) assert_equal('-95.640172', Oj.dump(-95.640172)) end =end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) assert_equal("a\u0000a", dump_and_load("a\u0000a", false)) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } dump_and_load("ぴーたー", false) Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) dump_and_load("ぴーたー", false) Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_invalid_unicode_raise # validate that an invalid unicode raises unless the :allow_invalid_unicode is true json = %{"x\\ud83dy"} begin Oj.load(json) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_invalid_unicode_ok # validate that an invalid unicode raises unless the :allow_invalid_unicode is true json = %{"x\\ud83dy"} obj = Oj.load(json, :allow_invalid_unicode => true) # The same as what ruby would do with the invalid encoding. assert_equal("x\xED\xA0\xBDy", obj.to_s) end def test_dump_options json = Oj.dump({ 'a' => 1, 'b' => [true, false]}, :mode => :compat, :indent => "--", :array_nl => "\n", :object_nl => "#\n", :space => "*", :space_before => "~") assert(%{{# --"a"~:*1,# --"b"~:*[ ----true, ----false --]# }} == json || %{{# --"b"~:*[ ----true, ----false --],# --"a"~:*1# }} == json) end def test_null_char assert_raises(Oj::ParseError) { Oj.load("\"\0\"") } assert_raises(Oj::ParseError) { Oj.load("\"\\\0\"") } end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_not_closed begin Oj.load('[') rescue Exception assert(true) return end assert(false, "*** expected an exception") end # multiple JSON in one string def test_multiple_json_callback json = %{{"a":1} [1,2][3,4] {"b":2} } results = [] Oj.load(json, :mode => :strict) { |x, start, len| results << [x, start, len] } assert_equal([[{"a"=>1}, 0, 7], [[1,2], 7, 6], [[3,4], 13, 5], [{"b"=>2}, 18, 8]], results) end def test_multiple_json_no_callback json = %{{"a":1} [1,2][3,4] {"b":2} } assert_raises(Oj::ParseError) { Oj.load(json) } end # encoding tests def test_does_not_escape_entities_by_default Oj.default_options = { :escape_mode => :ascii } # set in mimic mode # use Oj to create the hash since some Rubies don't deal nicely with unicode. json = %{{"key":"I <3 this\\u2028space"}} hash = Oj.load(json) out = Oj.dump(hash) assert_equal(json, out) end def test_escapes_entities_by_default_when_configured_to_do_so hash = {'key' => "I <3 this"} Oj.default_options = {:escape_mode => :xss_safe} out = Oj.dump hash assert_equal(%{{"key":"I \\u003c3 this"}}, out) end def test_escapes_entities_when_asked_to hash = {'key' => "I <3 this"} out = Oj.dump(hash, :escape_mode => :xss_safe) assert_equal(%{{"key":"I \\u003c3 this"}}, out) end def test_does_not_escape_entities_when_not_asked_to hash = {'key' => "I <3 this"} out = Oj.dump(hash, :escape_mode => :json) assert_equal(%{{"key":"I <3 this"}}, out) end def test_escapes_common_xss_vectors hash = {'key' => ""} out = Oj.dump(hash, :escape_mode => :xss_safe) assert_equal(%{{"key":"\\u003cscript\\u003ealert(123) \\u0026\\u0026 formatHD()\\u003c\\/script\\u003e"}}, out) end def test_escape_newline_by_default Oj.default_options = { :escape_mode => :json } json = %{["one","two\\n2"]} x = Oj.load(json) out = Oj.dump(x) assert_equal(json, out) end def test_does_not_escape_newline Oj.default_options = { :escape_mode => :newline } json = %{["one","two\n2"]} x = Oj.load(json) out = Oj.dump(x) assert_equal(json, out) end def test_dump_invalid_utf8 Oj.default_options = { :escape_mode => :ascii } assert_raises(EncodingError) { Oj.dump(["abc\xbe\x1f\x11"], mode: :strict) } end # Symbol def test_symbol_null json = Oj.dump(:abc, :mode => :null) assert_equal('"abc"', json) end # Time def test_time_null t = Time.local(2012, 1, 5, 23, 58, 7) json = Oj.dump(t, :mode => :null) assert_equal('null', json) end def test_time_neg t = Time.parse("1900-01-01 00:18:59 UTC") json = Oj.dump(t, :mode => :custom, :time_format => :unix) assert_equal('-2208987661.000000000', json) end def test_time_years (-2020..2020).each { |year| s = "%04d-03-01T15:14:13Z" % [year] json = Oj.dump(Time.parse(s), mode: :custom, time_format: :xmlschema, second_precision: -1) assert_equal(s, json[1..-2]) json = Oj.dump(Time.parse(s), mode: :custom, time_format: :xmlschema, second_precision: 3) assert_equal(s[0..-2] + '.000Z', json[1..-2]) } end # Class def test_class_null json = Oj.dump(Juice, :mode => :null) assert_equal('null', json) end # Module def test_module_null json = Oj.dump(TestModule, :mode => :null) assert_equal('null', json) end # Hash def test_non_str_hash_null begin Oj.dump({ 1 => true, 0 => false }, :mode => :null) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_hash_not_closed begin Oj.load('{') rescue Exception assert(true) return end assert(false, "*** expected an exception") end # Object with to_json() def test_json_object_null obj = Jeez.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end # Object with to_hash() def test_to_hash_object_null obj = Jazz.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end # Object with as_json() # contributed by sauliusg def test_as_json_object_null obj = Orange.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end # Object without to_json() or to_hash() def test_object_null obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end # Range def test_range_null json = Oj.dump(1..7, :mode => :null) assert_equal('null', json) end # BigNum def test_bignum_null json = Oj.dump(7 ** 55, :mode => :null) assert_equal('30226801971775055948247051683954096612865741943', json) end def test_bignum_object dump_and_load(7 ** 55, false) dump_and_load(10 ** 19, false) end # BigDecimal def test_bigdecimal_null mode = Oj.default_options[:mode] Oj.default_options = {:mode => :null} dump_and_load(BigDecimal('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} end def test_infinity n = Oj.load('Infinity', :mode => :object) assert_equal(BigDecimal('Infinity').to_f, n); x = Oj.load('Infinity', :mode => :compat) assert_equal('Infinity', x.to_s) end # Date def test_date_null json = Oj.dump(Date.new(2012, 6, 19), :mode => :null) assert_equal('null', json) end # DateTime def test_datetime_null json = Oj.dump(DateTime.new(2012, 6, 19, 20, 19, 27), :mode => :null) assert_equal('null', json) end # autodefine Oj::Bag def test_bag json = %{{ "^o":"Juice::Jem", "x":true, "y":58 }} obj = Oj.load(json, :mode => :object, :auto_define => true) assert_equal('Juice::Jem', obj.class.name) assert_equal(true, obj.x) assert_equal(58, obj.y) end # Stream Deeply Nested def test_deep_nest_dump begin a = [] 10000.times { a << [a] } Oj.dump(a) rescue Exception assert(true) return end assert(false, "*** expected an exception") end # Stream IO def test_io_string src = { 'x' => true, 'y' => 58, 'z' => [1, 2, 3]} output = StringIO.open("", "w+") Oj.to_stream(output, src) input = StringIO.new(output.string()) obj = Oj.load(input, :mode => :strict) assert_equal(src, obj) end def test_io_file src = { 'x' => true, 'y' => 58, 'z' => [1, 2, 3]} filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, "w") { |f| Oj.to_stream(f, src) } f = File.new(filename) obj = Oj.load(f, :mode => :strict) f.close() assert_equal(src, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{/*before*/ { "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_nilnil_false begin Oj.load(nil, :nilnil => false) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_nilnil_true obj = Oj.load(nil, :nilnil => true) assert_nil(obj) end def test_empty_string_true result = Oj.load(gen_whitespaced_string, :empty_string => true, mode: :strict) assert_nil(result) end def test_empty_string_false # Could be either a Oj::ParseError or an EncodingError depending on # whether mimic_JSON has been called. Since we don't know when the test # will be called either is okay. begin Oj.load(gen_whitespaced_string, :empty_string => false) rescue Exception => e assert(Oj::ParseError == e.class || EncodingError == e.class) return end assert(false, "*** expected an exception") end def test_quirks_null_mode assert_raises(Oj::ParseError) { Oj.load("null", :quirks_mode => false) } assert_nil(Oj.load("null", :quirks_mode => true)) end def test_quirks_bool_mode assert_raises(Oj::ParseError) { Oj.load("true", :quirks_mode => false) } assert_equal(true, Oj.load("true", :quirks_mode => true)) end def test_quirks_number_mode assert_raises(Oj::ParseError) { Oj.load("123", :quirks_mode => false) } assert_equal(123, Oj.load("123", :quirks_mode => true)) end def test_quirks_decimal_mode assert_raises(Oj::ParseError) { Oj.load("123.45", :quirks_mode => false) } assert_equal(123.45, Oj.load("123.45", :quirks_mode => true)) end def test_quirks_string_mode assert_raises(Oj::ParseError) { Oj.load('"string"', :quirks_mode => false) } assert_equal('string', Oj.load('"string"', :quirks_mode => true)) end def test_error_path msg = '' assert_raises(Oj::ParseError) { begin Oj.load(%|{ "first": [ 1, 2, { "third": 123x } ] }|) rescue Oj::ParseError => e msg = e.message raise e end } assert_equal('after first[2].third', msg.split('(')[1].split(')')[0]) end def test_bad_bignum if '2.4.0' < RUBY_VERSION assert_raises Oj::ParseError do Oj.load(%|{ "big": -e123456789 }|, mode: :strict) end end end def test_quirks_array_mode assert_equal([], Oj.load("[]", :quirks_mode => false)) assert_equal([], Oj.load("[]", :quirks_mode => true)) end def test_quirks_object_mode assert_equal({}, Oj.load("{}", :quirks_mode => false)) assert_equal({}, Oj.load("{}", :quirks_mode => true)) end def test_omit_nil jam = Jam.new({'a' => 1, 'b' => nil }, nil) json = Oj.dump(jam, :omit_nil => true, :mode => :object) assert_equal(%|{"^o":"Juice::Jam","x":{"a":1}}|, json) json = Oj.dump({'x' => {'a' => 1, 'b' => nil }, 'y' => nil}, :omit_nil => true, :mode => :strict) assert_equal(%|{"x":{"a":1}}|, json) json = Oj.dump({'x' => {'a' => 1, 'b' => nil }, 'y' => nil}, :omit_nil => true, :mode => :null) assert_equal(%|{"x":{"a":1}}|, json) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) puts json if trace loaded = Oj.load(json) if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/test_strict.rb0000755000004100000410000002312314136373754016143 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class StrictJuice < Minitest::Test module TestModule end class Jeez attr_accessor :x, :y def initialize(x, y) @x = x @y = y end end def setup @default_options = Oj.default_options # in strict mode other options other than the number formats are not used. Oj.default_options = { :mode => :strict } end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_nan_dump assert_equal('null', Oj.dump(0/0.0, :nan => :null)) assert_equal('3.3e14159265358979323846', Oj.dump(0/0.0, :nan => :huge)) begin Oj.dump(0/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_infinity_dump assert_equal('null', Oj.dump(1/0.0, :nan => :null)) assert_equal('3.0e14159265358979323846', Oj.dump(1/0.0, :nan => :huge)) begin Oj.dump(1/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_neg_infinity_dump assert_equal('null', Oj.dump(-1/0.0, :nan => :null)) assert_equal('-3.0e14159265358979323846', Oj.dump(-1/0.0, :nan => :huge)) begin Oj.dump(-1/0.0, :nan => :word) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_unicode_long # tests buffer overflow json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e #{'x' * 2000}"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end def test_deep_nest begin n = 10000 Oj.strict_load('[' * n + ']' * n) rescue Exception => e assert(false, e.message) end end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.strict_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_non_str_hash begin Oj.dump({ 1 => true, 0 => false }) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_strict Oj.default_options = { :bigdecimal_load => true} dump_and_load(BigDecimal('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal('80.51') json = Oj.dump(orig, :mode => :strict, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :strict, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end def test_json_object obj = Jeez.new(true, 58) begin Oj.dump(obj) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_range begin Oj.dump(1..7) rescue Exception assert(true) return end assert(false, "*** expected an exception") end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.strict_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.strict_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_symbol json = Oj.dump(:abc) assert_equal('"abc"', json) end def test_time t = Time.local(2012, 1, 5, 23, 58, 7) begin Oj.dump(t) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_class begin Oj.dump(StrictJuice) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_module begin Oj.dump(TestModule) rescue Exception assert(true) return end assert(false, "*** expected an exception") end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.strict_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end def test_symbol_keys_safe json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.safe_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_double json = %{{ "x": 1}{ "y": 2}} results = [] Oj.load(json, :mode => :strict) { |x| results << x } assert_equal([{ 'x' => 1 }, { 'y' => 2 }], results) end def test_invalid_decimal_dot_start assert_raises(Oj::ParseError) { Oj.load('.123', mode: :strict) } assert_raises(Oj::ParseError) { Oj.load('-.123', mode: :strict) } end def test_invalid_decimal_dot_end json = '123.' assert_raises(Oj::ParseError) { Oj.load(json, mode: :strict) } end def test_invalid_decimal_plus json = '+12' assert_raises(Oj::ParseError) { Oj.load(json, mode: :strict) } end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :indent => 2, :circular => true) assert_equal(%|{ "a":7, "b":null } |, json) end def test_omit_nil json = Oj.dump({'x' => {'a' => 1, 'b' => nil }, 'y' => nil}, :omit_nil => true) assert_equal(%|{"x":{"a":1}}|, json) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) puts json if trace loaded = Oj.strict_load(json); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/baz.rb0000755000004100000410000000051114136373754014344 0ustar www-datawww-data#!/usr/bin/env ruby $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'oj' Oj.mimic_JSON() begin ::JSON.load('name=&email=&subject=&comment=&submit=Send+Message') rescue ::JSON::ParserError puts "*** Pass" end oj-3.13.9/test/tests_mimic.rb0000755000004100000410000000056714136373754016123 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) $: << File.join(File.dirname(__FILE__), 'json_gem') require 'json_common_interface_test' require 'json_encoding_test' require 'json_ext_parser_test' require 'json_fixtures_test' require 'json_generator_test' require 'json_generic_object_test' require 'json_parser_test' require 'json_string_matching_test' oj-3.13.9/test/files.rb0000755000004100000410000000076014136373754014700 0ustar www-datawww-data#!/usr/bin/env ruby -wW2 if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'sample/file' require 'sample/dir' def files(dir) d = ::Sample::Dir.new(dir) Dir.new(dir).each do |fn| next if fn.start_with?('.') filename = File.join(dir, fn) #filename = '.' == dir ? fn : File.join(dir, fn) if File.directory?(filename) d << files(filename) else d << ::Sample::File.new(filename) end end #pp d d end oj-3.13.9/test/json_gem/0000755000004100000410000000000014136373754015044 5ustar www-datawww-dataoj-3.13.9/test/json_gem/json_encoding_test.rb0000644000004100000410000001011714136373754021247 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' class JSONEncodingTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport include Test::Unit::TestCasePendingSupport def setup @utf_8 = '"© ≠ €!"' @ascii_8bit = @utf_8.dup.force_encoding('ascii-8bit') @parsed = "© ≠ €!" @generated = '"\u00a9 \u2260 \u20ac!"' if String.method_defined?(:encode) @utf_16_data = @parsed.encode('utf-16be', 'utf-8') @utf_16be = @utf_8.encode('utf-16be', 'utf-8') @utf_16le = @utf_8.encode('utf-16le', 'utf-8') @utf_32be = @utf_8.encode('utf-32be', 'utf-8') @utf_32le = @utf_8.encode('utf-32le', 'utf-8') else require 'iconv' @utf_16_data, = Iconv.iconv('utf-16be', 'utf-8', @parsed) @utf_16be, = Iconv.iconv('utf-16be', 'utf-8', @utf_8) @utf_16le, = Iconv.iconv('utf-16le', 'utf-8', @utf_8) @utf_32be, = Iconv.iconv('utf-32be', 'utf-8', @utf_8) @utf_32le, = Iconv.iconv('utf-32le', 'utf-8', @utf_8) end end def test_parse assert_equal @parsed, JSON.parse(@ascii_8bit) assert_equal @parsed, JSON.parse(@utf_8) assert_equal @parsed, JSON.parse(@utf_16be) assert_equal @parsed, JSON.parse(@utf_16le) assert_equal @parsed, JSON.parse(@utf_32be) assert_equal @parsed, JSON.parse(@utf_32le) end def test_generate assert_equal @generated, JSON.generate(@parsed, :ascii_only => true) assert_equal @generated, JSON.generate(@utf_16_data, :ascii_only => true) end def test_unicode assert_equal '""', ''.to_json assert_equal '"\\b"', "\b".to_json assert_equal '"\u0001"', 0x1.chr.to_json assert_equal '"\u001f"', 0x1f.chr.to_json assert_equal '" "', ' '.to_json assert_equal "\"#{0x7f.chr}\"", 0x7f.chr.to_json utf8 = [ "© ≠ €! \01" ] json = '["© ≠ €! \u0001"]' assert_equal json, utf8.to_json(:ascii_only => false) assert_equal utf8, JSON.parse(json) json = '["\u00a9 \u2260 \u20ac! \u0001"]' assert_equal json, utf8.to_json(:ascii_only => true) assert_equal utf8, JSON.parse(json) utf8 = ["\343\201\202\343\201\204\343\201\206\343\201\210\343\201\212"] json = "[\"\343\201\202\343\201\204\343\201\206\343\201\210\343\201\212\"]" assert_equal utf8, JSON.parse(json) assert_equal json, utf8.to_json(:ascii_only => false) utf8 = ["\343\201\202\343\201\204\343\201\206\343\201\210\343\201\212"] assert_equal utf8, JSON.parse(json) json = "[\"\\u3042\\u3044\\u3046\\u3048\\u304a\"]" assert_equal json, utf8.to_json(:ascii_only => true) assert_equal utf8, JSON.parse(json) utf8 = ['საქართველო'] json = '["საქართველო"]' assert_equal json, utf8.to_json(:ascii_only => false) json = "[\"\\u10e1\\u10d0\\u10e5\\u10d0\\u10e0\\u10d7\\u10d5\\u10d4\\u10da\\u10dd\"]" assert_equal json, utf8.to_json(:ascii_only => true) assert_equal utf8, JSON.parse(json) assert_equal '["Ã"]', JSON.generate(["Ã"], :ascii_only => false) assert_equal '["\\u00c3"]', JSON.generate(["Ã"], :ascii_only => true) assert_equal ["€"], JSON.parse('["\u20ac"]') utf8 = ["\xf0\xa0\x80\x81"] json = "[\"\xf0\xa0\x80\x81\"]" assert_equal json, JSON.generate(utf8, :ascii_only => false) assert_equal utf8, JSON.parse(json) json = '["\ud840\udc01"]' assert_equal json, JSON.generate(utf8, :ascii_only => true) assert_equal utf8, JSON.parse(json) end def test_chars (0..0x7f).each do |i| json = '["\u%04x"]' % i i = i.chr assert_equal i, JSON.parse(json).first[0] if i == ?\b generated = JSON.generate(["" << i]) assert '["\b"]' == generated || '["\10"]' == generated elsif [?\n, ?\r, ?\t, ?\f].include?(i) assert_equal '[' << ('' << i).dump << ']', JSON.generate(["" << i]) elsif i.chr < 0x20.chr assert_equal json, JSON.generate(["" << i]) end end assert_raise(JSON::GeneratorError) do JSON.generate(["\x80"], :ascii_only => true) end assert_equal "\302\200", JSON.parse('["\u0080"]').first end end oj-3.13.9/test/json_gem/json_generator_test.rb0000644000004100000410000003034614136373754021455 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 # frozen_string_literal: false require 'json_gem/test_helper' class JSONGeneratorTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport include Test::Unit::TestCasePendingSupport def setup @hash = { 'a' => 2, 'b' => 3.141, 'c' => 'c', 'd' => [ 1, "b", 3.14 ], 'e' => { 'foo' => 'bar' }, 'g' => "\"\0\037", 'h' => 1000.0, 'i' => 0.001 } @json2 = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' + '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}' @json3 = <<'EOT'.chomp { "a": 2, "b": 3.141, "c": "c", "d": [ 1, "b", 3.14 ], "e": { "foo": "bar" }, "g": "\"\u0000\u001f", "h": 1000.0, "i": 0.001 } EOT end def test_generate json = JSON.generate(@hash) assert_equal(JSON.parse(@json2), JSON.parse(json)) json = JSON[@hash] assert_equal(JSON.parse(@json2), JSON.parse(json)) parsed_json = JSON.parse(json) assert_equal(@hash, parsed_json) json = JSON.generate({1=>2}) assert_equal('{"1":2}', json) parsed_json = JSON.parse(json) assert_equal({"1"=>2}, parsed_json) assert_equal '666', JSON.generate(666) end def test_generate_pretty json = JSON.pretty_generate(@hash) # hashes aren't (insertion) ordered on every ruby implementation assert_equal(@json3, json) assert_equal(JSON.parse(@json3), JSON.parse(json)) parsed_json = JSON.parse(json) assert_equal(@hash, parsed_json) json = JSON.pretty_generate({1=>2}) assert_equal(<<'EOT'.chomp, json) { "1": 2 } EOT parsed_json = JSON.parse(json) assert_equal({"1"=>2}, parsed_json) assert_equal '666', JSON.pretty_generate(666) end def test_generate_custom state = JSON::State.new(:space_before => " ", :space => " ", :indent => "", :object_nl => "\n", :array_nl => "") json = JSON.generate({1=>{2=>3,4=>[5,6]}}, state) assert_equal(<<'EOT'.chomp, json) { "1" : { "2" : 3, "4" : [5,6] } } EOT end def test_fast_generate json = JSON.fast_generate(@hash) assert_equal(JSON.parse(@json2), JSON.parse(json)) parsed_json = JSON.parse(json) assert_equal(@hash, parsed_json) json = JSON.fast_generate({1=>2}) assert_equal('{"1":2}', json) parsed_json = JSON.parse(json) assert_equal({"1"=>2}, parsed_json) assert_equal '666', JSON.fast_generate(666) end def test_own_state state = JSON::State.new json = JSON.generate(@hash, state) assert_equal(JSON.parse(@json2), JSON.parse(json)) parsed_json = JSON.parse(json) assert_equal(@hash, parsed_json) json = JSON.generate({1=>2}, state) assert_equal('{"1":2}', json) parsed_json = JSON.parse(json) assert_equal({"1"=>2}, parsed_json) assert_equal '666', JSON.generate(666, state) end # TBD Implement JSON.state to return state class. # set state attributes from defaults # implement methods # circular should use circular in defaults or maybe always set to true, allow changes with [:check_circular]= def test_states json = JSON.generate({1=>2}, nil) assert_equal('{"1":2}', json) s = JSON.state.new assert s.check_circular? assert s[:check_circular?] h = { 1=>2 } h[3] = h assert_raise(JSON::NestingError) { JSON.generate(h) } assert_raise(JSON::NestingError) { JSON.generate(h, s) } s = JSON.state.new a = [ 1, 2 ] a << a assert_raise(JSON::NestingError) { JSON.generate(a, s) } assert s.check_circular? assert s[:check_circular?] end def test_pretty_state state = JSON::PRETTY_STATE_PROTOTYPE.dup # In come cases in Ruby 3.0 an :escape_slash is included in the state. It # seems to occur on travis but not locally. actual = state.to_h actual.delete(:escape_slash) assert_equal({ :allow_nan => false, :array_nl => "\n", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => " ", :max_nesting => 100, :object_nl => "\n", :space => " ", :space_before => "", }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s }) end def test_safe_state state = JSON::SAFE_STATE_PROTOTYPE.dup # In come cases in Ruby 3.0 an :escape_slash is included in the state. It # seems to occur on travis but not locally. actual = state.to_h actual.delete(:escape_slash) assert_equal({ :allow_nan => false, :array_nl => "", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => "", :max_nesting => 100, :object_nl => "", :space => "", :space_before => "", }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s }) end def test_fast_state state = JSON::FAST_STATE_PROTOTYPE.dup # In come cases in Ruby 3.0 an :escape_slash is included in the state. It # seems to occur on travis but not locally. actual = state.to_h actual.delete(:escape_slash) assert_equal({ :allow_nan => false, :array_nl => "", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => "", :max_nesting => 0, :object_nl => "", :space => "", :space_before => "", }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s }) end def test_allow_nan assert_raise(JSON::GeneratorError) { JSON.generate([JSON::NaN]) } assert_equal '[NaN]', JSON.generate([JSON::NaN], :allow_nan => true) assert_raise(JSON::GeneratorError) { JSON.fast_generate([JSON::NaN]) } assert_raise(JSON::GeneratorError) { JSON.pretty_generate([JSON::NaN]) } assert_equal "[\n NaN\n]", JSON.pretty_generate([JSON::NaN], :allow_nan => true) assert_raise(JSON::GeneratorError) { JSON.generate([JSON::Infinity]) } assert_equal '[Infinity]', JSON.generate([JSON::Infinity], :allow_nan => true) assert_raise(JSON::GeneratorError) { JSON.fast_generate([JSON::Infinity]) } assert_raise(JSON::GeneratorError) { JSON.pretty_generate([JSON::Infinity]) } assert_equal "[\n Infinity\n]", JSON.pretty_generate([JSON::Infinity], :allow_nan => true) assert_raise(JSON::GeneratorError) { JSON.generate([JSON::MinusInfinity]) } assert_equal '[-Infinity]', JSON.generate([JSON::MinusInfinity], :allow_nan => true) assert_raise(JSON::GeneratorError) { JSON.fast_generate([JSON::MinusInfinity]) } assert_raise(JSON::GeneratorError) { JSON.pretty_generate([JSON::MinusInfinity]) } assert_equal "[\n -Infinity\n]", JSON.pretty_generate([JSON::MinusInfinity], :allow_nan => true) end def test_depth ary = []; ary << ary assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth assert_raise(JSON::NestingError) { JSON.generate(ary) } assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) } assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth s = JSON.state.new assert_equal 0, s.depth assert_raise(JSON::NestingError) { ary.to_json(s) } assert_equal 100, s.depth end def test_buffer_initial_length s = JSON.state.new assert_equal 1024, s.buffer_initial_length s.buffer_initial_length = 0 assert_equal 1024, s.buffer_initial_length s.buffer_initial_length = -1 assert_equal 1024, s.buffer_initial_length s.buffer_initial_length = 128 assert_equal 128, s.buffer_initial_length end def test_gc if respond_to?(:assert_in_out_err) assert_in_out_err(%w[-rjson --disable-gems], <<-EOS, [], []) bignum_too_long_to_embed_as_string = 1234567890123456789012345 expect = bignum_too_long_to_embed_as_string.to_s GC.stress = true 10.times do |i| tmp = bignum_too_long_to_embed_as_string.to_json raise "'\#{expect}' is expected, but '\#{tmp}'" unless tmp == expect end EOS end end if GC.respond_to?(:stress=) def test_configure_using_configure_and_merge numbered_state = { :indent => "1", :space => '2', :space_before => '3', :object_nl => '4', :array_nl => '5' } state1 = JSON.state.new state1.merge(numbered_state) assert_equal '1', state1.indent assert_equal '2', state1.space assert_equal '3', state1.space_before assert_equal '4', state1.object_nl assert_equal '5', state1.array_nl state2 = JSON.state.new state2.configure(numbered_state) assert_equal '1', state2.indent assert_equal '2', state2.space assert_equal '3', state2.space_before assert_equal '4', state2.object_nl assert_equal '5', state2.array_nl end def test_configure_hash_conversion state = JSON.state.new state.configure(:indent => '1') assert_equal '1', state.indent state = JSON.state.new foo = 'foo' assert_raise(TypeError) do state.configure(foo) end def foo.to_h { :indent => '2' } end state.configure(foo) assert_equal '2', state.indent end if defined?(JSON::Ext::Generator) def test_broken_bignum # [ruby-core:38867] pid = fork do x = 1 << 64 x.class.class_eval do def to_s end end begin j = JSON::Ext::Generator::State.new.generate(x) exit 1 rescue TypeError exit 0 end end _, status = Process.waitpid2(pid) assert status.success? rescue NotImplementedError # forking to avoid modifying core class of a parent process and # introducing race conditions of tests are run in parallel end end def test_hash_likeness_set_symbol state = JSON.state.new assert_equal nil, state[:foo] assert_equal nil.class, state[:foo].class assert_equal nil, state['foo'] state[:foo] = :bar assert_equal :bar, state[:foo] assert_equal :bar, state['foo'] state_hash = state.to_hash assert_kind_of Hash, state_hash assert_equal :bar, state_hash[:foo] end def test_hash_likeness_set_string state = JSON.state.new assert_equal nil, state[:foo] assert_equal nil, state['foo'] state['foo'] = :bar assert_equal :bar, state[:foo] assert_equal :bar, state['foo'] state_hash = state.to_hash assert_kind_of Hash, state_hash assert_equal :bar, state_hash[:foo] end def test_json_generate assert_raise JSON::GeneratorError do assert_equal true, JSON.generate(["\xea"]) end end def test_nesting too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' too_deep_ary = eval too_deep assert_raise(JSON::NestingError) { JSON.generate too_deep_ary } assert_raise(JSON::NestingError) { JSON.generate too_deep_ary, :max_nesting => 100 } ok = JSON.generate too_deep_ary, :max_nesting => 101 assert_equal too_deep, ok ok = JSON.generate too_deep_ary, :max_nesting => nil assert_equal too_deep, ok ok = JSON.generate too_deep_ary, :max_nesting => false assert_equal too_deep, ok ok = JSON.generate too_deep_ary, :max_nesting => 0 assert_equal too_deep, ok end def test_backslash data = [ '\\.(?i:gif|jpe?g|png)$' ] json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal json, JSON.generate(data) # data = [ '\\"' ] json = '["\\\\\""]' assert_equal json, JSON.generate(data) # data = [ '/' ] json = '["/"]' assert_equal json, JSON.generate(data) # data = ['"'] json = '["\""]' assert_equal json, JSON.generate(data) # data = ["'"] json = '["\\\'"]' assert_equal '["\'"]', JSON.generate(data) end def test_string_subclass s = Class.new(String) do def to_s; self; end undef to_json end assert_nothing_raised(SystemStackError) do assert_equal '["foo"]', JSON.generate([s.new('foo')]) end end end oj-3.13.9/test/json_gem/json_string_matching_test.rb0000644000004100000410000000154714136373754022650 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' require 'time' class JSONStringMatchingTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport class TestTime < ::Time def self.json_create(string) Time.parse(string) end def to_json(*) %{"#{strftime('%FT%T%z')}"} end def ==(other) to_i == other.to_i end end def test_match_date t = TestTime.new t_json = [ t ].to_json time_regexp = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\z/ assert_equal [ t ], JSON.parse( t_json, :create_additions => true, :match_string => { time_regexp => TestTime } ) assert_equal [ t.strftime('%FT%T%z') ], JSON.parse( t_json, :match_string => { time_regexp => TestTime } ) end end oj-3.13.9/test/json_gem/json_fixtures_test.rb0000644000004100000410000000176514136373754021343 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' class JSONFixturesTest < Test::Unit::TestCase def setup fixtures = File.join(File.dirname(__FILE__), 'fixtures/{fail,pass}.json') passed, failed = Dir[fixtures].partition { |f| f['pass'] } @passed = passed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort @failed = failed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort end def test_passing for name, source in @passed begin assert JSON.parse(source), "Did not pass for fixture '#{name}': #{source.inspect}" rescue => e warn "\nCaught #{e.class}(#{e}) for fixture '#{name}': #{source.inspect}\n#{e.backtrace * "\n"}" raise e end end end def test_failing for name, source in @failed assert_raise(JSON::ParserError, JSON::NestingError, "Did not fail for fixture '#{name}': #{source.inspect}") do JSON.parse(source) end end end end oj-3.13.9/test/json_gem/json_ext_parser_test.rb0000644000004100000410000000102114136373754021627 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' class JSONExtParserTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport if defined?(JSON::Ext::Parser) def test_allocate parser = JSON::Ext::Parser.new("{}") assert_raise(TypeError, '[ruby-core:35079]') do parser.__send__(:initialize, "{}") end parser = JSON::Ext::Parser.allocate assert_raise(TypeError, '[ruby-core:35079]') { parser.source } end end end oj-3.13.9/test/json_gem/json_addition_test.rb0000644000004100000410000001520314136373754021255 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' require 'date' if REAL_JSON_GEM require 'json/add/core' require 'json/add/complex' require 'json/add/rational' require 'json/add/bigdecimal' require 'json/add/ostruct' else #Oj.add_to_json() Oj.add_to_json(Array, BigDecimal, Complex, Date, DateTime, Exception, Hash, Integer, OpenStruct, Range, Rational, Regexp, Struct, Time) end class JSONAdditionTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport include Test::Unit::TestCasePendingSupport class A def self.json_creatable? true end def initialize(a) @a = a end attr_reader :a def ==(other) a == other.a end def self.json_create(object) new(*object['args']) end def to_json(*args) { 'json_class' => self.class.name, 'args' => [ @a ], }.to_json(*args) end end class A2 < A def to_json(*args) { 'json_class' => self.class.name, 'args' => [ @a ], }.to_json(*args) end end class B def self.json_creatable? false end def to_json(*args) { 'json_class' => self.class.name, }.to_json(*args) end end class C def self.json_creatable? false end def to_json(*args) { 'json_class' => 'JSONAdditionTest::Nix', }.to_json(*args) end end def test_extended_json a = A.new(666) assert A.json_creatable? json = JSON.generate(a) a_again = JSON.parse(json, :create_additions => true) assert_kind_of a.class, a_again assert_equal a, a_again end def test_extended_json_default a = A.new(666) assert A.json_creatable? json = JSON.generate(a) a_hash = JSON.parse(json) assert_kind_of Hash, a_hash end def test_extended_json_disabled a = A.new(666) assert A.json_creatable? json = JSON.generate(a) a_again = JSON.parse(json, :create_additions => true) assert_kind_of a.class, a_again assert_equal a, a_again a_hash = JSON.parse(json, :create_additions => false) assert_kind_of Hash, a_hash assert_equal( {"args"=>[666], "json_class"=>"JSONAdditionTest::A"}.sort_by { |k,| k }, a_hash.sort_by { |k,| k } ) end def test_extended_json_fail1 b = B.new assert !B.json_creatable? json = JSON.generate(b) assert_equal({ "json_class"=>"JSONAdditionTest::B" }, JSON.parse(json)) end def test_extended_json_fail2 c = C.new assert !C.json_creatable? json = JSON.generate(c) assert_raise(ArgumentError, NameError) { JSON.parse(json, :create_additions => true) } end def test_raw_strings raw = '' raw.respond_to?(:encode!) and raw.encode!(Encoding::ASCII_8BIT) raw_array = [] for i in 0..255 raw << i raw_array << i end json = raw.to_json_raw json_raw_object = raw.to_json_raw_object hash = { 'json_class' => 'String', 'raw'=> raw_array } assert_equal hash, json_raw_object assert_match(/\A\{.*\}\z/, json) assert_match(/"json_class":"String"/, json) assert_match(/"raw":\[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255\]/, json) raw_again = JSON.parse(json, :create_additions => true) assert_equal raw, raw_again end MyJsonStruct = Struct.new 'MyJsonStruct', :foo, :bar def test_core t = Time.now assert_equal t, JSON(JSON(t), :create_additions => true) d = Date.today assert_equal d, JSON(JSON(d), :create_additions => true) d = DateTime.civil(2007, 6, 14, 14, 57, 10, Rational(1, 12), 2299161) assert_equal d, JSON(JSON(d), :create_additions => true) assert_equal 1..10, JSON(JSON(1..10), :create_additions => true) assert_equal 1...10, JSON(JSON(1...10), :create_additions => true) assert_equal "a".."c", JSON(JSON("a".."c"), :create_additions => true) assert_equal "a"..."c", JSON(JSON("a"..."c"), :create_additions => true) s = MyJsonStruct.new 4711, 'foot' assert_equal s, JSON(JSON(s), :create_additions => true) struct = Struct.new :foo, :bar s = struct.new 4711, 'foot' assert_raise(JSON::JSONError) { JSON(s) } begin raise TypeError, "test me" rescue TypeError => e e_json = JSON.generate e e_again = JSON e_json, :create_additions => true assert_kind_of TypeError, e_again assert_equal e.message, e_again.message assert_equal e.backtrace, e_again.backtrace end assert_equal(/foo/, JSON(JSON(/foo/), :create_additions => true)) assert_equal(/foo/i, JSON(JSON(/foo/i), :create_additions => true)) end def test_utc_datetime now = Time.now d = DateTime.parse(now.to_s, :create_additions => true) # usual case assert_equal d, JSON.parse(d.to_json, :create_additions => true) d = DateTime.parse(now.utc.to_s) # of = 0 assert_equal d, JSON.parse(d.to_json, :create_additions => true) d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(1,24)) assert_equal d, JSON.parse(d.to_json, :create_additions => true) d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(12,24)) assert_equal d, JSON.parse(d.to_json, :create_additions => true) end def test_rational_complex assert_equal Rational(2, 9), JSON.parse(JSON(Rational(2, 9)), :create_additions => true) assert_equal Complex(2, 9), JSON.parse(JSON(Complex(2, 9)), :create_additions => true) end def test_bigdecimal assert_equal BigDecimal('3.141', 23), JSON(JSON(BigDecimal('3.141', 23)), :create_additions => true) assert_equal BigDecimal('3.141', 666), JSON(JSON(BigDecimal('3.141', 666)), :create_additions => true) end def test_ostruct o = OpenStruct.new # XXX this won't work; o.foo = { :bar => true } o.foo = { 'bar' => true } assert_equal o, JSON.parse(JSON(o), :create_additions => true) end end oj-3.13.9/test/json_gem/json_parser_test.rb0000644000004100000410000003525614136373754020770 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # frozen_string_literal: false require 'json_gem/test_helper' require 'stringio' require 'tempfile' require 'ostruct' class JSONParserTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport def test_construction parser = JSON::Parser.new('test') assert_equal 'test', parser.source end def test_argument_encoding source = "{}".encode("UTF-16") JSON::Parser.new(source) assert_equal Encoding::UTF_16, source.encoding end if defined?(Encoding::UTF_16) def test_error_message_encoding bug10705 = '[ruby-core:67386] [Bug #10705]' json = ".\"\xE2\x88\x9A\"".force_encoding(Encoding::UTF_8) e = assert_raise(JSON::ParserError) { JSON::Ext::Parser.new(json).parse } assert_equal(Encoding::UTF_8, e.message.encoding, bug10705) assert_include(e.message, json, bug10705) end if defined?(Encoding::UTF_8) def test_parsing parser = JSON::Parser.new('"test"') assert_equal 'test', parser.parse end def test_parser_reset parser = JSON::Parser.new('{"a":"b"}') assert_equal({ 'a' => 'b' }, parser.parse) assert_equal({ 'a' => 'b' }, parser.parse) end def test_parse_values assert_equal(nil, JSON.parse('null')) assert_equal(false, JSON.parse('false')) assert_equal(true, JSON.parse('true')) assert_equal(-23, JSON.parse('-23')) assert_equal(23, JSON.parse('23')) assert_in_delta(0.23, JSON.parse('0.23'), 1e-2) assert_in_delta(0.0, JSON.parse('0e0'), 1e-2) assert_equal("", JSON.parse('""')) assert_equal("foobar", JSON.parse('"foobar"')) end def test_parse_simple_arrays assert_equal([], JSON.parse('[]')) assert_equal([], JSON.parse(' [ ] ')) assert_equal([ nil ], JSON.parse('[null]')) assert_equal([ false ], JSON.parse('[false]')) assert_equal([ true ], JSON.parse('[true]')) assert_equal([ -23 ], JSON.parse('[-23]')) assert_equal([ 23 ], JSON.parse('[23]')) assert_equal_float([ 0.23 ], JSON.parse('[0.23]')) assert_equal_float([ 0.0 ], JSON.parse('[0e0]')) assert_equal([""], JSON.parse('[""]')) assert_equal(["foobar"], JSON.parse('["foobar"]')) assert_equal([{}], JSON.parse('[{}]')) end def test_parse_simple_objects assert_equal({}, JSON.parse('{}')) assert_equal({}, JSON.parse(' { } ')) assert_equal({ "a" => nil }, JSON.parse('{ "a" : null}')) assert_equal({ "a" => nil }, JSON.parse('{"a":null}')) assert_equal({ "a" => false }, JSON.parse('{ "a" : false } ')) assert_equal({ "a" => false }, JSON.parse('{"a":false}')) assert_raise(JSON::ParserError) { JSON.parse('{false}') } assert_equal({ "a" => true }, JSON.parse('{"a":true}')) assert_equal({ "a" => true }, JSON.parse(' { "a" : true } ')) assert_equal({ "a" => -23 }, JSON.parse(' { "a" : -23 } ')) assert_equal({ "a" => -23 }, JSON.parse(' { "a" : -23 } ')) assert_equal({ "a" => 23 }, JSON.parse('{"a":23 } ')) assert_equal({ "a" => 23 }, JSON.parse(' { "a" : 23 } ')) assert_equal({ "a" => 0.23 }, JSON.parse(' { "a" : 0.23 } ')) assert_equal({ "a" => 0.23 }, JSON.parse(' { "a" : 0.23 } ')) end def test_parse_numbers assert_raise(JSON::ParserError) { JSON.parse('+23.2') } assert_raise(JSON::ParserError) { JSON.parse('+23') } assert_raise(JSON::ParserError) { JSON.parse('.23') } assert_raise(JSON::ParserError) { JSON.parse('023') } assert_equal 23, JSON.parse('23') assert_equal -23, JSON.parse('-23') assert_equal_float 3.141, JSON.parse('3.141') assert_equal_float -3.141, JSON.parse('-3.141') assert_equal_float 3.141, JSON.parse('3141e-3') assert_equal_float 3.141, JSON.parse('3141.1e-3') assert_equal_float 3.141, JSON.parse('3141E-3') assert_equal_float 3.141, JSON.parse('3141.0E-3') assert_equal_float -3.141, JSON.parse('-3141.0e-3') assert_equal_float -3.141, JSON.parse('-3141e-3') assert_raise(JSON::ParserError) { JSON.parse('NaN') } assert JSON.parse('NaN', :allow_nan => true).nan? assert_raise(JSON::ParserError) { JSON.parse('Infinity') } assert_equal 1.0/0, JSON.parse('Infinity', :allow_nan => true) assert_raise(JSON::ParserError) { JSON.parse('-Infinity') } assert_equal -1.0/0, JSON.parse('-Infinity', :allow_nan => true) end if Array.method_defined?(:permutation) def test_parse_more_complex_arrays a = [ nil, false, true, "foßbar", [ "n€st€d", true ], { "nested" => true, "n€ßt€ð2" => {} }] a.permutation.each do |perm| json = JSON.pretty_generate(perm) assert_equal perm, JSON.parse(json) end end def test_parse_complex_objects a = [ nil, false, true, "foßbar", [ "n€st€d", true ], { "nested" => true, "n€ßt€ð2" => {} }] a.permutation.each do |perm| s = "a" orig_obj = perm.inject({}) { |h, x| h[s.dup] = x; s = s.succ; h } json = JSON.pretty_generate(orig_obj) assert_equal orig_obj, JSON.parse(json) end end end def test_parse_arrays assert_equal([1,2,3], JSON.parse('[1,2,3]')) assert_equal([1.2,2,3], JSON.parse('[1.2,2,3]')) assert_equal([[],[[],[]]], JSON.parse('[[],[[],[]]]')) assert_equal([], JSON.parse('[]')) assert_equal([], JSON.parse(' [ ] ')) assert_equal([1], JSON.parse('[1]')) assert_equal([1], JSON.parse(' [ 1 ] ')) ary = [[1], ["foo"], [3.14], [4711.0], [2.718], [nil], [[1, -2, 3]], [false], [true]] assert_equal(ary, JSON.parse('[[1],["foo"],[3.14],[47.11e+2],[2718.0E-3],[null],[[1,-2,3]],[false],[true]]')) assert_equal(ary, JSON.parse(%Q{ [ [1] , ["foo"] , [3.14] \t , [47.11e+2]\s , [2718.0E-3 ],\r[ null] , [[1, -2, 3 ]], [false ],[ true]\n ] })) end def test_parse_json_primitive_values assert_raise(JSON::ParserError) { JSON.parse('') } assert_raise(TypeError) { JSON.parse(nil) } assert_raise(JSON::ParserError) { JSON.parse(' /* foo */ ') } assert_equal nil, JSON.parse('null') assert_equal false, JSON.parse('false') assert_equal true, JSON.parse('true') assert_equal 23, JSON.parse('23') assert_equal 1, JSON.parse('1') assert_equal_float 3.141, JSON.parse('3.141'), 1E-3 assert_equal 2 ** 64, JSON.parse('18446744073709551616') assert_equal 'foo', JSON.parse('"foo"') assert JSON.parse('NaN', :allow_nan => true).nan? assert JSON.parse('Infinity', :allow_nan => true).infinite? assert JSON.parse('-Infinity', :allow_nan => true).infinite? assert_raise(JSON::ParserError) { JSON.parse('[ 1, ]') } end def test_parse_some_strings assert_equal([""], JSON.parse('[""]')) assert_equal(["\\"], JSON.parse('["\\\\"]')) assert_equal(['"'], JSON.parse('["\""]')) assert_equal(['\\"\\'], JSON.parse('["\\\\\\"\\\\"]')) assert_equal( ["\"\b\n\r\t\0\037"], JSON.parse('["\"\b\n\r\t\u0000\u001f"]') ) end def test_parse_big_integers json1 = JSON(orig = (1 << 31) - 1) assert_equal orig, JSON.parse(json1) json2 = JSON(orig = 1 << 31) assert_equal orig, JSON.parse(json2) json3 = JSON(orig = (1 << 62) - 1) assert_equal orig, JSON.parse(json3) json4 = JSON(orig = 1 << 62) assert_equal orig, JSON.parse(json4) json5 = JSON(orig = 1 << 64) assert_equal orig, JSON.parse(json5) end def test_some_wrong_inputs assert_raise(JSON::ParserError) { JSON.parse('[] bla') } assert_raise(JSON::ParserError) { JSON.parse('[] 1') } assert_raise(JSON::ParserError) { JSON.parse('[] []') } assert_raise(JSON::ParserError) { JSON.parse('[] {}') } assert_raise(JSON::ParserError) { JSON.parse('{} []') } assert_raise(JSON::ParserError) { JSON.parse('{} {}') } assert_raise(JSON::ParserError) { JSON.parse('[NULL]') } assert_raise(JSON::ParserError) { JSON.parse('[FALSE]') } assert_raise(JSON::ParserError) { JSON.parse('[TRUE]') } assert_raise(JSON::ParserError) { JSON.parse('[07] ') } assert_raise(JSON::ParserError) { JSON.parse('[0a]') } assert_raise(JSON::ParserError) { JSON.parse('[1.]') } assert_raise(JSON::ParserError) { JSON.parse(' ') } end def test_symbolize_names assert_equal({ "foo" => "bar", "baz" => "quux" }, JSON.parse('{"foo":"bar", "baz":"quux"}')) assert_equal({ :foo => "bar", :baz => "quux" }, JSON.parse('{"foo":"bar", "baz":"quux"}', :symbolize_names => true)) assert_raise(ArgumentError) do JSON.parse('{}', :symbolize_names => true, :create_additions => true) end end def test_parse_comments json = < "value1", "key2" => "value2", "key3" => "value3" }, JSON.parse(json)) json = < "value1" }, JSON.parse(json)) end def test_nesting too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' too_deep_ary = eval too_deep assert_raise(JSON::NestingError) { JSON.parse too_deep } assert_raise(JSON::NestingError) { JSON.parse too_deep, :max_nesting => 100 } ok = JSON.parse too_deep, :max_nesting => 101 assert_equal too_deep_ary, ok ok = JSON.parse too_deep, :max_nesting => nil assert_equal too_deep_ary, ok ok = JSON.parse too_deep, :max_nesting => false assert_equal too_deep_ary, ok ok = JSON.parse too_deep, :max_nesting => 0 assert_equal too_deep_ary, ok end def test_backslash data = [ '\\.(?i:gif|jpe?g|png)$' ] json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal data, JSON.parse(json) # data = [ '\\"' ] json = '["\\\\\""]' assert_equal data, JSON.parse(json) # json = '["/"]' data = [ '/' ] assert_equal data, JSON.parse(json) # json = '["\""]' data = ['"'] assert_equal data, JSON.parse(json) # json = '["\\\'"]' data = ["'"] assert_equal data, JSON.parse(json) end class SubArray < Array def <<(v) @shifted = true super end def shifted? @shifted end end class SubArray2 < Array def to_json(*a) { JSON.create_id => self.class.name, 'ary' => to_a, }.to_json(*a) end def self.json_create(o) o.delete JSON.create_id o['ary'] end end class SubArrayWrapper def initialize @data = [] end attr_reader :data def [](index) @data[index] end def <<(value) @data << value @shifted = true end def shifted? @shifted end end def test_parse_array_custom_array_derived_class res = JSON.parse('[1,2]', :array_class => SubArray) assert_equal([1,2], res) assert_equal(SubArray, res.class) assert res.shifted? end def test_parse_array_custom_non_array_derived_class res = JSON.parse('[1,2]', :array_class => SubArrayWrapper) assert_equal(SubArrayWrapper, res.class) assert_equal([1,2], res.data) assert res.shifted? end def test_parse_object assert_equal({}, JSON.parse('{}')) assert_equal({}, JSON.parse(' { } ')) assert_equal({'foo'=>'bar'}, JSON.parse('{"foo":"bar"}')) assert_equal({'foo'=>'bar'}, JSON.parse(' { "foo" : "bar" } ')) end class SubHash < Hash def []=(k, v) @item_set = true super end def item_set? @item_set end end class SubHash2 < Hash def to_json(*a) { JSON.create_id => self.class.name, }.merge(self).to_json(*a) end def self.json_create(o) o.delete JSON.create_id self[o] end end class SubOpenStruct < OpenStruct def [](k) __send__(k) end def []=(k, v) @item_set = true __send__("#{k}=", v) end def item_set? @item_set end end def test_parse_object_custom_hash_derived_class res = JSON.parse('{"foo":"bar"}', :object_class => SubHash) assert_equal(SubHash, res.class) assert_equal({"foo" => "bar"}, res) assert res.item_set? end def test_parse_object_custom_non_hash_derived_class res = JSON.parse('{"foo":"bar"}', :object_class => SubOpenStruct) assert_equal(SubOpenStruct, res.class) assert_equal "bar", res.foo assert res.item_set? end def test_parse_generic_object res = JSON.parse( '{"foo":"bar", "baz":{}}', :object_class => JSON::GenericObject ) assert_equal(JSON::GenericObject, res.class) assert_equal "bar", res.foo assert_equal "bar", res["foo"] assert_equal "bar", res[:foo] assert_equal "bar", res.to_hash[:foo] assert_equal(JSON::GenericObject, res.baz.class) end if defined?(JSON::GenericObject) def test_generate_core_subclasses_with_new_to_json obj = SubHash2["foo" => SubHash2["bar" => true]] obj_json = JSON(obj) obj_again = JSON.parse(obj_json, :create_additions => true) assert_kind_of SubHash2, obj_again assert_kind_of SubHash2, obj_again['foo'] assert obj_again['foo']['bar'] assert_equal obj, obj_again assert_equal ["foo"], JSON(JSON(SubArray2["foo"]), :create_additions => true) end def test_generate_core_subclasses_with_default_to_json assert_equal '{"foo":"bar"}', JSON(SubHash["foo" => "bar"]) assert_equal '["foo"]', JSON(SubArray["foo"]) end def test_generate_of_core_subclasses obj = SubHash["foo" => SubHash["bar" => true]] obj_json = JSON(obj) obj_again = JSON(obj_json) assert_kind_of Hash, obj_again assert_kind_of Hash, obj_again['foo'] assert obj_again['foo']['bar'] assert_equal obj, obj_again end def test_parsing_frozen_ascii8bit_string assert_equal( { 'foo' => 'bar' }, JSON('{ "foo": "bar" }'.force_encoding(Encoding::ASCII_8BIT).freeze) ) end private def assert_equal_float(expected, actual, delta = 1e-2) Array === expected and expected = expected.first Array === actual and actual = actual.first assert_in_delta(expected, actual, delta) end end oj-3.13.9/test/json_gem/json_generic_object_test.rb0000644000004100000410000000525314136373754022430 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' class JSONGenericObjectTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport def setup @go = JSON::GenericObject[ :a => 1, :b => 2 ] end def test_attributes assert_equal 1, @go.a assert_equal 1, @go[:a] assert_equal 2, @go.b assert_equal 2, @go[:b] assert_nil @go.c assert_nil @go[:c] end def test_generate_json switch_json_creatable do assert_equal @go, JSON(JSON(@go), :create_additions => true) end end def test_parse_json x = JSON( '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', :create_additions => true ) assert_kind_of Hash, JSON( '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', :create_additions => true ) switch_json_creatable do assert_equal @go, l = JSON( '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', :create_additions => true ) assert_equal 1, l.a assert_equal @go, l = JSON('{ "a": 1, "b": 2 }', :object_class => JSON::GenericObject) assert_equal 1, l.a assert_equal JSON::GenericObject[:a => JSON::GenericObject[:b => 2]], l = JSON('{ "a": { "b": 2 } }', :object_class => JSON::GenericObject) assert_equal 2, l.a.b end end def test_from_hash result = JSON::GenericObject.from_hash( :foo => { :bar => { :baz => true }, :quux => [ { :foobar => true } ] }) assert_kind_of JSON::GenericObject, result.foo assert_kind_of JSON::GenericObject, result.foo.bar assert_equal true, result.foo.bar.baz assert_kind_of JSON::GenericObject, result.foo.quux.first assert_equal true, result.foo.quux.first.foobar assert_equal true, JSON::GenericObject.from_hash(true) end def test_json_generic_object_load empty = JSON::GenericObject.load(nil) assert_kind_of JSON::GenericObject, empty simple_json = '{"json_class":"JSON::GenericObject","hello":"world"}' simple = JSON::GenericObject.load(simple_json) assert_kind_of JSON::GenericObject, simple assert_equal "world", simple.hello converting = JSON::GenericObject.load('{ "hello": "world" }') assert_kind_of JSON::GenericObject, converting assert_equal "world", converting.hello json = JSON::GenericObject.dump(JSON::GenericObject[:hello => 'world']) assert_equal JSON(json), JSON('{"json_class":"JSON::GenericObject","hello":"world"}') end private def switch_json_creatable JSON::GenericObject.json_creatable = true yield ensure JSON::GenericObject.json_creatable = false end end oj-3.13.9/test/json_gem/json_common_interface_test.rb0000644000004100000410000001174114136373754022775 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 #frozen_string_literal: false require 'json_gem/test_helper' require 'stringio' require 'tempfile' class JSONCommonInterfaceTest < Test::Unit::TestCase include Test::Unit::TestCaseOmissionSupport include Test::Unit::TestCasePendingSupport def setup @hash = { 'a' => 2, #'b' => 5.23683071, 'c' => 'c', 'd' => [ 1, "b", 3.14 ], 'e' => { 'foo' => 'bar' }, 'g' => "\"\0\037", 'h' => 1000.0, 'i' => 0.001 } # Tired of chasing floating point rounding and precision. Oj now uses the # Ruby float parser in compat mode yet on i386 machines there are issues # with this test when the float is included. #@json = '{"a":2,"b":5.23683071,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},'\ #'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}' @json = '{"a":2,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},'\ '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}' end def test_index assert_equal @json, JSON[@hash] assert_equal @hash, JSON[@json] end ############################################################################## # The next tests are omitted as implementing them and using them is a # performance hit. Use of the JSON.parse() and similar provide the same # functionality and perform better. def test_parser assert_match /::Parser\z/, JSON.parser.name end def test_generator assert_match /::Generator\z/, JSON.generator.name end def test_state assert_match /::Generator::State\z/, JSON.state.name end # This doesn't have anything to do with JSON parsing or generation. It seems # to be more of an internal tool that is exposed to users. def test_deep_const_get omit("mimic_JSON") unless REAL_JSON_GEM assert_raise(ArgumentError) { JSON.deep_const_get('Nix::Da') } assert_equal File::SEPARATOR, JSON.deep_const_get('File::SEPARATOR') end ############################################################################## def test_create_id assert_equal 'json_class', JSON.create_id JSON.create_id = 'foo_bar' assert_equal 'foo_bar', JSON.create_id ensure JSON.create_id = 'json_class' end def test_parse assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]') end def test_parse_bang # Modified this test to compare strings since NaN comparison fails if NaN # was defined in different places even if it represents the same value. assert_equal [ 1, NaN, 3, ].to_s, JSON.parse!('[ 1, NaN, 3 ]').to_s end def test_generate assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ]) end def test_fast_generate assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ]) end def test_pretty_generate assert_equal "[\n 1,\n 2,\n 3\n]", JSON.pretty_generate([ 1, 2, 3 ]) end def test_load assert_equal @hash, JSON.load(@json) tempfile = Tempfile.open('@json') tempfile.write @json tempfile.rewind assert_equal @hash, JSON.load(tempfile) stringio = StringIO.new(@json) stringio.rewind assert_equal @hash, JSON.load(stringio) assert_equal nil, JSON.load(nil) assert_equal nil, JSON.load('') ensure tempfile.close! end def test_load_with_options json = '{ "foo": NaN }' assert JSON.load(json, nil, :allow_nan => true)['foo'].nan? end def test_load_null assert_equal nil, JSON.load(nil, nil, :allow_blank => true) assert_raise(TypeError) { JSON.load(nil, nil, :allow_blank => false) } assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } # The next tests are added by Oj to catch additional cases. assert_equal nil, JSON.load('', nil, :allow_blank => true) assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } assert_raise(JSON::ParserError) { JSON.load(' ', nil, :allow_blank => true) } assert_raise(JSON::ParserError) { JSON.load(' ', nil, :allow_blank => false) } end def test_dump too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' assert_equal too_deep, JSON.dump(eval(too_deep)) assert_kind_of String, Marshal.dump(eval(too_deep)) assert_raise(ArgumentError) { JSON.dump(eval(too_deep), 100) } assert_raise(ArgumentError) { Marshal.dump(eval(too_deep), 100) } assert_equal too_deep, JSON.dump(eval(too_deep), 101) assert_kind_of String, Marshal.dump(eval(too_deep), 101) output = StringIO.new JSON.dump(eval(too_deep), output) assert_equal too_deep, output.string output = StringIO.new JSON.dump(eval(too_deep), output, 101) assert_equal too_deep, output.string end def test_dump_should_modify_defaults max_nesting = JSON.dump_default_options[:max_nesting] JSON.dump([], StringIO.new, 10) assert_equal max_nesting, JSON.dump_default_options[:max_nesting] end def test_JSON assert_equal @json, JSON(@hash) assert_equal @hash, JSON(@json) end end oj-3.13.9/test/json_gem/test_helper.rb0000644000004100000410000000127014136373754017707 0ustar www-datawww-data# frozen_string_literal: true $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__)))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'test/unit' REAL_JSON_GEM = !!ENV['REAL_JSON_GEM'] if ENV['REAL_JSON_GEM'] require 'json' else require 'oj' Oj.mimic_JSON if defined?(GC.verify_compaction_references) == 'method' # This method was added in Ruby 3.0.0. Calling it this way asks the GC to # move objects around, helping to find object movement bugs. GC.verify_compaction_references(double_heap: true, toward: :empty) end end NaN = JSON::NaN if defined?(JSON::NaN) NaN = 0.0/0 unless defined?(NaN) oj-3.13.9/test/perf_once.rb0000755000004100000410000000215714136373754015540 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' filename = 'tmp.json' File.open(filename, "w") { |f| cnt = 0 f.puts('{') ('a'..'z').each { |a| ('a'..'z').each { |b| ('a'..'z').each { |c| ('a'..'z').each { |d| f.puts(%|"#{a}#{b}#{c}#{d}":#{cnt},|) cnt += 1 } } } } f.puts('"_last":0}') } def mem `ps -o rss= -p #{$$}`.to_i end Oj.default_options = { mode: :strict, cache_keys: false, cache_str: -1 } start = Time.now Oj.load_file('tmp.json') dur = Time.now - start GC.start puts "no cache duration: #{dur} @ #{mem}" Oj.default_options = { cache_keys: true } start = Time.now Oj.load_file('tmp.json') dur = Time.now - start GC.start puts "initial cache duration: #{dur} @ #{mem}" start = Time.now Oj.load_file('tmp.json') dur = Time.now - start GC.start puts "second cache duration: #{dur} @ #{mem}" 10.times{ GC.start } start = Time.now Oj.load_file('tmp.json') dur = Time.now - start GC.start puts "after several GCs cache duration: #{dur} @ #{mem}" # TBD check memory use oj-3.13.9/test/perf_strict.rb0000755000004100000410000001075314136373754016125 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' $verbose = false $indent = 0 $iter = 20000 $with_bignum = false $with_nums = true $size = 0 $symbolize = false $cache_keys = true opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-n", "without numbers") { $with_nums = false } opts.on("-z", "--symbolize", "symbolize keys") { $symbolize = true } opts.on("-k", "--no-cache", "turn off key caching") { $cache_keys = false } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) if $with_nums $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj['g'] = 12345678901234567890123456789 if $with_bignum else $obj = { 'a' => 'Alpha', 'b' => true, 'c' => '12345', 'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]], 'e' => { 'zero' => '0', 'one' => '1', 'two' => '2' }, 'f' => nil, 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } end Oj.default_options = { :indent => $indent, :mode => :strict, cache_keys: $cache_keys, cache_str: 5 } if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj:strict', $obj, 'load', 'dump') { |o| Oj.strict_load(Oj.dump(o)) } capture_error('Yajl', $obj, 'encode', 'parse') { |o| require 'yajl' Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| require 'json' require 'json/ext' JSON.generator = JSON::Ext::Generator JSON.parser = JSON::Ext::Parser JSON.parse(JSON.generate(o)) } Oj.default_options = { symbol_keys: $symbolize } if $verbose puts "json:\n#{$json}\n" puts "Oj loaded object:\n#{Oj.strict_load($json)}\n" puts "Yajl loaded object:\n#{Yajl::Parser.parse($json)}\n" puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n" end puts '-' * 80 puts "Strict Parse Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'parse') { JSON.parse($json, symbolize_names: $symbolize) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } end unless $failed.has_key?('Oj:strict') perf.add('Oj:strict', 'strict_load') { Oj.strict_load($json) } perf.add('Oj:wab', 'wab_load') { Oj.wab_load($json) } end perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.run($iter) puts '-' * 80 puts "Strict Dump Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'dump') { JSON.generate($obj) } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } end unless $failed.has_key?('Oj:strict') perf.add('Oj:strict', 'dump') { Oj.dump($obj) } end perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) } unless $failed.has_key?('Yajl') perf.run($iter) puts puts '-' * 80 puts unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/test_debian.rb0000755000004100000410000000172214136373754016056 0ustar www-datawww-data# encoding: UTF-8 require 'helper' class DebJuice < Minitest::Test class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end# Jam # contributed by sauliusg to fix as_json class Orange < Jam def initialize(x, y) super end def as_json() { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end def test_as_json_object_compat_hash_cached Oj.default_options = { :mode => :compat, :class_cache => true, :use_as_json => true } obj = Orange.new(true, 58) json = Oj.dump(obj, :indent => 2) assert(!json.nil?) dump_and_load(obj, true) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) loaded = Oj.load(json); assert_equal(obj, loaded) loaded end end oj-3.13.9/test/_test_active.rb0000755000004100000410000000336314136373754016251 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) %w(lib ext test).each do |dir| $LOAD_PATH.unshift File.expand_path("../../#{dir}", __FILE__) end require 'minitest' require 'minitest/autorun' require 'sqlite3' require 'active_record' require 'oj' #Oj.mimic_JSON() Oj.default_options = {mode: :compat, indent: 2} #ActiveRecord::Base.logger = Logger.new(STDERR) ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => ":memory:" ) ActiveRecord::Schema.define do create_table :users do |table| table.column :first_name, :string table.column :last_name, :string table.column :email, :string end end class User < ActiveRecord::Base end class ActiveTest < Minitest::Test def test_active User.find_or_create_by(first_name: "John", last_name: "Smith", email: "john@example.com") User.find_or_create_by(first_name: "Joan", last_name: "Smith", email: "joan@example.com") # Single instance. assert_equal(%|{ "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" } |, Oj.dump(User.first)) # Array of instances. assert_equal(%|[ { "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" }, { "id":2, "first_name":"Joan", "last_name":"Smith", "email":"joan@example.com" } ] |, Oj.dump(User.all)) # Single instance as json. (not Oj) assert_equal(%|{"id":1,"first_name":"John","last_name":"Smith","email":"john@example.com"}|, User.first.to_json) # Array of instances as json. (not Oj) assert_equal(%|[{"id":1,"first_name":"John","last_name":"Smith","email":"john@example.com"},{"id":2,"first_name":"Joan","last_name":"Smith","email":"joan@example.com"}]|, User.all.to_json) end end oj-3.13.9/test/activesupport6/0000755000004100000410000000000014136373754016241 5ustar www-datawww-dataoj-3.13.9/test/activesupport6/encoding_test.rb0000644000004100000410000003637714136373754021433 0ustar www-datawww-data# frozen_string_literal: true require "securerandom" require_relative "abstract_unit" require "active_support/core_ext/string/inflections" require "active_support/json" require "active_support/time" require_relative "time_zone_test_helpers" require_relative "encoding_test_cases" require 'oj' # Sets the ActiveSupport encoder to be Oj and also wraps the setting of # globals. Oj::Rails.set_encoder() #Oj::Rails.optimize(Hash, Array, BigDecimal, Time, Range, Regexp, ActiveSupport::TimeWithZone) Oj::Rails.optimize() class TestJSONEncoding < ActiveSupport::TestCase include TimeZoneTestHelpers # Added for testing if Oj is used. test "oj is used as an encoder" do assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder end def sorted_json(json) if json.start_with?("{") && json.end_with?("}") "{" + json[1..-2].split(",").sort.join(",") + "}" else json end end JSONTest::EncodingTestCases.constants.each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do prev = ActiveSupport.use_standard_json_time_format standard_class_tests = /Standard/.match?(class_tests) ActiveSupport.escape_html_entities_in_json = !standard_class_tests ActiveSupport.use_standard_json_time_format = standard_class_tests JSONTest::EncodingTestCases.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end ensure ActiveSupport.escape_html_entities_in_json = false ActiveSupport.use_standard_json_time_format = prev end end def test_process_status rubinius_skip "https://github.com/rubinius/rubinius/issues/3334" # There doesn't seem to be a good way to get a handle on a Process::Status object without actually # creating a child process, hence this to populate $? system("not_a_real_program_#{SecureRandom.hex}") assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?) end def test_hash_encoding assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b) assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1) assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2]) assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2) assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d)) end def test_hash_keys_encoding ActiveSupport.escape_html_entities_in_json = true assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>") ensure ActiveSupport.escape_html_entities_in_json = false end def test_utf8_string_encoded_properly # The original test seems to expect that # ActiveSupport.escape_html_entities_in_json reverts to true even after # being set to false. I haven't been able to figure that out so the value is # set to true, the default, before running the test. This might be wrong but # for now it will have to do. ActiveSupport.escape_html_entities_in_json = true result = ActiveSupport::JSON.encode("€2.99") assert_equal '"€2.99"', result assert_equal(Encoding::UTF_8, result.encoding) result = ActiveSupport::JSON.encode("✎☺") assert_equal '"✎☺"', result assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes s = "二".encode("Shift_JIS") result = ActiveSupport::JSON.encode(s) assert_equal '"二"', result assert_equal Encoding::UTF_8, result.encoding end def test_wide_utf8_chars w = "𠜎" result = ActiveSupport::JSON.encode(w) assert_equal '"𠜎"', result end def test_wide_utf8_roundtrip hash = { string: "𐒑" } json = ActiveSupport::JSON.encode(hash) decoded_hash = ActiveSupport::JSON.decode(json) assert_equal "𐒑", decoded_hash["string"] end def test_hash_key_identifiers_are_always_quoted values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" } assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) end def test_hash_should_allow_key_filtering_with_only assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" }) end def test_hash_should_allow_key_filtering_with_except assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] }) end def test_time_to_json_includes_local_offset with_standard_json_time_format(true) do with_env_tz "US/Eastern" do assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10)) end end end def test_hash_with_time_to_json with_standard_json_time_format(false) do assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json end end def test_nested_hash_with_float assert_nothing_raised do hash = { "CHI" => { display_name: "chicago", latitude: 123.234 } } ActiveSupport::JSON.encode(hash) end end def test_hash_like_with_options h = JSONTest::Hashlike.new json = h.to_json only: [:foo] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_object_to_json_with_options obj = Object.new obj.instance_variable_set :@foo, "hello" obj.instance_variable_set :@bar, "world" json = obj.to_json only: ["foo"] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_struct_to_json_with_options struct = Struct.new(:foo, :bar).new struct.foo = "hello" struct.bar = "world" json = struct.to_json only: [:foo] assert_equal({ "foo" => "hello" }, JSON.parse(json)) end def test_struct_to_json_with_options_nested klass = Struct.new(:foo, :bar) struct = klass.new "hello", "world" parent_struct = klass.new struct, "world" json = parent_struct.to_json only: [:foo] assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json)) end def test_hash_should_pass_encoding_options_to_children_in_as_json person = { name: "John", address: { city: "London", country: "UK" } } json = person.as_json only: [:address, :city] assert_equal({ "address" => { "city" => "London" } }, json) end def test_hash_should_pass_encoding_options_to_children_in_to_json person = { name: "John", address: { city: "London", country: "UK" } } json = person.to_json only: [:address, :city] assert_equal(%({"address":{"city":"London"}}), json) end def test_array_should_pass_encoding_options_to_children_in_as_json people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_array_should_pass_encoding_options_to_children_in_to_json people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end People = Class.new(BasicObject) do include Enumerable def initialize @people = [ { name: "John", address: { city: "London", country: "UK" } }, { name: "Jean", address: { city: "Paris", country: "France" } } ] end def each(*, &blk) @people.each do |p| yield p if blk p end.each end end def test_enumerable_should_generate_json_with_as_json json = People.new.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_generate_json_with_to_json json = People.new.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end def test_enumerable_should_pass_encoding_options_to_children_in_as_json json = People.new.each.as_json only: [:address, :city] expected = [ { "address" => { "city" => "London" } }, { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_pass_encoding_options_to_children_in_to_json json = People.new.each.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end class CustomWithOptions attr_accessor :foo, :bar def as_json(options = {}) options[:only] = %w(foo bar) super(options) end end def test_hash_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } } assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" }, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json)) end def test_array_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" f.bar = "world" array = [f, { "foo" => "other_foo", "test" => "other_test" }] assert_equal([{ "foo" => "hello", "bar" => "world" }, { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json)) end class OptionsTest def as_json(options = :default) options end end def test_hash_as_json_without_options json = { foo: OptionsTest.new }.as_json assert_equal({ "foo" => :default }, json) end def test_array_as_json_without_options json = [ OptionsTest.new ].as_json assert_equal([:default], json) end def test_struct_encoding Struct.new("UserNameAndEmail", :name, :email) Struct.new("UserNameAndDate", :name, :date) Struct.new("Custom", :name, :sub) user_email = Struct::UserNameAndEmail.new "David", "sample@example.com" user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01) custom = Struct::Custom.new "David", user_birthday json_strings = "" json_string_and_date = "" json_custom = "" assert_nothing_raised do json_strings = user_email.to_json json_string_and_date = user_birthday.to_json json_custom = custom.to_json end assert_equal({ "name" => "David", "sub" => { "name" => "David", "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom)) assert_equal({ "name" => "David", "email" => "sample@example.com" }, ActiveSupport::JSON.decode(json_strings)) assert_equal({ "name" => "David", "date" => "2010-01-01" }, ActiveSupport::JSON.decode(json_string_and_date)) end def test_nil_true_and_false_represented_as_themselves assert_nil nil.as_json assert_equal true, true.as_json assert_equal false, false.as_json end class HashWithAsJson < Hash attr_accessor :as_json_called def initialize(*) super end def as_json(options = {}) @as_json_called = true super end end def test_json_gem_dump_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h) assert_nil h.as_json_called end def test_json_gem_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h) assert_nil h.as_json_called end def test_json_gem_pretty_generate_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" h[:bar] = "world" assert_equal < Float::INFINITY } end end def test_to_json_works_when_as_json_returns_infinite_number assert_equal '{"number":null}', InfiniteNumber.new.to_json end class NaNNumber def as_json(options = nil) { "number" => Float::NAN } end end def test_to_json_works_when_as_json_returns_NaN_number assert_equal '{"number":null}', NaNNumber.new.to_json end def test_to_json_works_on_io_objects assert_equal STDOUT.to_s.to_json, STDOUT.to_json end private def object_keys(json_object) json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort end def with_standard_json_time_format(boolean = true) old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean yield ensure ActiveSupport.use_standard_json_time_format = old end def with_time_precision(value) old_value = ActiveSupport::JSON::Encoding.time_precision ActiveSupport::JSON::Encoding.time_precision = value yield ensure ActiveSupport::JSON::Encoding.time_precision = old_value end end oj-3.13.9/test/activesupport6/time_zone_test_helpers.rb0000644000004100000410000000170714136373754023345 0ustar www-datawww-data# frozen_string_literal: true module TimeZoneTestHelpers def with_tz_default(tz = nil) old_tz = Time.zone Time.zone = tz yield ensure Time.zone = old_tz end def with_env_tz(new_tz = "US/Eastern") old_tz, ENV["TZ"] = ENV["TZ"], new_tz yield ensure old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ") end def with_preserve_timezone(value) old_preserve_tz = ActiveSupport.to_time_preserves_timezone ActiveSupport.to_time_preserves_timezone = value yield ensure ActiveSupport.to_time_preserves_timezone = old_preserve_tz end def with_tz_mappings(mappings) old_mappings = ActiveSupport::TimeZone::MAPPING.dup ActiveSupport::TimeZone.clear ActiveSupport::TimeZone::MAPPING.clear ActiveSupport::TimeZone::MAPPING.merge!(mappings) yield ensure ActiveSupport::TimeZone.clear ActiveSupport::TimeZone::MAPPING.clear ActiveSupport::TimeZone::MAPPING.merge!(old_mappings) end end oj-3.13.9/test/activesupport6/abstract_unit.rb0000644000004100000410000000231014136373754021424 0ustar www-datawww-data# frozen_string_literal: true ORIG_ARGV = ARGV.dup require "active_support/core_ext/kernel/reporting" silence_warnings do Encoding.default_internal = Encoding::UTF_8 Encoding.default_external = Encoding::UTF_8 end require "active_support/testing/autorun" require "active_support/testing/method_call_assertions" ENV["NO_RELOAD"] = "1" require "active_support" Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true # Default to old to_time behavior but allow running tests with new behavior ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1" # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false class ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions private # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = "") skip message if RUBY_ENGINE == "rbx" end # Skips the current run on JRuby using Minitest::Assertions#skip def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end end require_relative "test_common" oj-3.13.9/test/activesupport6/encoding_test_cases.rb0000644000004100000410000001012314136373754022566 0ustar www-datawww-data# frozen_string_literal: true require "bigdecimal" require "date" require "time" require "pathname" require "uri" module JSONTest class Foo def initialize(a, b) @a, @b = a, b end end class Hashlike def to_hash { foo: "hello", bar: "world" } end end class Custom def initialize(serialized) @serialized = serialized end def as_json(options = nil) @serialized end end MyStruct = Struct.new(:name, :value) do def initialize(*) @unused = "unused instance variable" super end end module EncodingTestCases TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] NumericTests = [[ 1, %(1) ], [ 2.5, %(2.5) ], [ 0.0 / 0.0, %(null) ], [ 1.0 / 0.0, %(null) ], [ -1.0 / 0.0, %(null) ], [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ], [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]] StringTests = [[ "this is the ", %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], [ "http://test.host/posts/1", %("http://test.host/posts/1")], [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ], [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ], [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]] RangeTests = [[ 1..2, %("1..2")], [ 1...2, %("1...2")], [ 1.5..2.5, %("1.5..2.5")]] SymbolTests = [[ :a, %("a") ], [ :this, %("this") ], [ :"a b", %("a b") ]] ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ], [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]] CustomTests = [[ Custom.new("custom"), '"custom"' ], [ Custom.new(nil), "null" ], [ Custom.new(:a), '"a"' ], [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]] PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]] DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]] TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]] DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]] StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]] StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]] StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]] StandardStringTests = [[ "this is the ", %("this is the ")]] end end oj-3.13.9/test/activesupport6/decoding_test.rb0000644000004100000410000001344014136373754021403 0ustar www-datawww-data# frozen_string_literal: true require_relative "abstract_unit" require "active_support/json" require "active_support/time" require_relative "time_zone_test_helpers" require 'oj' Oj::Rails.set_decoder() class TestJSONDecoding < ActiveSupport::TestCase include TimeZoneTestHelpers # Added for testing if Oj is used. test "oj is used as an encoder" do assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder end class Foo def self.json_create(object) "Foo" end end TESTS = { %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } }, %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } }, %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } }, %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] }, %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] }, %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" }, %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" }, # multibyte %({"matzue": "松江", "asakusa": "浅草"}) => { "matzue" => "松江", "asakusa" => "浅草" }, %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) }, %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) }, %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], # no time zone %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") }, # invalid date %({"a": "1089-10-40"}) => { "a" => "1089-10-40" }, # xmlschema date notation %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") }, %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) }, %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) }, %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) }, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " }, %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" }, %([]) => [], %({}) => {}, %({"a":1}) => { "a" => 1 }, %({"a": ""}) => { "a" => "" }, %({"a":"\\""}) => { "a" => "\"" }, %({"a": null}) => { "a" => nil }, %({"a": true}) => { "a" => true }, %({"a": false}) => { "a" => false }, '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" }, %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" }, %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "" }, '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" }, %q({"a": "\u003cbr /\u003e"}) => { "a" => "
" }, %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["", "", ""] }, # test combination of dates and escaped or unicode encoded data in arrays %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => [{ "d" => Date.new(1970, 1, 1), "s" => " escape" }, { "d" => Date.new(1970, 1, 1), "s" => " escape" }], %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }, { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }], # tests escaping of "\n" char with Yaml backend %q({"a":"\n"}) => { "a" => "\n" }, %q({"a":"\u000a"}) => { "a" => "\n" }, %q({"a":"Line1\u000aLine2"}) => { "a" => "Line1\nLine2" }, # prevent json unmarshalling '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class" => "TestJSONDecoding::Foo" }, # json "fragments" - these are invalid JSON, but ActionPack relies on this '"a string"' => "a string", "1.1" => 1.1, "1" => 1, "-1" => -1, "true" => true, "false" => false, "null" => nil } TESTS.each_with_index do |(json, expected), index| fail_message = "JSON decoding failed for #{json}" test "json decodes #{index}" do with_tz_default "Eastern Time (US & Canada)" do with_parse_json_times(true) do silence_warnings do if expected.nil? assert_nil ActiveSupport::JSON.decode(json), fail_message else assert_equal expected, ActiveSupport::JSON.decode(json), fail_message end end end end end end test "json decodes time json with time parsing disabled" do with_parse_json_times(false) do expected = { "a" => "2007-01-01 01:12:34 Z" } assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end end def test_failed_json_decoding assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } end def test_cannot_pass_unsupported_options assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) } end private def with_parse_json_times(value) old_value = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = value yield ensure ActiveSupport.parse_json_times = old_value end end oj-3.13.9/test/activesupport6/test_common.rb0000644000004100000410000000076114136373754021121 0ustar www-datawww-data# frozen_string_literal: true if ENV["BUILDKITE"] require "minitest/reporters" require "fileutils" module Minitest def self.plugin_rails_ci_junit_format_test_report_for_buildkite_init(*) dir = File.join(__dir__, "../test-reports/#{ENV['BUILDKITE_JOB_ID']}") reporter << Minitest::Reporters::JUnitReporter.new(dir, false) FileUtils.mkdir_p(dir) end end Minitest.load_plugins Minitest.extensions.unshift "rails_ci_junit_format_test_report_for_buildkite" end oj-3.13.9/test/activesupport6/test_helper.rb0000644000004100000410000001404514136373754021110 0ustar www-datawww-data# frozen_string_literal: true gem "minitest" # make sure we get the gem, not stdlib require "minitest" require "active_support/testing/tagged_logging" require "active_support/testing/setup_and_teardown" require "active_support/testing/assertions" require "active_support/testing/deprecation" require "active_support/testing/declarative" require "active_support/testing/isolation" require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" require "active_support/testing/parallelization" require "concurrent/utility/processor_counter" module ActiveSupport class TestCase < ::Minitest::Test Assertion = Minitest::Assertion class << self # Sets the order in which test cases are run. # # ActiveSupport::TestCase.test_order = :random # => :random # # Valid values are: # * +:random+ (to run tests in random order) # * +:parallel+ (to run tests in parallel) # * +:sorted+ (to run tests alphabetically by method name) # * +:alpha+ (equivalent to +:sorted+) def test_order=(new_order) ActiveSupport.test_order = new_order end # Returns the order in which test cases are run. # # ActiveSupport::TestCase.test_order # => :random # # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. # Defaults to +:random+. def test_order ActiveSupport.test_order ||= :random end # Parallelizes the test suite. # # Takes a +workers+ argument that controls how many times the process # is forked. For each process a new database will be created suffixed # with the worker number. # # test-database-0 # test-database-1 # # If ENV["PARALLEL_WORKERS"] is set the workers argument will be ignored # and the environment variable will be used instead. This is useful for CI # environments, or other environments where you may need more workers than # you do for local testing. # # If the number of workers is set to +1+ or fewer, the tests will not be # parallelized. # # If +workers+ is set to +:number_of_processors+, the number of workers will be # set to the actual core count on the machine you are on. # # The default parallelization method is to fork processes. If you'd like to # use threads instead you can pass with: :threads to the +parallelize+ # method. Note the threaded parallelization does not create multiple # database and will not work with system tests at this time. # # parallelize(workers: :number_of_processors, with: :threads) # # The threaded parallelization uses minitest's parallel executor directly. # The processes parallelization uses a Ruby DRb server. def parallelize(workers: :number_of_processors, with: :processes) workers = Concurrent.physical_processor_count if workers == :number_of_processors workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] return if workers <= 1 executor = case with when :processes Testing::Parallelization.new(workers) when :threads Minitest::Parallel::Executor.new(workers) else raise ArgumentError, "#{with} is not a supported parallelization executor." end self.lock_threads = false if defined?(self.lock_threads) && with == :threads Minitest.parallel_executor = executor parallelize_me! end # Set up hook for parallel testing. This can be used if you have multiple # databases or any behavior that needs to be run after the process is forked # but before the tests run. # # Note: this feature is not available with the threaded parallelization. # # In your +test_helper.rb+ add the following: # # class ActiveSupport::TestCase # parallelize_setup do # # create databases # end # end def parallelize_setup(&block) ActiveSupport::Testing::Parallelization.after_fork_hook do |worker| yield worker end end # Clean up hook for parallel testing. This can be used to drop databases # if your app uses multiple write/read databases or other clean up before # the tests finish. This runs before the forked process is closed. # # Note: this feature is not available with the threaded parallelization. # # In your +test_helper.rb+ add the following: # # class ActiveSupport::TestCase # parallelize_teardown do # # drop databases # end # end def parallelize_teardown(&block) ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker| yield worker end end end alias_method :method_name, :name include ActiveSupport::Testing::TaggedLogging prepend ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation include ActiveSupport::Testing::TimeHelpers include ActiveSupport::Testing::FileFixtures extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods alias :assert_raise :assert_raises alias :assert_not_empty :refute_empty alias :assert_not_equal :refute_equal alias :assert_not_in_delta :refute_in_delta alias :assert_not_in_epsilon :refute_in_epsilon alias :assert_not_includes :refute_includes alias :assert_not_instance_of :refute_instance_of alias :assert_not_kind_of :refute_kind_of alias :assert_no_match :refute_match alias :assert_not_nil :refute_nil alias :assert_not_operator :refute_operator alias :assert_not_predicate :refute_predicate alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same ActiveSupport.run_load_hooks(:active_support_test_case, self) end end oj-3.13.9/test/perf_compat.rb0000755000004100000410000000705514136373754016101 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' $verbose = false $indent = 0 $iter = 20000 $size = 0 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) puts obj unless orig == obj raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj:compat', $obj, 'load', 'dump') { |o| Oj.compat_load(Oj.dump(o, :mode => :compat)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| require 'json' require 'json/ext' JSON.generator = JSON::Ext::Generator JSON.parser = JSON::Ext::Parser JSON.load(JSON.generate(o)) } module One module Two module Three class Empty def initialize() @a = 1 @b = 2 @c = 3 end def eql?(o) self.class == o.class && @a == o.a && @b = o.b && @c = o.c end alias == eql? def as_json(*a) {JSON.create_id => self.class.name, 'a' => @a, 'b' => @b, 'c' => @c } end def to_json(*a) JSON.generate(as_json()) end def self.json_create(h) self.new() end end # Empty end # Three end # Two end # One $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'g' => One::Two::Three::Empty.new(), 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat, :use_to_json => true, :create_additions => true, :create_id => '^o' } if 0 < $size s = Oj.dump($obj).size + 1 cnt = $size * 1024 / s o = $obj $obj = [] cnt.times do $obj << o end end $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later if $verbose puts "size: #{$json.size}" puts "json:\n#{$json}\n" puts "Oj:compat loaded object:\n#{Oj.compat_load($json)}\n" puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n" end puts '-' * 80 puts "Compat Parse Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'parse') { JSON.load($json) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } end unless $failed.has_key?('Oj:compat') perf.add('Oj:compat', 'compat_load') { Oj.compat_load($json) } end perf.run($iter) puts puts '-' * 80 puts unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/_test_active_mimic.rb0000755000004100000410000000346614136373754017433 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) %w(lib ext test).each do |dir| $LOAD_PATH.unshift File.expand_path("../../#{dir}", __FILE__) end require 'minitest' require 'minitest/autorun' require 'sqlite3' require 'active_record' require 'oj' Oj.mimic_JSON() Oj.default_options = {mode: :compat, indent: 2} #ActiveRecord::Base.logger = Logger.new(STDERR) ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => ":memory:" ) ActiveRecord::Schema.define do create_table :users do |table| table.column :first_name, :string table.column :last_name, :string table.column :email, :string end end class User < ActiveRecord::Base end class ActiveTest < Minitest::Test def test_active User.find_or_create_by(first_name: "John", last_name: "Smith", email: "john@example.com") User.find_or_create_by(first_name: "Joan", last_name: "Smith", email: "joan@example.com") # Single instance. assert_equal(%|{ "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" } |, Oj.dump(User.first)) # Array of instances. assert_equal(%|[ { "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" }, { "id":2, "first_name":"Joan", "last_name":"Smith", "email":"joan@example.com" } ] |, Oj.dump(User.all)) # Single instance as json. (not Oj) assert_equal(%|{ "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" } |, User.first.to_json) # Array of instances as json. (not Oj) assert_equal(%|[ { "id":1, "first_name":"John", "last_name":"Smith", "email":"john@example.com" }, { "id":2, "first_name":"Joan", "last_name":"Smith", "email":"joan@example.com" } ] |, User.all.to_json) end end oj-3.13.9/test/test_custom.rb0000755000004100000410000003346014136373754016152 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class CustomJuice < Minitest::Test module TestModule end class Jeez attr_accessor :x, :y, :_z def initialize(x, y) @x = x @y = y @_z = x.to_s end def ==(o) self.class == o.class && @x == o.x && @y = o.y end def to_json(*args) %|{"xx":#{@x},"yy":#{y}}| end def raw_json(depth, indent) %|{"xxx":#{@x},"yyy":#{y}}| end def as_json(*args) {'a' => @x, :b => @y } end def to_hash() {'b' => @x, 'n' => @y } end end class AsJson attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def ==(o) self.class == o.class && @x == o.x && @y = o.y end def as_json(*args) {'a' => @x, :b => @y } end end class AsRails attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def ==(o) self.class == o.class && @x == o.x && @y = o.y end def as_json(*args) a = @x a = a.as_json if a.respond_to?('as_json') b = @y b = b.as_json if b.respond_to?('as_json') {'a' => a, :b => b } end end def setup @default_options = Oj.default_options Oj.default_options = { :mode => :custom } end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_float_parse f = Oj.load("12.123456789012345678", mode: :custom, bigdecimal_load: :float); assert_equal(Float, f.class) end def test_float_parse_fast f = Oj.load("12.123456789012345678", mode: :custom, bigdecimal_load: :fast); assert_equal(Float, f.class) assert(12.12345678901234 <= f && f < 12.12345678901236) end def test_nan_dump assert_equal('null', Oj.dump(0/0.0, :nan => :null)) assert_equal('3.3e14159265358979323846', Oj.dump(0/0.0, :nan => :huge)) assert_equal('NaN', Oj.dump(0/0.0, :nan => :word)) begin Oj.dump(0/0.0, :nan => :raise) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_infinity_dump assert_equal('null', Oj.dump(1/0.0, :nan => :null)) assert_equal('3.0e14159265358979323846', Oj.dump(1/0.0, :nan => :huge)) assert_equal('Infinity', Oj.dump(1/0.0, :nan => :word)) begin Oj.dump(1/0.0, :nan => :raise) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_neg_infinity_dump assert_equal('null', Oj.dump(-1/0.0, :nan => :null)) assert_equal('-3.0e14159265358979323846', Oj.dump(-1/0.0, :nan => :huge)) assert_equal('-Infinity', Oj.dump(-1/0.0, :nan => :word)) begin Oj.dump(-1/0.0, :nan => :raise) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_string_ascii json = Oj.dump("ぴーたー", :escape_mode => :ascii) assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) dump_and_load("ぴーたー", false, :escape_mode => :ascii) end def test_string_json json = Oj.dump("ぴーたー", :escape_mode => :json) assert_equal(%{"ぴーたー"}, json) dump_and_load("ぴーたー", false, :escape_mode => :json) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end def test_deep_nest begin n = 10000 Oj.strict_load('[' * n + ']' * n) rescue Exception => e assert(false, e.message) end end def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_hash_non_string_key assert_equal(%|{"1":true}|, Oj.dump({ 1 => true }, :indent => 0)) end def test_bignum_object dump_and_load(7 ** 55, false) end def test_bigdecimal assert_equal('0.314159265358979323846e1', Oj.dump(BigDecimal('3.14159265358979323846'), bigdecimal_as_decimal: true).downcase()) assert_equal('"0.314159265358979323846e1"', Oj.dump(BigDecimal('3.14159265358979323846'), bigdecimal_as_decimal: false).downcase()) dump_and_load(BigDecimal('3.14159265358979323846'), false, :bigdecimal_load => true) end def test_object obj = Jeez.new(true, 58) json = Oj.dump(obj, create_id: "^o", use_to_json: false, use_as_json: false, use_to_hash: false) assert_equal(%|{"x":true,"y":58,"_z":"true"}|, json) json = Oj.dump(obj, create_id: "^o", use_to_json: false, use_as_json: false, use_to_hash: false, ignore_under: true) assert_equal(%|{"x":true,"y":58}|, json) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_object_to_json obj = Jeez.new(true, 58) json = Oj.dump(obj, :use_to_json => true, :use_as_json => false, :use_to_hash => false) assert_equal(%|{"xx":true,"yy":58}|, json) end def test_object_as_json obj = Jeez.new(true, 58) json = Oj.dump(obj, :use_to_json => false, :use_as_json => true, :use_to_hash => false) assert_equal(%|{"a":true,"b":58}|, json) end def test_object_to_hash obj = Jeez.new(true, 58) json = Oj.dump(obj, :use_to_json => false, :use_as_json => false, :use_to_hash => true) assert_equal(%|{"b":true,"n":58}|, json) end def test_object_raw_json obj = Jeez.new(true, 58) json = Oj.dump(obj, :use_to_json => true, :use_as_json => false, :use_raw_json => true, :use_to_hash => false) assert_equal(%|{"xxx":true,"yyy":58}|, json) end def test_raw_json_stringwriter obj = Oj::StringWriter.new(:indent => 0) obj.push_array() obj.pop() json = Oj.dump(obj, :use_raw_json => true) assert_equal(%|[]|, json) end def test_as_raw_json_stringwriter obj = Oj::StringWriter.new(:indent => 0) obj.push_array() obj.push_value(3) obj.pop() j = AsJson.new(1, obj) json = Oj.dump(j, use_raw_json: true, use_as_json: true, indent: 2) assert_equal(%|{ "a":1, "b":[3] } |, json) json = Oj.dump(j, use_raw_json: false, use_as_json: true, indent: 2) assert_equal(%|{ "a":1, "b":{} } |, json) end def test_rails_as_raw_json_stringwriter obj = Oj::StringWriter.new(:indent => 0) obj.push_array() obj.push_value(3) obj.pop() j = AsRails.new(1, obj) json = Oj.dump(j, mode: :rails, use_raw_json: true, indent: 2) assert_equal(%|{ "a":1, "b":{} } |, json) Oj::Rails.optimize json = Oj.dump(j, mode: :rails, use_raw_json: true, indent: 2) Oj::Rails.deoptimize assert_equal(%|{ "a":1, "b":[3] } |, json) end def test_symbol json = Oj.dump(:abc) assert_equal('"abc"', json) end def test_class assert_equal(%|"CustomJuice"|, Oj.dump(CustomJuice)) end def test_module assert_equal(%|"CustomJuice::TestModule"|, Oj.dump(TestModule)) end def test_symbol_keys json = %|{ "x":true, "y":58, "z": [1,2,3] } | obj = Oj.load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end def test_double json = %{{ "x": 1}{ "y": 2}} results = [] Oj.load(json, :mode => :strict) { |x| results << x } assert_equal([{ 'x' => 1 }, { 'y' => 2 }], results) end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :indent => 2, :circular => true) assert_equal(%|{ "a":7, "b":null } |, json) end def test_omit_nil json = Oj.dump({'x' => {'a' => 1, 'b' => nil }, 'y' => nil}, :omit_nil => true) assert_equal(%|{"x":{"a":1}}|, json) end def test_complex obj = Complex(2, 9) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_rational obj = Rational(2, 9) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_range obj = 3..8 dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_date obj = Date.new(2017, 1, 5) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_date_unix obj = Date.new(2017, 1, 5) json = Oj.dump(obj, :indent => 2, time_format: :unix) assert_equal('1483574400.000000000', json) end def test_date_unix_zone obj = Date.new(2017, 1, 5) json = Oj.dump(obj, :indent => 2, time_format: :unix_zone) assert_equal('1483574400.000000000', json) end def test_date_ruby obj = Date.new(2017, 1, 5) json = Oj.dump(obj, :indent => 2, time_format: :ruby) assert_equal('"2017-01-05"', json) end def test_date_xmlschema obj = Date.new(2017, 1, 5) json = Oj.dump(obj, :indent => 2, time_format: :xmlschema) assert_equal('"2017-01-05"', json) end def test_datetime obj = DateTime.new(2017, 1, 5, 10, 20, 30) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_datetime_unix obj = DateTime.new(2017, 1, 5, 10, 20, 30, '-0500') json = Oj.dump(obj, :indent => 2, time_format: :unix) assert_equal('1483629630.000000000', json) end def test_datetime_unix_zone # older versions seems to have issues getting the utc offset. if '2.4' <= RUBY_VERSION obj = DateTime.new(2017, 1, 5, 10, 20, 30, '-0500') json = Oj.dump(obj, :indent => 2, time_format: :unix_zone) assert_equal('1483629630.000000000e-18000', json) end end def test_datetime_ruby obj = DateTime.new(2017, 1, 5, 10, 20, 30, '-0500') json = Oj.dump(obj, :indent => 2, time_format: :ruby) assert_equal('"2017-01-05T10:20:30-05:00"', json) end def test_datetime_xmlschema obj = DateTime.new(2017, 1, 5, 10, 20, 30, '-0500') json = Oj.dump(obj, :indent => 2, time_format: :xmlschema) assert_equal('"2017-01-05T10:20:30-05:00"', json) end def test_regexp # this notation must be used to get an == match later obj = /(?ix-m:^yes$)/ dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_openstruct obj = OpenStruct.new(:a => 1, 'b' => 2) dump_and_load(obj, false, :create_id => "^o", :create_additions => true) end def test_time obj = Time.now() dump_load_dump(obj, false, :time_format => :unix, :create_id => "^o", :create_additions => true) dump_load_dump(obj, false, :time_format => :unix_zone, :create_id => "^o", :create_additions => true) dump_load_dump(obj, false, :time_format => :xmlschema, :create_id => "^o", :create_additions => true) dump_load_dump(obj, false, :time_format => :ruby, :create_id => "^o", :create_additions => true) end def dump_and_load(obj, trace=false, options={}) options = options.merge(:indent => 2, :mode => :custom) json = Oj.dump(obj, options) puts json if trace loaded = Oj.load(json, options); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end def dump_and_load_inspect(obj, trace=false, options={}) options = options.merge(:indent => 2, :mode => :custom) json = Oj.dump(obj, options) puts json if trace loaded = Oj.load(json, options); if obj.nil? assert_nil(loaded) else assert_equal(obj.inspect, loaded.inspect) end loaded end def dump_load_dump(obj, trace=false, options={}) options = options.merge(:indent => 2, :mode => :custom) json = Oj.dump(obj, options) puts json if trace loaded = Oj.load(json, options); if obj.nil? assert_nil(loaded) else json2 = Oj.dump(loaded, options) assert_equal(json, json2) end loaded end end oj-3.13.9/test/test_gc.rb0000755000004100000410000000164014136373754015224 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' class GCTest < Minitest::Test class Goo attr_accessor :x, :child def initialize(x, child) @x = x @child = child end def to_hash() { 'json_class' => "#{self.class}", 'x' => x, 'child' => child } end def self.json_create(h) GC.start self.new(h['x'], h['child']) end end # Goo def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end # if no crash then the GC marking is working def test_parse_compat_gc g = Goo.new(0, nil) 100.times { |i| g = Goo.new(i, g) } json = Oj.dump(g, :mode => :compat) Oj.compat_load(json) end def test_parse_object_gc g = Goo.new(0, nil) 100.times { |i| g = Goo.new(i, g) } json = Oj.dump(g, :mode => :object) Oj.object_load(json) end end oj-3.13.9/test/test_integer_range.rb0000755000004100000410000000426214136373754017447 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'oj' class IntegerRangeTest < Minitest::Test def setup @default_options = Oj.default_options # in null mode other options other than the number formats are not used. Oj.default_options = { :mode => :null, bigdecimal_as_decimal: true } end def teardown Oj.default_options = @default_options end def test_range test = {s: 0, s2: -1, s3: 1, u: -2, u2: 2, u3: 9007199254740993} exp = '{"s":0,"s2":-1,"s3":1,"u":"-2","u2":"2","u3":"9007199254740993"}' assert_equal(exp, Oj.dump(test, integer_range: (-1..1))) end def test_bignum test = {u: -10000000000000000000, u2: 10000000000000000000} exp = '{"u":"-10000000000000000000","u2":"10000000000000000000"}' assert_equal(exp, Oj.dump(test, integer_range: (-1..1))) end def test_valid_modes test = {safe: 0, unsafe: 9007199254740993} exp = '{"safe":0,"unsafe":"9007199254740993"}' [:strict, :null, :compat, :rails, :custom].each do |mode| assert_equal(exp, Oj.dump(test, mode: mode, integer_range: (-1..1)), "Invalid mode #{mode}") end exp = '{":safe":0,":unsafe":"9007199254740993"}' [:object].each do |mode| assert_equal(exp, Oj.dump(test, mode: mode, integer_range: (-1..1)), "Invalid mode #{mode}") end end def test_modes_without_opt test = {safe:0, unsafe: 10000000000000000000} exp = '{"safe":0,"unsafe":10000000000000000000}' [:strict, :null, :compat, :rails, :custom].each do |mode| assert_equal(exp, Oj.dump(test, mode: mode), "Invalid mode #{mode}") end exp = '{":safe":0,":unsafe":10000000000000000000}' [:object].each do |mode| assert_equal(exp, Oj.dump(test, mode: mode), "Invalid mode #{mode}") end end def test_accept_nil_and_false test = {safe:0, unsafe: 10000000000000000000} exp = '{"safe":0,"unsafe":10000000000000000000}' assert_equal(exp, Oj.dump(test, integer_range: nil)) assert_equal(exp, Oj.dump(test, integer_range: false)) end end oj-3.13.9/test/perf_parser.rb0000755000004100000410000001170714136373754016111 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' require 'json' $verbose = false $iter = 50_000 $with_bignum = false $size = 1 $cache_keys = true $symbol_keys = false opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-k", "no cache") { $cache_keys = false } opts.on("-sym", "symbol keys") { $symbol_keys = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false, 1, nil], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj['g'] = 12345678901234567890123456789 if $with_bignum if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later Oj.default_options = {create_id: '^', create_additions: true, class_cache: true} if $cache_keys Oj.default_options = {cache_keys: true, cache_str: 6, symbol_keys: $symbol_keys} else Oj.default_options = {cache_keys: false, cache_str: -1, symbol_keys: $symbol_keys} end JSON.parser = JSON::Ext::Parser class AllSaj def initialize() end def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end end # AllSaj class NoSaj def initialize() end end # NoSaj no_handler = NoSaj.new() all_handler = AllSaj.new() if $verbose puts "json:\n#{$json}\n" end ### Validate ###################### p_val = Oj::Parser.new(:validate) puts '-' * 80 puts "Validate Performance" perf = Perf.new() perf.add('Oj::Parser.validate', 'none') { p_val.parse($json) } perf.add('Oj::Saj.none', 'none') { Oj.saj_parse(no_handler, $json) } perf.run($iter) ### SAJ ###################### p_all = Oj::Parser.new(:saj) p_all.handler = all_handler p_all.cache_keys = $cache_keys p_all.cache_strings = 6 puts '-' * 80 puts "Parse Callback Performance" perf = Perf.new() perf.add('Oj::Parser.saj', 'all') { p_all.parse($json) } perf.add('Oj::Saj.all', 'all') { Oj.saj_parse(all_handler, $json) } perf.run($iter) ### Usual ###################### p_usual = Oj::Parser.new(:usual) p_usual.cache_keys = $cache_keys p_usual.cache_strings = ($cache_keys ? 6 : 0) p_usual.symbol_keys = $symbol_keys puts '-' * 80 puts "Parse Usual Performance" perf = Perf.new() perf.add('Oj::Parser.usual', '') { p_usual.parse($json) } perf.add('Oj::strict_load', '') { Oj.strict_load($json) } perf.add('JSON::Ext', 'parse') { JSON.load($json) } perf.run($iter) ### Usual Objects ###################### # Original Oj follows the JSON gem for creating objects which uses the class # json_create(arg) method. Oj::Parser in usual mode supprts the same but also # handles populating the object variables directly which is faster. class Stuff attr_accessor :alpha, :bravo, :charlie, :delta, :echo, :foxtrot, :golf, :hotel, :india, :juliet def self.json_create(arg) obj = self.new obj.alpha = arg["alpha"] obj.bravo = arg["bravo"] obj.charlie = arg["charlie"] obj.delta = arg["delta"] obj.echo = arg["echo"] obj.foxtrot = arg["foxtrot"] obj.golf = arg["golf"] obj.hotel = arg["hotel"] obj.india = arg["india"] obj.juliet = arg["juliet"] obj end end $obj_json = %|{ "alpha": [0, 1,2,3,4,5,6,7,8,9], "bravo": true, "charlie": 123, "delta": "some string", "echo": null, "^": "Stuff", "foxtrot": false, "golf": "gulp", "hotel": {"x": true, "y": false}, "india": [null, true, 123], "juliet": "junk" }| p_usual = Oj::Parser.new(:usual) p_usual.cache_keys = $cache_keys p_usual.cache_strings = ($cache_keys ? 6 : 0) p_usual.symbol_keys = $symbol_keys p_usual.create_id = '^' p_usual.class_cache = true p_usual.ignore_json_create = true JSON.create_id = '^' puts '-' * 80 puts "Parse Usual Object Performance" perf = Perf.new() perf.add('Oj::Parser.usual', '') { p_usual.parse($obj_json) } perf.add('Oj::compat_load', '') { Oj.compat_load($obj_json) } perf.add('JSON::Ext', 'parse') { JSON.load($obj_json) } perf.run($iter) unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/test_hash.rb0000755000004100000410000000135314136373754015557 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' class HashTest < Minitest::Test module TestModule end def test_dump h = Oj::EasyHash.new() h['abc'] = 3 out = Oj.dump(h, :mode => :compat) assert_equal(%|{"abc":3}|, out) end def test_load obj = Oj.load(%|{"abc":3}|, :mode => :compat, :hash_class => Oj::EasyHash) assert_equal(Oj::EasyHash, obj.class) assert_equal(3, obj['abc']) assert_equal(3, obj[:abc]) assert_equal(3, obj.abc()) end def test_marshal h = Oj::EasyHash.new() h['abc'] = 3 out = Marshal.dump(h) obj = Marshal.load(out) assert_equal(Oj::EasyHash, obj.class) assert_equal(3, obj[:abc]) end end # HashTest oj-3.13.9/test/activerecord/0000755000004100000410000000000014136373754015715 5ustar www-datawww-dataoj-3.13.9/test/activerecord/result_test.rb0000755000004100000410000000142414136373754020623 0ustar www-datawww-data#!/usr/bin/env ruby $: << File.dirname(__FILE__) $: << File.dirname(File.dirname(__FILE__)) require 'helper' require "rails/all" Oj::Rails.set_encoder() Oj::Rails.optimize() Oj.default_options = { mode: :rails } class ActiveRecordResultTest < Minitest::Test def test_hash_rows result = ActiveRecord::Result.new(["one", "two"], [ ["row 1 col 1", "row 1 col 2"], ["row 2 col 1", "row 2 col 2"], ["row 3 col 1", "row 3 col 2"], ]) #puts "*** result: #{Oj.dump(result, indent: 2)}" json_result = if ActiveRecord.version >= Gem::Version.new("6") result.to_a else result.to_hash end assert_equal Oj.dump(result, mode: :rails), Oj.dump(json_result) end end oj-3.13.9/test/perf_saj.rb0000755000004100000410000000624414136373754015372 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 10000 $gets = 0 $fetch = false $write = false $read = false opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i } opts.on("-f", "fetch") { $fetch = true } opts.on("-w", "write") { $write = true } opts.on("-r", "read") { $read = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) class AllSaj < Oj::Saj def initialize() end def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end end # AllSaj class NoSaj < Oj::Saj def initialize() end end # NoSaj saj_handler = AllSaj.new() no_saj = NoSaj.new() $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array 'e' => { 'one' => 1, 'two' => 2 }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, # big number 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Saj', 'all') { Oj.saj_parse(saj_handler, $json) } perf.add('Oj::Saj', 'none') { Oj.saj_parse(no_saj, $json) } perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/test_parser_usual.rb0000755000004100000410000001232114136373754017336 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) require 'helper' class UsualTest < Minitest::Test def test_nil p = Oj::Parser.new(:usual) doc = p.parse('nil') assert_nil(doc) end def test_primitive p = Oj::Parser.new(:usual) [ ['true', true], ['false', false], ['123', 123], ['1.25', 1.25], ['"abc"', 'abc'], ].each { |x| doc = p.parse(x[0]) assert_equal(x[1], doc) } end def test_big p = Oj::Parser.new(:usual) doc = p.parse('12345678901234567890123456789') assert_equal(BigDecimal, doc.class) doc = p.parse('1234567890.1234567890123456789') assert_equal(BigDecimal, doc.class) end def test_array p = Oj::Parser.new(:usual) [ ['[]', []], ['[false]', [false]], ['[true,false]', [true,false]], ['[[]]', [[]]], ['[true,[],false]', [true,[],false]], ['[true,[true],false]', [true,[true],false]], ].each { |x| doc = p.parse(x[0]) assert_equal(x[1], doc) } end def test_hash p = Oj::Parser.new(:usual) [ ['{}', {}], ['{"a": null}', {'a' => nil}], ['{"t": true, "f": false, "s": "abc"}', {'t' => true, 'f' => false, 's' => 'abc'}], ['{"a": {}}', {'a' => {}}], ['{"a": {"b": 2}}', {'a' => {'b' => 2}}], ['{"a": [true]}', {'a' => [true]}], ].each { |x| doc = p.parse(x[0]) assert_equal(x[1], doc) } end def test_symbol_keys p = Oj::Parser.new(:usual) assert_equal(false, p.symbol_keys) p.symbol_keys = true doc = p.parse('{"a": true, "b": false}') assert_equal({a: true, b: false}, doc) end def test_strings p = Oj::Parser.new(:usual) doc = p.parse('{"ぴ": "", "ぴ ": "x", "c": "ぴーたー", "d": " ぴーたー "}') assert_equal({'ぴ' => '', 'ぴ ' => 'x', 'c' => 'ぴーたー', 'd' => ' ぴーたー '}, doc) end def test_capacity p = Oj::Parser.new(:usual, capacity: 1000) assert_equal(4096, p.capacity) p.capacity = 5000 assert_equal(5000, p.capacity) end def test_decimal p = Oj::Parser.new(:usual) assert_equal(:auto, p.decimal) doc = p.parse('1.234567890123456789') assert_equal(BigDecimal, doc.class) assert_equal('0.1234567890123456789e1', doc.to_s) doc = p.parse('1.25') assert_equal(Float, doc.class) p.decimal = :float assert_equal(:float, p.decimal) doc = p.parse('1.234567890123456789') assert_equal(Float, doc.class) p.decimal = :bigdecimal assert_equal(:bigdecimal, p.decimal) doc = p.parse('1.234567890123456789') assert_equal(BigDecimal, doc.class) doc = p.parse('1.25') assert_equal(BigDecimal, doc.class) assert_equal('0.125e1', doc.to_s) p.decimal = :ruby assert_equal(:ruby, p.decimal) doc = p.parse('1.234567890123456789') assert_equal(Float, doc.class) end def test_omit_null p = Oj::Parser.new(:usual) p.omit_null = true doc = p.parse('{"a":true,"b":null}') assert_equal({'a'=>true}, doc) p.omit_null = false doc = p.parse('{"a":true,"b":null}') assert_equal({'a'=>true, 'b'=>nil}, doc) end class MyArray < Array end def test_array_class p = Oj::Parser.new(:usual) p.array_class = MyArray assert_equal(MyArray, p.array_class) doc = p.parse('[true]') assert_equal(MyArray, doc.class) end class MyHash < Hash end def test_hash_class p = Oj::Parser.new(:usual) p.hash_class = MyHash assert_equal(MyHash, p.hash_class) doc = p.parse('{"a":true}') assert_equal(MyHash, doc.class) end class MyClass attr_accessor :a attr_accessor :b def to_s "#{self.class}{a: #{@a} b: #{b}}" end end class MyClass2 < MyClass def self.json_create(arg) obj = new obj.a = arg['a'] obj.b = arg['b'] obj end end def test_create_id p = Oj::Parser.new(:usual) p.create_id = '^' doc = p.parse('{"a":true}') assert_equal(Hash, doc.class) doc = p.parse('{"a":true,"^":"UsualTest::MyClass","b":false}') assert_equal('UsualTest::MyClass{a: true b: false}', doc.to_s) doc = p.parse('{"a":true,"^":"UsualTest::MyClass2","b":false}') assert_equal('UsualTest::MyClass2{a: true b: false}', doc.to_s) p.hash_class = MyHash assert_equal(MyHash, p.hash_class) doc = p.parse('{"a":true}') assert_equal(MyHash, doc.class) doc = p.parse('{"a":true,"^":"UsualTest::MyClass","b":false}') assert_equal('UsualTest::MyClass{a: true b: false}', doc.to_s) end def test_missing_class p = Oj::Parser.new(:usual, create_id: '^') json = '{"a":true,"^":"Auto","b":false}' doc = p.parse(json) assert_equal(Hash, doc.class) p.missing_class = :auto doc = p.parse(json) # Auto should be defined after parsing assert_equal(Auto, doc.class) end def test_class_cache p = Oj::Parser.new(:usual) p.create_id = '^' p.class_cache = true p.missing_class = :auto json = '{"a":true,"^":"Auto2","b":false}' doc = p.parse(json) assert_equal(Auto2, doc.class) doc = p.parse(json) assert_equal(Auto2, doc.class) end def test_default_parser doc = Oj::Parser.usual.parse('{"a":true,"b":null}') assert_equal({'a'=>true, 'b'=>nil}, doc) end end oj-3.13.9/test/prec.rb0000644000004100000410000000101214136373754014513 0ustar www-datawww-data# frozen_string_literal: true require 'oj' extras = {"locationLng" => -97.14690769100295} Oj.default_options = {float_precision: 17} encoded = Oj.dump(extras) puts encoded puts Oj.load(encoded) require "active_record" Oj::Rails.set_encoder() Oj::Rails.set_decoder() Oj.default_options = {float_precision: 17} # Using Oj rails encoder, gets the correct value: {"locationLng":-97.14690769100295} encoded = ActiveSupport::JSON.encode(extras) puts encoded puts ActiveSupport::JSON.decode(encoded) puts Oj.load(encoded) oj-3.13.9/test/foo.rb0000755000004100000410000000116314136373754014357 0ustar www-datawww-data#!/usr/bin/env ruby $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") # require 'json' require 'oj' Oj.mimic_JSON source = %( {"a": 1, "b": 2} ) puts "JSON.load, no symbolize => OK" pp JSON.load( source ) puts "JSON.load, do symbolize => KO: keys are not symbols" #pp JSON.load( source, nil, symbolize_names: true, create_additions: false ) pp JSON.load( source, nil, symbolize_names: true, create_additions: false ) puts "JSON.parse, no symbolize => OK" pp JSON.parse( source ) puts "JSON.parse, do symbolize => OK" pp JSON.parse( source, symbolize_names: true ) oj-3.13.9/test/perf_fast.rb0000755000004100000410000001421314136373754015545 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' #require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 100000 $gets = 0 $fetch = false $write = false $read = false opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i } opts.on("-f", "fetch") { $fetch = true } opts.on("-w", "write") { $write = true } opts.on("-r", "read") { $read = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) # This just navigates to each leaf of the JSON structure. def dig(obj, &blk) case obj when Array obj.each { |e| dig(e, &blk) } when Hash obj.values.each { |e| dig(e, &blk) } else blk.yield(obj) end end $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array 'e' => { 'one' => 1, 'two' => 2 }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, # big number 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj::Doc', $obj, 'load', 'dump') { |o| Oj::Doc.open(Oj.dump(o, :mode => :strict)) { |f| f.fetch() } } #capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| } } unless $failed.has_key?('Oj::Doc') #perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) puts '-' * 80 puts "JSON generation Performance" Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'dump') { doc.dump() } # perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) } perf.add('JSON::Ext', 'fast_generate') { JSON.fast_generate($obj) } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } perf.run($iter) end if 0 < $gets puts '-' * 80 puts "Parse and get all values Performance" perf = Perf.new() perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| $gets.times { f.each_value() {} } } } unless $failed.has_key?('Oj::Doc') # perf.add('Yajl', 'parse') { $gets.times { dig(Yajl::Parser.parse($json)) {} } } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { $gets.times { dig(JSON::Ext::Parser.new($json).parse) {} } } unless $failed.has_key?('JSON::Ext') perf.run($iter) end if $fetch puts '-' * 80 puts "fetch nested Performance" json_hash = Oj.load($json, :mode => :strict) Oj::Doc.open($json) do |fast| #puts "*** C fetch: #{fast.fetch('/d/2/4/y')}" #puts "*** Ruby fetch: #{json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil)}" perf = Perf.new() perf.add('Oj::Doc', 'fetch') { fast.fetch('/d/2/4/x'); fast.fetch('/h/a/b/c/d/e/f/g'); fast.fetch('/i/1/1/1/1/1/1/1') } # version that fails gracefully perf.add('Ruby', 'fetch') do json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil) json_hash.fetch('h', {}).fetch('a', {}).fetch('b', {}).fetch('c', {}).fetch('d', {}).fetch('e', {}).fetch('f', {}).fetch('g', {}) json_hash.fetch('i', []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, nil) end # version that raises if the path is incorrect # perf.add('Ruby', 'fetch') { $fetch.times { json_hash['d'][1][3][1] } } perf.run($iter) end end if $write puts '-' * 80 puts "JSON write to file Performance" Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'dump') { doc.dump(nil, 'oj.json') } # perf.add('Yajl', 'encode') { File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) } } perf.add('JSON::Ext', 'fast_generate') { File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) } } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } perf.run($iter) end end if $read puts '-' * 80 puts "JSON read from file Performance" Oj::Doc.open($json) { |doc| doc.dump(nil, 'oj.json') } # File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) } JSON.generator = JSON::Ext::Generator File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) } Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'open_file') { ::Oj::Doc.open_file('oj.json') } # perf.add('Yajl', 'decode') { Yajl::decoder.decode(File.read('yajl.json')) } perf.add('JSON::Ext', '') { JSON.parse(File.read('json_ext.json')) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } perf.run($iter) end end unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/test_parser.rb0000755000004100000410000000101714136373754016125 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class ParserJuice < Minitest::Test def test_array p = Oj::Parser.new(:debug) out = p.parse(%|[true, false, null, 123, -1.23, "abc"]|) puts out out = p.parse(%|{"abc": []}|) puts out end end oj-3.13.9/test/test_parser_saj.rb0000755000004100000410000001300614136373754016763 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' $json = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class AllSaj < Oj::Saj attr_accessor :calls def initialize() @calls = [] end def hash_start(key) @calls << [:hash_start, key] end def hash_end(key) @calls << [:hash_end, key] end def array_start(key) @calls << [:array_start, key] end def array_end(key) @calls << [:array_end, key] end def add_value(value, key) @calls << [:add_value, value, key] end def error(message, line, column) @calls << [:error, message, line, column] end end # AllSaj class SajTest < Minitest::Test def test_nil handler = AllSaj.new() json = %{null} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, nil, nil]], handler.calls) end def test_true handler = AllSaj.new() json = %{true} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, true, nil]], handler.calls) end def test_false handler = AllSaj.new() json = %{false} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, false, nil]], handler.calls) end def test_string handler = AllSaj.new() json = %{"a string"} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, 'a string', nil]], handler.calls) end def test_fixnum handler = AllSaj.new() json = %{12345} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, 12345, nil]], handler.calls) end def test_float handler = AllSaj.new() json = %{12345.6789} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:add_value, 12345.6789, nil]], handler.calls) end def test_float_exp handler = AllSaj.new() json = %{12345.6789e7} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal(1, handler.calls.size) assert_equal(:add_value, handler.calls[0][0]) assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i) end def test_array_empty handler = AllSaj.new() json = %{[]} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:array_start, nil], [:array_end, nil]], handler.calls) end def test_array handler = AllSaj.new() json = %{[true,false]} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:array_start, nil], [:add_value, true, nil], [:add_value, false, nil], [:array_end, nil]], handler.calls) end def test_hash_empty handler = AllSaj.new() json = %{{}} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:hash_start, nil], [:hash_end, nil]], handler.calls) end def test_hash handler = AllSaj.new() json = %{{"one":true,"two":false}} p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([[:hash_start, nil], [:add_value, true, 'one'], [:add_value, false, 'two'], [:hash_end, nil]], handler.calls) end def test_full handler = AllSaj.new() Oj.saj_parse(handler, $json) assert_equal([[:hash_start, nil], [:array_start, 'array'], [:hash_start, nil], [:add_value, 3, 'num'], [:add_value, 'message', 'string'], [:hash_start, 'hash'], [:hash_start, 'h2'], [:array_start, 'a'], [:add_value, 1, nil], [:add_value, 2, nil], [:add_value, 3, nil], [:array_end, 'a'], [:hash_end, 'h2'], [:hash_end, 'hash'], [:hash_end, nil], [:array_end, 'array'], [:add_value, true, 'boolean'], [:hash_end, nil]], handler.calls) end def test_multiple handler = AllSaj.new() json = %|[true][false]| p = Oj::Parser.new(:saj) p.handler = handler p.parse(json) assert_equal([ [:array_start, nil], [:add_value, true, nil], [:array_end, nil], [:array_start, nil], [:add_value, false, nil], [:array_end, nil], ], handler.calls) end def test_io handler = AllSaj.new() json = %| [true,false] | p = Oj::Parser.new(:saj) p.handler = handler p.load(StringIO.new(json)) assert_equal([ [:array_start, nil], [:add_value, true, nil], [:add_value, false, nil], [:array_end, nil], ], handler.calls) end def test_file handler = AllSaj.new() p = Oj::Parser.new(:saj) p.handler = handler p.file('saj_test.json') assert_equal([ [:array_start, nil], [:add_value, true, nil], [:add_value, false, nil], [:array_end, nil], ], handler.calls) end def test_default handler = AllSaj.new() json = %|[true]| Oj::Parser.saj.handler = handler Oj::Parser.saj.parse(json) assert_equal([ [:array_start, nil], [:add_value, true, nil], [:array_end, nil], ], handler.calls) end end oj-3.13.9/test/test_scp.rb0000755000004100000410000002503414136373754015423 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) require 'helper' require 'socket' require 'stringio' $json = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class NoHandler < Oj::ScHandler def initialize() end end class AllHandler < Oj::ScHandler attr_accessor :calls def initialize() @calls = [] end def hash_start() @calls << [:hash_start] {} end def hash_end() @calls << [:hash_end] end def hash_key(key) @calls << [:hash_key, key] return 'too' if 'two' == key return :symbol if 'symbol' == key key end def array_start() @calls << [:array_start] [] end def array_end() @calls << [:array_end] end def add_value(value) @calls << [:add_value, value] end def hash_set(h, key, value) @calls << [:hash_set, key, value] end def array_append(a, value) @calls << [:array_append, value] end end # AllHandler class Closer < AllHandler attr_accessor :io def initialize(io) super() @io = io end def hash_start() @calls << [:hash_start] @io.close {} end def hash_set(h, key, value) @calls << [:hash_set, key, value] @io.close end end # Closer class ScpTest < Minitest::Test def setup @default_options = Oj.default_options end def teardown Oj.default_options = @default_options end def test_nil handler = AllHandler.new() json = %{null} Oj.sc_parse(handler, json) assert_equal([[:add_value, nil]], handler.calls) end def test_true handler = AllHandler.new() json = %{true} Oj.sc_parse(handler, json) assert_equal([[:add_value, true]], handler.calls) end def test_false handler = AllHandler.new() json = %{false} Oj.sc_parse(handler, json) assert_equal([[:add_value, false]], handler.calls) end def test_string handler = AllHandler.new() json = %{"a string"} Oj.sc_parse(handler, json) assert_equal([[:add_value, 'a string']], handler.calls) end def test_fixnum handler = AllHandler.new() json = %{12345} Oj.sc_parse(handler, json) assert_equal([[:add_value, 12345]], handler.calls) end def test_float handler = AllHandler.new() json = %{12345.6789} Oj.sc_parse(handler, json) assert_equal([[:add_value, 12345.6789]], handler.calls) end def test_float_exp handler = AllHandler.new() json = %{12345.6789e7} Oj.sc_parse(handler, json) assert_equal(1, handler.calls.size) assert_equal(:add_value, handler.calls[0][0]) assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i) end def test_array_empty handler = AllHandler.new() json = %{[]} Oj.sc_parse(handler, json) assert_equal([[:array_start], [:array_end], [:add_value, []]], handler.calls) end def test_array handler = AllHandler.new() json = %{[true,false]} Oj.sc_parse(handler, json) assert_equal([[:array_start], [:array_append, true], [:array_append, false], [:array_end], [:add_value, []]], handler.calls) end def test_hash_empty handler = AllHandler.new() json = %{{}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_end], [:add_value, {}]], handler.calls) end def test_hash handler = AllHandler.new() json = %{{"one":true,"two":false}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'two'], [:hash_set, 'too', false], [:hash_end], [:add_value, {}]], handler.calls) end def test_hash_sym handler = AllHandler.new() json = %{{"one":true,"two":false}} Oj.sc_parse(handler, json, :symbol_keys => true) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'two'], [:hash_set, 'too', false], [:hash_end], [:add_value, {}]], handler.calls) end def test_symbol_hash_key_without_symbol_keys handler = AllHandler.new() json = %{{"one":true,"symbol":false}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'symbol'], [:hash_set, :symbol, false], [:hash_end], [:add_value, {}]], handler.calls) end def test_full handler = AllHandler.new() Oj.sc_parse(handler, $json) assert_equal([[:hash_start], [:hash_key, 'array'], [:array_start], [:hash_start], [:hash_key, 'num'], [:hash_set, "num", 3], [:hash_key, 'string'], [:hash_set, "string", "message"], [:hash_key, 'hash'], [:hash_start], [:hash_key, 'h2'], [:hash_start], [:hash_key, 'a'], [:array_start], [:array_append, 1], [:array_append, 2], [:array_append, 3], [:array_end], [:hash_set, "a", []], [:hash_end], [:hash_set, "h2", {}], [:hash_end], [:hash_set, "hash", {}], [:hash_end], [:array_append, {}], [:array_end], [:hash_set, "array", []], [:hash_key, 'boolean'], [:hash_set, "boolean", true], [:hash_end], [:add_value, {}]], handler.calls) end def test_double handler = AllHandler.new() json = %{{"one":true,"two":false}{"three":true,"four":false}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'two'], [:hash_set, 'too', false], [:hash_end], [:add_value, {}], [:hash_start], [:hash_key, 'three'], [:hash_set, 'three', true], [:hash_key, 'four'], [:hash_set, 'four', false], [:hash_end], [:add_value, {}]], handler.calls) end def test_double_io handler = AllHandler.new() json = %{{"one":true,"two":false}{"three":true,"four":false}} Oj.sc_parse(handler, StringIO.new(json)) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'two'], [:hash_set, 'too', false], [:hash_end], [:add_value, {}], [:hash_start], [:hash_key, 'three'], [:hash_set, 'three', true], [:hash_key, 'four'], [:hash_set, 'four', false], [:hash_end], [:add_value, {}]], handler.calls) end def test_none handler = NoHandler.new() Oj.sc_parse(handler, $json) end def test_fixnum_bad handler = AllHandler.new() json = %{12345xyz} assert_raises Oj::ParseError do Oj.sc_parse(handler, json) end end def test_null_string handler = AllHandler.new() json = %{"\0"} assert_raises Oj::ParseError do Oj.sc_parse(handler, json) end end def test_pipe # Windows does not support fork return if RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ handler = AllHandler.new() json = %{{"one":true,"two":false}} IO.pipe do |read_io, write_io| if fork write_io.close Oj.sc_parse(handler, read_io) read_io.close assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true], [:hash_key, 'two'], [:hash_set, 'too', false], [:hash_end], [:add_value, {}]], handler.calls) else read_io.close write_io.write json write_io.close Process.exit(0) end end end def test_bad_bignum if '2.4.0' < RUBY_VERSION handler = AllHandler.new() json = %|{"big":-e123456789}| assert_raises Exception do # Can be either Oj::ParseError or ArgumentError depending on Ruby version Oj.sc_parse(handler, json) end end end def test_pipe_close # Windows does not support fork return if RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ json = %{{"one":true,"two":false}} IO.pipe do |read_io, write_io| if fork write_io.close handler = Closer.new(read_io) err = nil begin Oj.sc_parse(handler, read_io) read_io.close rescue Exception => e err = e.class.to_s end assert_equal("IOError", err) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true]], handler.calls) else read_io.close write_io.write json[0..11] sleep(0.1) begin write_io.write json[12..-1] rescue Exception => e # ignore, should fail to write end write_io.close Process.exit(0) end end end def test_socket_close json = %{{"one":true,"two":false}} begin server = TCPServer.new(8080) rescue # Not able to open a socket to run the test. Might be Travis. return end Thread.start(json) do |j| c = server.accept() c.puts json[0..11] 10.times { break if c.closed? sleep(0.1) } unless c.closed? c.puts json[12..-1] c.close end end begin sock = TCPSocket.new('localhost', 8080) rescue # Not able to open a socket to run the test. Might be Travis. return end handler = Closer.new(sock) err = nil begin Oj.sc_parse(handler, sock) rescue Exception => e err = e.class.to_s end assert_equal("IOError", err) assert_equal([[:hash_start], [:hash_key, 'one'], [:hash_set, 'one', true]], handler.calls) end end oj-3.13.9/test/bug.rb0000644000004100000410000000047114136373754014347 0ustar www-datawww-data$: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") #require 'bundler/setup' require 'oj' require 'active_support' require 'active_support/time_with_zone' require 'tzinfo' puts ActiveSupport::TimeWithZone json = File.read('./bug.json') Oj.load(json) oj-3.13.9/test/tests_mimic_addition.rb0000755000004100000410000000022614136373754017766 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.dirname(__FILE__) $: << File.join(File.dirname(__FILE__), 'json_gem') require 'json_addition_test' oj-3.13.9/test/test_compat.rb0000755000004100000410000003156614136373754016130 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: utf-8 $: << File.dirname(__FILE__) $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__))) %w(lib ext).each do |dir| $: << File.join($oj_dir, dir) end require 'minitest' require 'minitest/autorun' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class CompatJuice < Minitest::Test class Jeez attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def to_json(*a) %|{"json_class":"#{self.class.to_s}","x":#{@x},"y":#{@y}}| end def self.json_create(h) self.new(h['x'], h['y']) end end # Jeez class Argy def initialize() end def to_json(*a) %|{"args":"#{a}"}| end end # Argy class Stringy def initialize() end def to_s() %|[1,2]| end end # Stringy module One module Two module Three class Deep def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_json(*a) %|{"json_class":"#{self.class.name}"}| end def self.json_create(h) self.new() end end # Deep end # Three end # Two end # One def setup @default_options = Oj.default_options # in compat mode other options other than the JSON gem globals and options # are not used. Oj.default_options = { :mode => :compat } end def teardown Oj.default_options = @default_options end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(0.56, false) dump_and_load(3.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(1.7775, false) dump_and_load(2.5024, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) dump_and_load(1405460727.723866, false) dump_and_load(0.5773, false) dump_and_load(0.6768, false) dump_and_load(0.685, false) dump_and_load(0.7032, false) dump_and_load(0.7051, false) dump_and_load(0.8274, false) dump_and_load(0.9149, false) dump_and_load(64.4, false) dump_and_load(71.6, false) dump_and_load(73.4, false) dump_and_load(80.6, false) dump_and_load(-95.640172, false) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end def test_symbol json = Oj.dump(:abc, :mode => :compat) assert_equal('"abc"', json) end def test_time_xml_schema t = Time.xmlschema("2012-01-05T23:58:07.123456000+09:00") #t = Time.local(2012, 1, 5, 23, 58, 7, 123456) json = Oj.dump(t, :mode => :compat) assert_equal(%{"2012-01-05 23:58:07 +0900"}, json) end def test_class json = Oj.dump(CompatJuice, :mode => :compat) assert_equal(%{"CompatJuice"}, json) end def test_module json = Oj.dump(One::Two, :mode => :compat) assert_equal(%{"CompatJuice::One::Two"}, json) end # Hash def test_non_str_hash json = Oj.dump({ 1 => true, 0 => false }, :mode => :compat) h = Oj.load(json, :mode => :strict) assert_equal({ "1" => true, "0" => false }, h) end def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.compat_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_invalid_escapes_handled json = '{"subtext":"\"404er\” \w \k \3 \a"}' obj = Oj.compat_load(json) assert_equal({"subtext" => "\"404er” w k 3 a"}, obj) end def test_hash_escaping json = Oj.to_json({'<>' => '<>'}, mode: :compat) assert_equal(json, '{"<>":"<>"}') end def test_bignum_object dump_and_load(7 ** 55, false) end def test_json_object obj = Jeez.new(true, 58) json = Oj.to_json(obj) assert(%|{"json_class":"CompatJuice::Jeez","x":true,"y":58}| == json || %|{"json_class":"CompatJuice::Jeez","y":58,"x":true}| == json) dump_to_json_and_load(obj, false) end def test_json_object_create_id Oj.default_options = { :create_id => 'kson_class', :create_additions => true} expected = Jeez.new(true, 58) json = %{{"kson_class":"CompatJuice::Jeez","x":true,"y":58}} obj = Oj.load(json) assert_equal(expected, obj) Oj.default_options = { :create_id => 'json_class' } end def test_bignum_compat json = Oj.dump(7 ** 55, :mode => :compat) b = Oj.load(json, :mode => :strict) assert_equal(30226801971775055948247051683954096612865741943, b) end # BigDecimal def test_bigdecimal # BigDecimals are dumped as strings and can not be restored to the # original value without using an undocumented feature of the JSON gem. json = Oj.dump(BigDecimal('3.14159265358979323846')) # 2.4.0 changes the exponent to lowercase assert_equal('"0.314159265358979323846e1"', json.downcase) end def test_decimal_class big = BigDecimal('3.14159265358979323846') # :decimal_class is the undocumented feature. json = Oj.load('3.14159265358979323846', mode: :compat, decimal_class: BigDecimal) assert_equal(big, json) end def test_infinity assert_raises(Oj::ParseError) { Oj.load('Infinity', :mode => :strict) } x = Oj.load('Infinity', :mode => :compat) assert_equal('Infinity', x.to_s) end # Time def test_time_from_time_object t = Time.new(2015, 1, 5, 21, 37, 7.123456, -8 * 3600) expect = '"' + t.to_s + '"' json = Oj.dump(t) assert_equal(expect, json) end def test_date_compat orig = Date.new(2012, 6, 19) json = Oj.dump(orig, :mode => :compat) x = Oj.load(json, :mode => :compat) # Some Rubies implement Date as data and some as a real Object. Either are # okay for the test. if x.is_a?(String) assert_equal(orig.to_s, x) else # better be a Hash assert_equal({"year" => orig.year, "month" => orig.month, "day" => orig.day, "start" => orig.start}, x) end end def test_datetime_compat orig = DateTime.new(2012, 6, 19, 20, 19, 27) json = Oj.dump(orig, :mode => :compat) x = Oj.load(json, :mode => :compat) # Some Rubies implement Date as data and some as a real Object. Either are # okay for the test. assert_equal(orig.to_s, x) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.compat_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = File.join(File.dirname(__FILE__), 'open_file_test.json') File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.compat_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.compat_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # If mimic_JSON has not been called then Oj.dump will call to_json on the # top level object only. def test_json_object_top obj = Jeez.new(true, 58) dump_to_json_and_load(obj, false) end # A child to_json should not be called. def test_json_object_child obj = { "child" => Jeez.new(true, 58) } assert_equal('{"child":{"json_class":"CompatJuice::Jeez","x":true,"y":58}}', Oj.dump(obj)) end def test_json_module_object obj = One::Two::Three::Deep.new() dump_to_json_and_load(obj, false) end def test_json_object_dump_create_id expected = Jeez.new(true, 58) json = Oj.to_json(expected) obj = Oj.compat_load(json, :create_additions => true) assert_equal(expected, obj) end def test_json_object_bad json = %{{"json_class":"CompatJuice::Junk","x":true}} begin Oj.compat_load(json, :create_additions => true) rescue Exception => e assert_equal("ArgumentError", e.class().name) return end assert(false, "*** expected an exception") end def test_json_object_create_cache expected = Jeez.new(true, 58) json = Oj.to_json(expected) obj = Oj.compat_load(json, :class_cache => true, :create_additions => true) assert_equal(expected, obj) obj = Oj.compat_load(json, :class_cache => false, :create_additions => true) assert_equal(expected, obj) end def test_json_object_create_id_other expected = Jeez.new(true, 58) json = Oj.to_json(expected) json.gsub!('json_class', '_class_') obj = Oj.compat_load(json, :create_id => "_class_", :create_additions => true) assert_equal(expected, obj) end def test_json_object_create_deep expected = One::Two::Three::Deep.new() json = Oj.to_json(expected) obj = Oj.compat_load(json, :create_additions => true) assert_equal(expected, obj) end def test_range json = Oj.dump(1..7) assert_equal('"1..7"', json) end def test_arg_passing json = Oj.to_json(Argy.new(), :max_nesting=> 40) assert_equal(%|{"args":"[{:max_nesting=>40}]"}|, json) end def test_bad_unicode assert_raises() { Oj.to_json("\xE4xy") } end def test_bad_unicode_e2 assert_raises() { Oj.to_json("L\xE2m ") } end def test_bad_unicode_start assert_raises() { Oj.to_json("\x8abc") } end def test_parse_to_s s = Stringy.new assert_equal([1,2], Oj.load(s, :mode => :compat)) end def dump_and_load(obj, trace=false) json = Oj.dump(obj) puts json if trace loaded = Oj.compat_load(json, :create_additions => true); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end def dump_to_json_and_load(obj, trace=false) json = Oj.to_json(obj, :indent => ' ') puts json if trace loaded = Oj.compat_load(json, :create_additions => true); if obj.nil? assert_nil(loaded) else assert_equal(obj, loaded) end loaded end end oj-3.13.9/test/perf_scp.rb0000755000004100000410000000741414136373754015402 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 50_000 $with_bignum = false $size = 1 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj['g'] = 12345678901234567890123456789 if $with_bignum if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end Oj.default_options = { :indent => $indent, :mode => :strict, cache_keys: true, cache_str: 5 } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later class AllSaj < Oj::Saj def initialize() end def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end end # AllSaj class NoSaj < Oj::Saj def initialize() end end # NoSaj class NoHandler < Oj::ScHandler def initialize() end end # NoHandler class AllHandler < Oj::ScHandler def initialize() end def hash_start() return nil end def hash_end() end def array_start() return nil end def array_end() end def add_value(value) end def hash_set(h, key, value) end def array_append(a, value) end end # AllHandler saj_handler = AllSaj.new() no_saj = NoSaj.new() sc_handler = AllHandler.new() no_handler = NoHandler.new() def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Saj.all', 'all') { Oj.saj_parse(saj_handler, $json) } perf.add('Oj::Saj.none', 'none') { Oj.saj_parse(no_saj, $json) } perf.add('Oj::Scp.all', 'all') { Oj.sc_parse(sc_handler, $json) } perf.add('Oj::Scp.none', 'none') { Oj.sc_parse(no_handler, $json) } perf.add('Oj::load', 'none') { Oj.wab_load($json) } perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-3.13.9/test/bar.rb0000755000004100000410000000043414136373754014340 0ustar www-datawww-data#!/usr/bin/env ruby $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' json = %|[{"x12345678901234567890": true}]| p = Oj::Parser.new(:usual) p.cache_keys = false p.symbol_keys = true x = p.parse(json) pp x oj-3.13.9/test/sample/0000755000004100000410000000000014136373754014524 5ustar www-datawww-dataoj-3.13.9/test/sample/group.rb0000644000004100000410000000030614136373754016204 0ustar www-datawww-data module Sample class Group attr_reader :members def initialize() @members = [] end def <<(member) @members << member end end # Group end # Sample oj-3.13.9/test/sample/oval.rb0000644000004100000410000000021114136373754016004 0ustar www-datawww-datamodule Sample class Oval < Shape def initialize(left, top, wide, high, color=nil) super end end # Oval end # Sample oj-3.13.9/test/sample/shape.rb0000644000004100000410000000110514136373754016146 0ustar www-datawww-data module Sample class Shape include HasProps attr_accessor :bounds attr_accessor :color attr_accessor :border, :border_color def initialize(left, top, wide, high, color=nil) @bounds = [[left, top], [left + wide, top + high]] @color = color @border = 1 @border_color = :black end def left @bounds[0][0] end def top @bounds[0][1] end def width @bounds[1][0] - @bounds[0][0] end def height @bounds[1][1] - @bounds[0][1] end end # Shape end # Sample oj-3.13.9/test/sample/dir.rb0000644000004100000410000000032614136373754015630 0ustar www-datawww-data require 'etc' module Sample class Dir < File attr_accessor :files def initialize(filename) super @files = [] end def <<(f) @files << f end end # Dir end # Sample oj-3.13.9/test/sample/line.rb0000644000004100000410000000050014136373754015773 0ustar www-datawww-datamodule Sample class Line include HasProps attr_accessor :x, :y, :dx, :dy attr_accessor :color attr_accessor :thick def initialize(x, y, dx, dy, thick, color) @x = x @y = y @dx = dx @dy = dy @thick = thick @color = color end end # Line end # Sample oj-3.13.9/test/sample/layer.rb0000644000004100000410000000023414136373754016164 0ustar www-datawww-data module Sample class Layer < Group attr_accessor :name def initialize(name) super() @name = name end end # Layer end # Sample oj-3.13.9/test/sample/hasprops.rb0000644000004100000410000000045614136373754016715 0ustar www-datawww-data module Sample module HasProps def add_prop(key, value) @props = { } unless self.instance_variable_defined?(:@props) @props[key] = value end def props @props = { } unless self.instance_variable_defined?(:@props) @props end end # HasProps end # Sample oj-3.13.9/test/sample/file.rb0000644000004100000410000000320014136373754015763 0ustar www-datawww-data require 'etc' module Sample class File attr_accessor :name, :ctime, :mtime, :size, :owner, :group, :permissions def initialize(filename) @name = ::File.basename(filename) stat = ::File.stat(filename) @ctime = stat.ctime @mtime = stat.mtime @size = stat.size @owner = Etc.getpwuid(stat.uid).name @group = Etc.getgrgid(stat.gid).name if false @permissions = { 'user' => { 'read' => (0 != (stat.mode & 0x0100)), 'write' => (0 != (stat.mode & 0x0080)), 'execute' => (0 != (stat.mode & 0x0040))}, 'group' => { 'read' => (0 != (stat.mode & 0x0020)), 'write' => (0 != (stat.mode & 0x0010)), 'execute' => (0 != (stat.mode & 0x0008))}, 'other' => { 'read' => (0 != (stat.mode & 0x0004)), 'write' => (0 != (stat.mode & 0x0002)), 'execute' => (0 != (stat.mode & 0x0001))} } else @permissions = { 'user' => [(0 != (stat.mode & 0x0100)) ? 'r' : '-', (0 != (stat.mode & 0x0080)) ? 'w' : '-', (0 != (stat.mode & 0x0040)) ? 'x' : '-'].join(''), 'group' => [(0 != (stat.mode & 0x0020)) ? 'r' : '-', (0 != (stat.mode & 0x0010)) ? 'w' : '-', (0 != (stat.mode & 0x0008)) ? 'x' : '-'].join(''), 'other' => [(0 != (stat.mode & 0x0004)) ? 'r' : '-', (0 != (stat.mode & 0x0002)) ? 'w' : '-', (0 != (stat.mode & 0x0001)) ? 'x' : '-'].join('') } end end end # File end # Sample oj-3.13.9/test/sample/doc.rb0000644000004100000410000000137014136373754015617 0ustar www-datawww-data require 'sample/hasprops' require 'sample/group' require 'sample/layer' require 'sample/line' require 'sample/shape' require 'sample/oval' require 'sample/rect' require 'sample/text' require 'sample/change' module Sample class Doc include HasProps attr_accessor :title attr_accessor :create_time attr_accessor :user # Hash of layers in the document indexed by layer name. attr_reader :layers attr_reader :change_history def initialize(title) @title = title @user = ENV['USER'] @create_time = Time.now @layers = { } @change_history = [] end def add_change(comment, time=nil, user=nil) @change_history << Change.new(comment, time, user) end end # Doc end # Sample oj-3.13.9/test/sample/change.rb0000644000004100000410000000044114136373754016275 0ustar www-datawww-data module Sample class Change attr_accessor :time attr_accessor :user attr_accessor :comment def initialize(comment=nil, time=nil, user=nil) @user = user || ENV['USER'] @time = time || Time.now @comment = comment end end # Change end # Sample oj-3.13.9/test/sample/rect.rb0000644000004100000410000000021114136373754016000 0ustar www-datawww-data module Sample class Rect < Shape def initialize(left, top, wide, high, color=nil) super end end # Rect end # Sample oj-3.13.9/test/sample/text.rb0000644000004100000410000000064414136373754016041 0ustar www-datawww-data module Sample class Text < Shape attr_accessor :text attr_accessor :font attr_accessor :font_size attr_accessor :just attr_accessor :text_color def initialize(text, left, top, wide, high, color=nil) super(left, top, wide, high, color) @text = text @font = 'helvetica' @font_size = 14 @just = 'left' @text_color = 'black' end end # Text end # Sample oj-3.13.9/pages/0000755000004100000410000000000014136373754013363 5ustar www-datawww-dataoj-3.13.9/pages/Advanced.md0000644000004100000410000000241314136373754015412 0ustar www-datawww-data# Advanced Features Optimized JSON (Oj), as the name implies, was written to provide speed optimized JSON handling. It was designed as a faster alternative to Yajl and other common Ruby JSON parsers. So far it has achieved that, and is about 2 times faster than any other Ruby JSON parser, and 3 or more times faster at serializing JSON. Oj has several `dump` or serialization modes which control how Ruby `Object`s are converted to JSON. These modes are set with the `:mode` option in either the default options or as one of the options to the `dump` method. In addition to the various options there are also alternative APIs for parsing JSON. The fastest alternative parser API is the `Oj::Doc` API. The `Oj::Doc` API takes a completely different approach by opening a JSON document and providing calls to navigate around the JSON while it is open. With this approach, JSON access can be well over 20 times faster than conventional JSON parsing. The `Oj::Saj` and `Oj::ScHandler` APIs are callback parsers that walk the JSON document depth first and makes callbacks for each element. Both callback parser are useful when only portions of the JSON are of interest. Performance up to 20 times faster than conventional JSON is possible if only a few elements of the JSON are of interest. oj-3.13.9/pages/Modes.md0000644000004100000410000002337314136373754014764 0ustar www-datawww-data# Oj Modes Oj uses modes to switch the load and dump behavior. Initially Oj supported on the :object mode which uses a format that allows Ruby object encoding and decoding in a manner that lets almost any Ruby object be encoded and decoded without monkey patching the object classes. From that start other demands were made the were best met by giving Oj multiple modes of operation. The current modes are: - `:strict` - `:null` - `:compat` or `:json` - `:rails` - `:object` - `:custom` Since modes determine what the JSON output will look like and alternatively what Oj expects when the `Oj.load()` method is called, mixing the output and input mode formats will most likely not behave as intended. If the object mode is used for producing JSON then use object mode for reading. The same is true for each mode. It is possible to mix but only for advanced users. ## :strict Mode Strict mode follows the JSON specifications and only supports the JSON native types, Boolean, nil, String, Hash, Array, and Numbers are encoded as expected. Encountering any other type causes an Exception to be raised. This is the safest mode as it is just simple translation, no code outside Oj or the core Ruby is execution on loading. Very few options are supported by this mode other than formatting options. ## :null Mode Null mode is similar to the :strict mode except that a JSON null is inserted if a non-native type is encountered instead of raising an Exception. ## :compat or :json Mode The `:compat` mode mimics the json gem. The json gem is built around the use of the `to_json(*)` method defined for a class. Oj attempts to provide the same functionality by being a drop in replacement with a few exceptions. [{file:JsonGem.md}](JsonGem.md) includes more details on compatibility and use. ## :rails Mode The `:rails` mode mimics the ActiveSupport version 5 encoder. Rails and ActiveSupport are built around the use of the `as_json(*)` method defined for a class. Oj attempts to provide the same functionality by being a drop in replacement with a few exceptions. [{file:Rails.md}](Rails.md) includes more details on compatibility and use. ## :object Mode Object mode is for fast Ruby object serialization and deserialization. That was the primary purpose of Oj when it was first developed. As such it is the default mode unless changed in the Oj default options. In :object mode Oj generates JSON that follows conventions which allow Class and other information such as Object IDs for circular reference detection to be encoded in a JSON document. The formatting follows the rules describe on the [{file:Encoding.md}](Encoding.md) page. ## :custom Mode Custom mode honors all options. It provides the most flexibility although it can not be configured to be exactly like any of the other modes. Each mode has some special aspect that makes it unique. For example, the `:object` mode has it's own unique format for object dumping and loading. The `:compat` mode mimic the json gem including methods called for encoding and inconsistencies between `JSON.dump()`, `JSON.generate()`, and `JSON()`. More details on the [{file:Custom.md}](Custom.md) page. ## :wab Mode WAB mode ignores all options except the indent option. Performance of this mode is on slightly better than the :strict and :null modes. It is included to support the [WABuR](https://github.com/ohler55/wabur) project. More details on the [{file:WAB.md}](WAB.md) page. ## Options Matrix Not all options are available in all modes. The options matrix identifies the options available in each mode. An `x` in the matrix indicates the option is supported in that mode. A number indicates the footnotes describe additional information. | Option | type | :null | :strict | :compat | :rails | :object | :custom | :wab | | ---------------------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | :allow_blank | Boolean | | | 1 | 1 | | x | | | :allow_gc | Boolean | x | x | x | x | x | x | | | :allow_invalid_unicode | Boolean | | | | | x | x | | | :allow_nan | Boolean | | | x | | x | x | | | :array_class | Class | | | x | x | | x | | | :array_nl | String | | | | | | x | | | :ascii_only | Boolean | x | x | 2 | 2 | x | x | | | :auto_define | Boolean | | | | | x | x | | | :bigdecimal_as_decimal | Boolean | | | | 3 | x | x | | | :bigdecimal_load | Boolean | | | | | | x | | | :compat_bigdecimal | Boolean | | | x | | | x | | | :cache_keys | Boolean | x | x | x | x | | x | | | :cache_strings | Fixnum | x | x | x | x | | x | | | :circular | Boolean | x | x | x | x | x | x | | | :class_cache | Boolean | | | | | x | x | | | :create_additions | Boolean | | | x | x | | x | | | :create_id | String | | | x | x | | x | | | :empty_string | Boolean | | | | | | x | | | :escape_mode | Symbol | | | | | | x | | | :float_precision | Fixnum | x | x | | | | x | | | :hash_class | Class | | | x | x | | x | | | :ignore | Array | | | | | x | x | | | :indent | Integer | x | x | 3 | 4 | x | x | x | | :indent_str | String | | | x | x | | x | | | :integer_range | Range | x | x | x | x | x | x | x | | :match_string | Hash | | | x | x | | x | | | :max_nesting | Fixnum | 4 | 4 | x | | 5 | 4 | | | :mode | Symbol | - | - | - | - | - | - | | | :nan | Symbol | | | | | | x | | | :nilnil | Boolean | | | | | | x | | | :object_class | Class | | | x | | | x | | | :object_nl | String | | | x | x | | x | | | :omit_nil | Boolean | x | x | x | x | x | x | | | :quirks_mode | Boolean | | | 6 | | | x | | | :safe | String | | | x | | | | | | :second_precision | Fixnum | | | | | x | x | | | :space | String | | | x | x | | x | | | :space_before | String | | | x | x | | x | | | :symbol_keys | Boolean | x | x | x | x | x | x | | | :trace | Boolean | x | x | x | x | x | x | x | | :time_format | Symbol | | | | | x | x | | | :use_as_json | Boolean | | | | | | x | | | :use_raw_json | Boolean | | | x | x | x | x | | | :use_to_hash | Boolean | | | | | | x | | | :use_to_json | Boolean | | | | | | x | | -------------------------------------------------------------------------------------------------------- 1. :allow_blank an alias for :nilnil. 2. The :ascii_only options is an undocumented json gem option. 3. By default the bigdecimal_as decimal is not set and the default encoding for Rails is as a string. Setting the value to true will encode a BigDecimal as a number which breaks compatibility. 4. The integer indent value in the default options will be honored by since the json gem expects a String type the indent in calls to 'to_json()', 'Oj.generate()', or 'Oj.generate_fast()' expect a String and not an integer. 5. The max_nesting option is for the json gem and rails only. It exists for compatibility. For other Oj dump modes the maximum nesting is set to over 1000. If reference loops exist in the object being dumped then using the `:circular` option is a far better choice. It adds a slight overhead but detects an object that appears more than once in a dump and does not dump that object a second time. 6. The quirks mode option is no longer supported in the most recent json gem. It is supported by Oj for backward compatibility with older json gem versions. oj-3.13.9/pages/WAB.md0000644000004100000410000000113114136373754014312 0ustar www-datawww-data# WAB mode The `:wab` mode ignores all options except the indent option. Performance of this mode is slightly faster than the :strict and :null modes. It is included to support the [WABuR](https://github.com/ohler55/wabur) project. Options other than the indentation are not supported since the encoding and formats are defined by the API that is used to encode data being passed from one components in a WAB system and allowing an option that would break the data exchange is best not supported. The mode encodes like the strict mode except the URI, Time, WAB::UUID, and BigDecimal are supported. oj-3.13.9/pages/Custom.md0000644000004100000410000000231314136373754015156 0ustar www-datawww-data# Custom mode The `:custom` mode is the most configurable mode and honors almost all options. It provides the most flexibility although it can not be configured to be exactly like any of the other modes. Each mode has some special aspect that makes it unique. For example, the `:object` mode has it's own unique format for object dumping and loading. The `:compat` mode mimic the json gem including methods called for encoding and inconsistencies between `JSON.dump()`, `JSON.generate()`, and `JSON()`. The `:custom` mode is the default mode. It can be configured either by passing options to the `Oj.dump()` and `Oj.load()` methods or by modifying the default options. The ability to create objects from JSON object elements is supported and considers the `:create_additions` option. Special treatment is given to the `:create_id` though. If the `:create_id` is set to `"^o"` then the Oj internal encoding and decoding is used. These are more efficient than calling out to a `to_json` method or `create_json` method on the classes. Those method do not have to exist for the `"^o"` behavior to be utilized. Any other `:create_id` value behaves similar to the json gem by calling `to_json` and `create_json` as appropriate. oj-3.13.9/pages/Rails.md0000644000004100000410000001265314136373754014766 0ustar www-datawww-data# Oj Rails Compatibility The `:rails` mode mimics the ActiveSupport version 5 encoder. Rails and ActiveSupport are built around the use of the `as_json(*)` method defined for a class. Oj attempts to provide the same functionality by being a drop in replacement with a few exceptions. ```ruby require 'oj' Oj::Rails.set_encoder() Oj::Rails.set_decoder() Oj::Rails.optimize() Oj::Rails.mimic_JSON() ``` or simply call ```ruby Oj.optimize_rails() ``` Either of those steps will setup Oj to mimic Rails but it will not change the default mode type as the mode type is only used when calling the Oj encoding directly. If Rails mode is also desired then use the `Oj.default_options` to change the default mode. Some of the Oj options are supported as arguments to the encoder if called from `Oj::Rails.encode()` but when using the `Oj::Rails::Encoder` class the `encode()` method does not support optional arguments as required by the ActiveSupport compliance guidelines. The general approach Rails takes for configuring encoding options is to either set global values or to create a new instance of the Encoder class and provide options in the initializer. The globals that ActiveSupport uses for encoding are: * `ActiveSupport::JSON::Encoding.use_standard_json_time_format` * `ActiveSupport::JSON::Encoding.escape_html_entities_in_json` * `ActiveSupport::JSON::Encoding.time_precision` * `ActiveSupport::JSON::Encoding.json_encoder` Those globals are aliased to also be accessed from the ActiveSupport module directly so `ActiveSupport::JSON::Encoding.time_precision` can also be accessed from `ActiveSupport.time_precision`. Oj makes use of these globals in mimicking Rails after the `Oj::Rails.set_encode()` method is called. That also sets the `ActiveSupport.json_encoder` to the `Oj::Rails::Encoder` class. Options passed into a call to `to_json()` are passed to the `as_json()` methods. These are mostly ignored by Oj and simply passed on without modifications as per the guidelines. The exception to this are the options specific to Oj such as the `:circular` option which it used to detect circular references while encoding. By default Oj acts like the ActiveSupport encoder and honors any changes in the `as_json()` methods. There are some optimized Oj encoders for some classes. When the optimized encoder it toggled the `as_json()` methods will not be called for that class but instead the optimized version will be called. The optimized version is the same as the ActiveSupport default encoding for a given class. The optimized versions are toggled with the `optimize()` and `deoptimize()` methods. There is a default optimized version for every class that takes the visible attributes and encodes them but that may not be the same as what Rails uses. Trial and error is the best approach for classes not listed here. The classes that can be put in optimized mode and are optimized when `Oj::Rails.optimize` is called with no arguments are: * Array * BigDecimal * Float * Hash * Range * Regexp * Time * ActiveSupport::TimeWithZone * ActionController::Parameters * any class inheriting from ActiveRecord::Base * any other class where all attributes should be dumped The ActiveSupport decoder is the `JSON.parse()` method. Calling the `Oj::Rails.set_decoder()` method replaces that method with the Oj equivalent. ### Usage in Rails 3 To support Rails 3 you can create a new module mixin to prepend to controllers: ```ruby require 'oj' module OjJsonEncoder def render(options = nil, extra_options = {}, &block) if options && options.is_a?(Hash) && options[:json] obj = options.delete(:json) obj = Oj.dump(obj, :mode => :rails) unless obj.is_a?(String) options[:text] = obj response.content_type ||= Mime::JSON end super end end ``` Usage: ```ruby class MyController < ApplicationController prepend OjJsonEncoder def index render :json => { :hello => 'world' } end end ``` ### Older Ruby Version Support (Pre 2.3.0) If you are using an older version of Ruby, you can pin `oj` to an earlier version in your Gemfile: ```ruby gem 'oj', '3.7.12' ``` ### Notes: 1. Optimized Floats set the significant digits to 16. This is different than Ruby which is used by the json gem and by Rails. Ruby varies the significant digits which can be either 16 or 17 depending on the value. 2. Optimized Hashes do not collapse keys that become the same in the output. As an example, a non-String object that has a `to_s()` method will become the return value of the `to_s()` method in the output without checking to see if that has already been used. This could occur is a mix of String and Symbols are used as keys or if a other non-String objects such as Numerics are mixed with numbers as Strings. 3. To verify Oj is being used turn on the Oj `:trace` option. Similar to the Ruby Tracer Oj will then print out trace information. Another approach is to turn on C extension tracing. Set `tracer = TracePoint.new(:c_call) do |tp| p [tp.lineno, tp.event, tp.defined_class, tp.method_id] end` or, in older Rubies, set `Tracer.display_c_call = true`. For example: ``` require 'active_support/core_ext' require 'active_support/json' require 'oj' Oj.optimize_rails tracer.enable { Time.now.to_json } # prints output including .... [20, :c_call, #, :new] [20, :c_call, Oj::Rails::Encoder, :encode] .... => "\"2018-02-23T12:13:42.493-06:00\"" ``` oj-3.13.9/pages/Security.md0000644000004100000410000000246414136373754015522 0ustar www-datawww-data# Security and Optimization Two settings in Oj are useful for parsing but do expose a vulnerability if used from an untrusted source. Symbolized keys can cause memory to be filled with previous versions of ruby. Ruby 2.1 and below does not garbage collect Symbols. The same is true for auto defining classes in all versions of ruby; memory will also be exhausted if too many classes are automatically defined. Auto defining is a useful feature during development and from trusted sources but it allows too many classes to be created in the object load mode and auto defined is used with an untrusted source. The `Oj.safe_load()` method sets and uses the most strict and safest options. It should be used by developers who find it difficult to understand the options available in Oj. The options in Oj are designed to provide flexibility to the developer. This flexibility allows Objects to be serialized and deserialized. No methods are ever called on these created Objects but that does not stop the developer from calling methods on them. As in any system, check your inputs before working with them. Taking an arbitrary `String` from a user and evaluating it is never a good idea from an unsecure source. The same is true for `Object` attributes as they are not more than `String`s. Always check inputs from untrusted sources. oj-3.13.9/pages/Compatibility.md0000644000004100000410000000175014136373754016521 0ustar www-datawww-data# Compatibility **Ruby** Oj is compatible with Ruby 2.0.0, 2.1, 2.2, 2.3, 2.4 and RBX. Support for JRuby has been removed as JRuby no longer supports C extensions and there are bugs in the older versions that are not being fixed. **Rails** Although up until 4.1 Rails uses [multi_json](https://github.com/intridea/multi_json), an [issue in Rails](https://github.com/rails/rails/issues/9212) causes ActiveSupport to fail to make use Oj for JSON handling. There is a [gem to patch this](https://github.com/GoodLife/rails-patch-json-encode) for Rails 3.2 and 4.0. As of the Oj 2.6.0 release the default behavior is to not use the `to_json()` method unless the `:use_to_json` option is set. This provides another work around to the rails older and newer behavior. The latest ActiveRecord is able to work with Oj by simply using the line: ``` serialize :metadata, Oj ``` In version Rails 4.1, multi_json has been removed, and this patch is unnecessary and will no longer work. See {file:Rails.md}. oj-3.13.9/pages/Encoding.md0000644000004100000410000000656114136373754015443 0ustar www-datawww-data# Oj `:object` Mode Encoding Object mode is for fast Ruby object serialization and deserialization. That was the primary purpose of Oj when it was first developed. As such it is the default mode unless changed in the Oj default options. In :object mode Oj generates JSON that follows conventions which allow Class and other information such as Object IDs for circular reference detection to be encoded in a JSON document. The formatting follows these rules. * JSON native types, true, false, nil, String, Hash, Array, and Number are encoded normally. * A Symbol is encoded as a JSON string with a preceding `':'` character. * The `'^'` character denotes a special key value when in a JSON Object sequence. * A Ruby String that starts with `':'`or the sequence `'^i'` or `'^r'` are encoded by excaping the first character so that it appears as `'\u005e'` or `'\u003a'` instead of `':'` or `'^'`. * A `"^c"` JSON Object key indicates the value should be converted to a Ruby class. The sequence `{"^c":"Oj::Bag"}` is read as the Oj::Bag class. * A `"^t"` JSON Object key indicates the value should be converted to a Ruby Time. The sequence `{"^t":1325775487.000000}` is read as Jan 5, 2012 at 23:58:07. * A `"^o"` JSON Object key indicates the value should be converted to a Ruby Object. The first entry in the JSON Object must be a class with the `"^o"` key. After that each entry is treated as a variable of the Object where the key is the variable name without the preceding `'@'`. An example is `{"^o":"Oj::Bag","x":58,"y":"marbles"}`. `"^O"`is the same except that it is for built in or odd classes that don't obey the normal Ruby rules. Examples are Rational, Date, and DateTime. * A `"^u"` JSON Object key indicates the value should be converted to a Ruby Struct. The first entry in the JSON Object must be a class with the `"^u"` key. After that each entry is is given a numeric position in the struct and that is used as the key in the JSON Object. An example is `{"^u":["Range",1,7,false]}`. * When encoding an Object, if the variable name does not begin with an `'@'`character then the name preceded by a `'~'` character. This occurs in the Exception class. An example is `{"^o":"StandardError","~mesg":"A Message","~bt":[".\/tests.rb:345:in 'test_exception'"]}`. * If a Hash entry has a key that is not a String or Symbol then the entry is encoded with a key of the form `"^#n"` where n is a hex number. The value is an Array where the first element is the key in the Hash and the second is the value. An example is `{"^#3":[2,5]}`. * A `"^i"` JSON entry in either an Object or Array is the ID of the Ruby Object being encoded. It is used when the :circular flag is set. It can appear in either a JSON Object or in a JSON Array. In an Object the `"^i"` key has a corresponding reference Fixnum. In an array the sequence will include an embedded reference number. An example is `{"^o":"Oj::Bag","^i":1,"x":["^i2",true],"me":"^r1"}`. * A `"^r"`JSON entry in an Object is a references to a Object or Array that already appears in the JSON String. It must match up with a previous `"^i"` ID. An example is `{"^o":"Oj::Bag","^i":1,"x":3,"me":"^r1"}`. * If an Array element is a String and starts with `"^i"` then the first character, the `'^'` is encoded as a hex character sequence. An example is `["\u005ei37",3]`. oj-3.13.9/pages/Parser.md0000644000004100000410000003237514136373754015153 0ustar www-datawww-data# How Oj Just Got Faster The original Oj parser is a performant parser that supports several modes. As of this writing Oj is almost 10 years old. A dinosaur by coding standards. It was time for an upgrade. Dealing with issues over the years it became clear that a few things could have been done better. The new `Oj::Parser` is a response that not only attempts to address some of the issues but also give the Oj parser a significant boost in performance. `Oj::Parser` takes a different approach to JSON parsing than the now legacy Oj parser. Not really a legacy parser yet since the `Oj::Parser` is not a drop-in replacement for the JSON gem but it is as much 3 times or more faster than the previous parser in some modes. ## Address Issues There are a few features of the`Oj.load` parser that continue to be the reason for many of the issue on the project. The most significant area is compatibility with both Rails and the JSON gem as they battle it out for which behavior will win out in any particular situation. Most of the issues are on the writing or dumping side of the JSON packages but some are present on the parsing as well. Conversion of decimals is one area where the Rails and the JSON gem vary. The `Oj::Parser` addresses this by allowing for completely separate parser instances. Create a parser and configure it for the situation and leave the others parsers on their own. The `Oj::Parser` is mostly compatible with the JSON gem and Rails but no claims are made that the behavior will be the same as either. The most frequent issues that can addressed with the new parser are around the handling of options. For `Oj.load` there is a set of default options that can be set and the same options can be specified for each call to parse or load. This approach as a couple of downsides. One the defaults are shared across all calls to parse no matter what the desire mode is. The second is that having to provide all the options on each parse call incurs a performance penalty and is just annoying to repeat the same set of options over may calls. By localizing options to a specific parser instance there is never any bleed over to other instances. ## How It's wonderful to wish for a faster parser that solves all the annoyances of the previous parser but how was it done is a much more interesting question to answer. At the core, the API for parsing was changed. Instead of a sinle global parser any number of parsers can be created and each is separate from the others. The parser itself is able to rip through a JSON string, stream, or file and then make calls to a delegate to process the JSON elements according to the delegate behavior. This is similar to the `Oj.load` parser but the new parser takes advantage of character maps, reduced conditional branching, and calling function pointers. ### Options As mentioned, one way to change the options issues was to change the API. Instead of having a shared set of default options a separate parser is created and configured for each use case. Options are set with methods on the parser so no more guessing what options are available. With options isolated to individual parsers there is no unintended leakage to other parse use cases. ### Structure A relative small amount of time is spent in the actual parsing of JSON in `Oj.load`. Most of the time is spent building the Ruby Objects. Even cutting the parsing time in half only gives a 10% improvement in performance but 10% is still an improvement. The `Oj::Parser` is designed to reduce conditional branching. To do that it uses character maps for the various states that the parser goes through when parsing. There is no recursion as the JSON elements are parsed. The use of a character maps for each parser state means the parser function can and is re-entrant so partial blocks of JSON can be parsed and the results combined. There are no Ruby calls in the parser itself. Instead delegates are used to implement the various behaviors of the parser which are currently validation (validate), callbacks (SAJ), or building Ruby objects (usual). The delegates are where all the Ruby calls and related optimizations take place. Considering JSON file parsing, `Oj.load_file` is able to read a file a block at a time and the new `Oj::Parser` does the same. There was a change in how that is done though. `Oj.load_file` sets up a reader that must be called for each character. Basically a buffered reader. `Oj::Parser` drops down a level and uses a re-entrant parser that takes a block of bytes at a time so there is no call needed for each character but rather just iterating over the block read from the file. Reading a block at a time also allows for an efficient second thread to be used for reading blocks. That feature is not in the first iteration of the `Oj::Parser` but the stage is set for it in the future. The same approach was used successfully in [OjC](https://github.com/ohler55/ojc) which is where the code for the parser was taken from. ### Delegates There are three delegates; validate, SAJ, and usual. #### Validate The validate delegate is trivial in that does nothing other than let the parser complete. There are no options for the validate delegate. By not making any Ruby calls other than to start the parsing the validate delegate is no surprise that the validate delegate is the best performer. #### SAJ (Simple API for JSON) The SAJ delegate is compatible with the SAJ handlers used with `Oj.saj_parse` so it needs to keep track of keys for the callbacks. Two optimizations are used. The first is a reuseable key stack while the second is a string cache similar to the Ruby intern function. When parsing a Hash (JSON object) element the key is passed to the callback function if the SAJ handler responds to the method. The key is also provided when closing an Array or Hash that is part of a parent Hash. A key stack supports this. If the option is turned on a lookup is made and previously cached key VALUEs are used. This avoids creating the string for the key and setting the encoding on it. The cache used is a auto expanding hash implementation that is limited to strings less than 35 characters which covers most keys. Larger strings use the slower string creation approach. The use of the cache reduces object creation which save on both memory allocation and time. It is not appropriate for one time parsing of say all the keys in a dictionary but is ideally suited for loading similar JSON multiple times. #### Usual By far the more complex of the delegates is the 'usual' delegate. The usual delegate builds Ruby Objects when parsing JSON. It incorporates many options for configuration and makes use of a number of optimizations. ##### Reduce Branching In keeping with the goal of reducing conditional branching most of the delegate options are implemented by changing a function pointer according to the option selected. For example when turning on or off `:symbol_keys` the function to calculate the key is changed so no decision needs to be made during parsing. Using this approach option branching happens when the option is set and not each time when parsing. ##### Cache Creating Ruby Objects whether Strings, Array, or some other class is expensive. Well expensive when running at the speeds Oj runs at. One way to reduce Object creation is to cache those objects on the assumption that they will most likely be used again. This is especially true of Hash keys and Object attribute IDs. When creating Objects from a class name in the JSON a class cache saves resolving the string to a class each time. Of course there are times when caching is not preferred so caching can be turned on or off with option methods on the parser which are passed down to the delegate.. The Oj cache implementation is an auto expanding hash. When certain limits are reached the hash is expanded and rehashed. Rehashing can take some time as the number of items cached increases so there is also an option to start with a larger cache size to avoid or reduce the likelihood of a rehash. The Oj cache has an advantage over the Ruby intern function (`rb_intern()`) in that several steps are needed for some cached items. As an example Object attribute IDs are created by adding an `@` character prefix to a string and then converting to a ID. This is done once when inserting into the cache and after that only a lookup is needed. ##### Bulk Insert The Ruby functions available for C extension functions are extensive and offer many options across the board. The bulk insert functions for both Arrays and Hashes are much faster than appending or setting functions that set one value at a time. The Array bulk insert is around 15 times faster and for Hash it is about 3 times faster. To take advantage of the bulk inserts arrays of VALUEs are needed. With a little planning there VALUE arrays can be reused which leads into another optimization, the use of stacks. ##### Stacks Parsing requires memory to keep track of values when parsing nested JSON elements. That can be done on the call stack making use of recursive calls or it can be done with a stack managed by the parser. The `Oj.load` method maintains a stack for Ruby object and builds the output as the parsing progresses. `Oj::Parser` uses three different stacks. One stack for values, one for keys, and one for collections (Array and Hash). By postponing the creation of the collection elements the bulk insertions for Array and Hash can be used. For arrays the use of a value stack and creating the array after all elements have been identified gives a 15x improvement in array creation. For Hash the story is a little different. The bulk insert for Hash alternates keys and values but there is a wrinkle to consider. Since Ruby Object creation is triggered by the occurrence of an element that matches a creation identifier the creation of a collection is not just for Array and Hash but also Object. Setting Object attributes uses an ID and not a VALUE. For that reason the keys should not be created as String or Symbol types as they would be ignored and the VALUE creation wasted when setting Object attributes. Using the bulk insert for Hash gives a 3x improvement for that part of the object building. Looking at the Object creation the JSON gem expects a class method of `#json_create(arg)`. The single argument is the Hash resulting from the parsing assuming that the parser parsed to a Hash first. This is less than ideal from a performance perspective so `Oj::Parser` provides an option to take that approach or to use the much more efficient approach of never creating the Hash but instead creating the Object and then setting the attributes directly. To further improve performance and reduce the amount of memory allocations and frees the stacks are reused from one call to `#parse` to another. ## Results The results are even better than expected. Running the [perf_parser.rb](https://github.com/ohler55/oj/blob/develop/test/perf_parser.rb) file shows the improvements. There are four comparisons all run on a MacBook Pro with Intel processor. ### Validation Without a comparible parser that just validates a JSON document the `Oj.saj_parse` callback parser with a nil handler is used for comparison to the new `Oj::Parser.new(:validate)`. In that case the comparison is: ``` System time (secs) rate (ops/sec) ------------------- ----------- -------------- Oj::Parser.validate 0.101 494369.136 Oj::Saj.none 0.205 244122.745 ``` The `Oj::Parser.new(:validate)` is **2.03** times faster! ### Callback Oj has two callback parsers. One is SCP and the other SAJ. Both are similar in that a handler is provided that implements methods for processing the various element types in a JSON document. Comparing `Oj.saj_parse` to `Oj::Parser.new(:saj)` with a all callback methods implemented handler gives the following raw results: ``` System time (secs) rate (ops/sec) -------------- ----------- -------------- Oj::Parser.saj 0.783 63836.986 Oj::Saj.all 1.182 42315.397 ``` The `Oj::Parser.new(:saj)` is **1.51** times faster. ### Parse to Ruby primitives Parsing to Ruby primitives and Array and Hash is possible with most parsers including the JSON gem parser. The raw results comparing `Oj.strict_load`, `Oj::Parser.new(:usual)`, and the JSON gem are: ``` System time (secs) rate (ops/sec) ---------------- ----------- -------------- Oj::Parser.usual 0.452 110544.876 Oj::strict_load 0.699 71490.257 JSON::Ext 1.009 49555.094 ``` The `Oj::Parser.new(:saj)` is **1.55** times faster than `Oj.load` and **2.23** times faster than the JSON gem. ### Object Oj supports two modes for Object serialization and deserialization. Comparing to the JSON gem compatible mode `Oj.compat_load`, `Oj::Parser.new(:usual)`, and the JSON gem yields the following raw results: ``` System time (secs) rate (ops/sec) ---------------- ----------- -------------- Oj::Parser.usual 0.071 703502.033 Oj::compat_load 0.225 221762.927 JSON::Ext 0.401 124638.859 ``` The `Oj::Parser.new(:saj)` is **3.17** times faster than `Oj.compat_load` and **5.64** times faster than the JSON gem. ## Summary With a performance boost of from 1.5x to over 3x over the `Oj.load` parser the new `Oj::Parser` is a big win in the performance arena. The isolation of options is another feature that should make life easier for developers. oj-3.13.9/pages/JsonGem.md0000644000004100000410000000542014136373754015250 0ustar www-datawww-data# Oj JSON Gem Compatibility The `:compat` mode mimics the json gem. The json gem is built around the use of the `to_json(*)` method defined for a class. Oj attempts to provide the same functionality by being a drop in replacement for the 2.0.x version of the json gem with a few exceptions. First a description of the json gem behavior and then the differences between the json gem and the Oj.mimic_JSON behavior. ```ruby require 'oj' Oj.mimic_JSON() Oj.add_to_json(Array, BigDecimal, Complex, Date, DateTime, Exception, Hash, Integer, OpenStruct, Range, Rational, Regexp, Struct, Time) # Alternativel just call without arguments to add all available. # Oj.add_to_json() ``` The json gem monkey patches core and base library classes with a `to_json(*)` method. This allows calls such as `obj.to_json()` to be used to generate a JSON string. The json gem also provides the JSON.generate(), JSON.dump(), and JSON() functions. These functions generally act the same with some exceptions such as JSON.generate(), JSON(), and to_json raise an exception when attempting to encode infinity while JSON.dump() returns a the string "Infinity". The String class is also monkey patched with to_json_raw() and to_json_raw_object(). Oj in mimic mode mimics this behavior including the seemly inconsistent behavior with NaN and Infinity. Any class can define a to_json() method and JSON.generate(), JSON.dump(), and JSON() functions will call that method when an object of that type is encountered when traversing a Hash or Array. The core classes monkey patches can be over-ridden but unless the to_json() method is called directory the to_json() method will be ignored. Oj in mimic mode follow the same logic, The json gem includes additions. These additions change the behavior of some library and core classes. These additions also add the as_json() method and json_create() class method. They are activated by requiring the appropriate files. As an example, to get the modified to_json() for the Rational class this line would be added. ```ruby require 'json/add/rational' ``` Oj in mimic mode does not include these files although it will support the modified to_json() methods. In keeping with the goal of providing a faster encoder Oj offers an alternative. To activate faster addition version of the to_json() method call ```ruby Oj.add_to_json(Rational) ``` To revert back to the unoptimized version, just remove the Oj flag on that class. ```ruby Oj.remove_to_json(Rational) ``` The classes that can be added are: * Array * BigDecimal * Complex * Date * DateTime * Exception * Hash * Integer * OpenStruct * Range * Rational * Regexp * Struct * Time The compatibility target version is 2.0.3. The json gem unit tests were used to verify compatibility with a few changes to use Oj instead of the original gem. oj-3.13.9/pages/Options.md0000644000004100000410000002421714136373754015346 0ustar www-datawww-data# Oj Options To change default serialization mode use the following form. Attempting to modify the Oj.default_options Hash directly will not set the changes on the actual default options but on a copy of the Hash: ```ruby Oj.default_options = {:mode => :compat } ``` Another way to make use of options when calling load or dump methods is to pass in a Hash with the options already set in the Hash. This is slightly less efficient than setting the globals for many smaller JSON documents but does provide a more thread safe approach to using custom options for loading and dumping. ### Options for serializer and parser ### :allow_blank [Boolean] If true a nil input to load will return nil and not raise an Exception. ### :allow_gc [Boolean] Allow or prohibit GC during parsing, default is true (allow). ### :allow_invalid_unicode [Boolean] Allow invalid unicode, default is false (don't allow). ### :allow_nan Alias for the :nan option. ### :array_class [Class] Class to use instead of Array on load. ### :array_nl Trailer appended to the end of an array dump. The default is an empty string. Primarily intended for json gem compatibility. Using just indent as an integer gives better performance. ### :ascii_only If true all non-ASCII character are escaped when dumping. This is the same as setting the :escape_mode options to :ascii and exists for json gem compatibility. ### :auto_define [Boolean] Automatically define classes if they do not exist. ### :bigdecimal_as_decimal [Boolean] If true dump BigDecimal as a decimal number otherwise as a String ### :bigdecimal_load [Symbol] Determines how to load decimals. - `:bigdecimal` convert all decimal numbers to BigDecimal. - `:float` convert all decimal numbers to Float. - `:auto` the most precise for the number of digits is used. This can also be set with `:decimal_class` when used as a load or parse option to match the JSON gem. In that case either `Float`, `BigDecimal`, or `nil` can be provided. ### :cache_keys [Boolean] If true Hash keys are cached or interned. There are trade-offs with caching keys. Large caches will use more memory and in extreme cases (like over a million) the cache may be slower than not using it. Repeated parsing of similar JSON docs is where cache_keys shines especially with symbol keys. There is a maximum length for cached keys. Any key longer than 34 bytes is not cached. Everything still works but the key is not cached. ### :cache_strings [Int] Shorter strings can be cached for better performance. A limit, cache_strings, defines the upper limit on what strings are cached. As with cached keys only strings less than 35 bytes are cached even if the limit is set higher. Setting the limit to zero effectively disables the caching of string values. Note that caching for strings is for string values and not Hash keys or Object attributes. ### :circular [Boolean] Detect circular references while dumping. In :compat mode raise a NestingError. For other modes except the :object mode place a null in the output. For :object mode place references in the output that will be used to recreate the looped references on load. ### :class_cache [Boolean] Cache classes for faster parsing. This option should not be used if dynamically modifying classes or reloading classes then don't use this. ### :compat_bigdecimal [Boolean] Determines how to load decimals when in `:compat` mode. - `true` convert all decimal numbers to BigDecimal. - `false` convert all decimal numbers to Float. ### :create_additions A flag indicating that the :create_id key, when encountered during parsing, should create an Object matching the class name specified in the value associated with the key. ### :create_id [String] The :create_id option specifies that key is used for dumping and loading when specifying the class for an encoded object. The default is `json_create`. In the `:custom` mode, setting the `:create_id` to nil will cause Complex, Rational, Range, and Regexp to be output as strings instead of as JSON objects. ### :empty_string [Boolean] If true an empty or all whitespace input will not raise an Exception. The default_options will be honored for :null, :strict, and :custom modes. Ignored for :custom and :wab. The :compat has a more complex set of rules. The JSON gem compatibility is best described by examples. ``` JSON.parse('') => raise JSON.parse(' ') => raise JSON.load('') => nil JSON.load('', nil, allow_blank: false) => raise JSON.load('', nil, allow_blank: true) => nil JSON.load(' ') => raise JSON.load(' ', nil, allow_blank: false) => raise JSON.load(' ', nil, allow_blank: true) => raise ``` ### :escape_mode [Symbol] Determines the characters to escape when dumping. Only the :ascii and :json modes are supported in :compat mode. - `:newline` allows unescaped newlines in the output. - `:json` follows the JSON specification. This is the default mode. - `:xss_safe` escapes HTML and XML characters such as `&` and `<`. - `:ascii` escapes all non-ascii or characters with the hi-bit set. - `:unicode_xss` escapes a special unicodes and is xss safe. ### :float_precision [Fixnum] The number of digits of precision when dumping floats, 0 indicates use Ruby directly. ### :hash_class [Class] Class to use instead of Hash on load. This is the same as the :object_class. ### :ignore [Array] Ignore all the classes in the Array when dumping. A value of nil indicates ignore nothing. ### :indent [Fixnum] Number of spaces to indent each element in a JSON document, zero is no newline between JSON elements, negative indicates no newline between top level JSON elements in a stream. ### :indent_str Indentation for each element when dumping. The default is an empty string. Primarily intended for json gem compatibility. Using just indent as an integer gives better performance. ### :integer_range [Range] Dump integers outside range as strings. Note: range bounds must be Fixnum. ### :match_string Provides a means to detect strings that should be used to create non-String objects. The value to the option must be a Hash with keys that are regular expressions and values are class names. For strict json gem compatibility a RegExp should be used. For better performance but sacrificing some regexp options a string can be used and the C version of regex will be used instead. ### :max_nesting The maximum nesting depth on both dump and load that is allowed. This exists for json gem compatibility. ### :mode [Symbol] Primary behavior for loading and dumping. The :mode option controls which other options are in effect. For more details see the {file:Modes.md} page. By default Oj uses the :custom mode which is provides the highest degree of customization. ### :nan [Symbol] How to dump Infinity, -Infinity, and NaN in :null, :strict, and :compat mode. Default is :auto but is ignored in the :compat and :rails modes. - `:null` places a null - `:huge` places a huge number - `:word` places Infinity or NaN - `:raise` raises and exception - `:auto` uses default for each mode which are `:raise` for `:strict`, `:null` for `:null`, and `:word` for `:compat`. ### :nilnil [Boolean] If true a nil input to load will return nil and not raise an Exception. ### :object_class The class to use when creating a Hash on load instead of the Hash class. ### :object_nl Trailer appended to the end of an object dump. The default is an empty string. Primarily intended for json gem compatibility. Using just indent as an integer gives better performance. ### :omit_nil [Boolean] If true, Hash and Object attributes with nil values are omitted. ### :quirks_mode [Boolean] Allow single JSON values instead of documents, default is true (allow). This can also be used in :compat mode to be backward compatible with older versions of the json gem. ### :safe The JSON gem includes the complete JSON in parse errors with no limit on size. To break from the JSON gem behavior for this case set `:safe` to true. ### :second_precision [Fixnum] The number of digits after the decimal when dumping the seconds of time. ### :space String inserted after the ':' character when dumping a JSON object. The default is an empty string. Primarily intended for json gem compatibility. Using just indent as an integer gives better performance. ### :space_before String inserted before the ':' character when dumping a JSON object. The default is an empty string. Primarily intended for json gem compatibility. Using just indent as an integer gives better performance. ### :symbol_keys [Boolean] Use symbols instead of strings for hash keys. ### :symbolize_names [Boolean] Like :symbol_keys has keys are made into symbols but only when mimicking the JSON gem and then only as the JSON gem honors it so JSON.parse honors the option but JSON.load does not. ### :trace When true dump and load functions are traced by printing beginning and ending of blocks and of specific calls. ### :time_format [Symbol] The :time_format when dumping. - `:unix` time is output as a decimal number in seconds since epoch including fractions of a second. - `:unix_zone` is similar to the `:unix` format but with the timezone encoded in the exponent of the decimal number of seconds since epoch. - `:xmlschema` time is output as a string that follows the XML schema definition. - `:ruby` time is output as a string formatted using the Ruby `to_s` conversion. ### :use_as_json [Boolean] Call `as_json()` methods on dump, default is false. The option is ignored in the :compat and :rails modes. ### :use_raw_json [Boolean] Call `raw_json()` methods on dump, default is false. The option is accepted in the :compat and :rails modes even though it is not supported by other JSON gems. It provides a means to optimize dump or generate performance. The `raw_json(depth, indent)` method should be called only by Oj. It is not intended for any other use. This is meant to replace the abused `to_json` methods. Calling `Oj.dump` inside the `raw_json` with the object itself when `:use_raw_json` is true will result in an infinite loop. ### :use_to_hash [Boolean] Call `to_hash()` methods on dump, default is false. The option is ignored in the :compat and :rails modes. ### :use_to_json [Boolean] Call `to_json()` methods on dump, default is false. The option is ignored in the :compat and :rails modes. oj-3.13.9/README.md0000644000004100000410000001027614136373754013551 0ustar www-datawww-data# [![{}j](http://www.ohler.com/dev/images/oj_comet_64.svg)](http://www.ohler.com/oj) gem [![Build Status](https://img.shields.io/github/workflow/status/ohler55/oj/CI?logo=github)](https://github.com/ohler55/oj/actions/workflows/CI.yml) ![Gem](https://img.shields.io/gem/v/oj.svg) ![Gem](https://img.shields.io/gem/dt/oj.svg) [![SemVer compatibility](https://api.dependabot.com/badges/compatibility_score?dependency-name=oj&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=oj&package-manager=bundler&version-scheme=semver) [![TideLift](https://tidelift.com/badges/github/ohler55/oj)](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme) A *fast* JSON parser and Object marshaller as a Ruby gem. Version 3.13 is out with a much faster parser (`Oj::Parser`) and option isolation. ## Using ```ruby require 'oj' h = { 'one' => 1, 'array' => [ true, false ] } json = Oj.dump(h) # json = # { # "one":1, # "array":[ # true, # false # ] # } h2 = Oj.load(json) puts "Same? #{h == h2}" # true ``` ## Installation ``` gem install oj ``` or in Bundler: ``` gem 'oj' ``` ## Support [Get supported Oj with a Tidelift Subscription.](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme) Security updates are [supported](https://tidelift.com/security). ## Further Reading For more details on options, modes, advanced features, and more follow these links. - [{file:Options.md}](pages/Options.md) for parse and dump options. - [{file:Modes.md}](pages/Modes.md) for details on modes for strict JSON compliance, mimicking the JSON gem, and mimicking Rails and ActiveSupport behavior. - [{file:JsonGem.md}](pages/JsonGem.md) includes more details on json gem compatibility and use. - [{file:Rails.md}](pages/Rails.md) includes more details on Rails and ActiveSupport compatibility and use. - [{file:Custom.md}](pages/Custom.md) includes more details on Custom mode. - [{file:Encoding.md}](pages/Encoding.md) describes the :object encoding format. - [{file:Compatibility.md}](pages/Compatibility.md) lists current compatibility with Rubys and Rails. - [{file:Advanced.md}](pages/Advanced.md) for fast parser and marshalling features. - [{file:Security.md}](pages/Security.md) for security considerations. ## Releases See [{file:CHANGELOG.md}](CHANGELOG.md) and [{file:RELEASE_NOTES.md}](RELEASE_NOTES.md) ## Links - *Documentation*: http://www.ohler.com/oj/doc, http://rubydoc.info/gems/oj - *GitHub* *repo*: https://github.com/ohler55/oj - *RubyGems* *repo*: https://rubygems.org/gems/oj Follow [@peterohler on Twitter](http://twitter.com/peterohler) for announcements and news about the Oj gem. #### Performance Comparisons - [Oj Strict Mode Performance](http://www.ohler.com/dev/oj_misc/performance_strict.html) compares Oj strict mode parser performance to other JSON parsers. - [Oj Compat Mode Performance](http://www.ohler.com/dev/oj_misc/performance_compat.html) compares Oj compat mode parser performance to other JSON parsers. - [Oj Object Mode Performance](http://www.ohler.com/dev/oj_misc/performance_object.html) compares Oj object mode parser performance to other marshallers. - [Oj Callback Performance](http://www.ohler.com/dev/oj_misc/performance_callback.html) compares Oj callback parser performance to other JSON parsers. #### Links of Interest - *Fast XML parser and marshaller on RubyGems*: https://rubygems.org/gems/ox - *Fast XML parser and marshaller on GitHub*: https://github.com/ohler55/ox - [Need for Speed](http://www.ohler.com/dev/need_for_speed/need_for_speed.html) for an overview of how Oj::Doc was designed. - *OjC, a C JSON parser*: https://www.ohler.com/ojc also at https://github.com/ohler55/ojc - *Agoo, a high performance Ruby web server supporting GraphQL on GitHub*: https://github.com/ohler55/agoo - *Agoo-C, a high performance C web server supporting GraphQL on GitHub*: https://github.com/ohler55/agoo-c #### Contributing + Provide a Pull Request off the `develop` branch. + Report a bug + Suggest an idea + Code is now formatted with the clang-format tool with the configuration file in the root of the repo. oj-3.13.9/CHANGELOG.md0000644000004100000410000012574514136373754014113 0ustar www-datawww-data# CHANGELOG ## 3.13.9 - 2021-10-06 - Fix mimic JSON load so that it honors the `:symbolize_names` option. ## 3.13.8 - 2021-09-27 - Fix `Oj::Doc` behaviour for inexisting path. ```ruby Oj::Doc.open('{"foo":1}') do |doc| doc.fetch('/foo/bar') # used to give `1`, now gives `nil` doc.exists?('/foo/bar') # used to give `true`, now gives `false` end ``` - Fix `Oj::Parser` handling of BigDecimal. `snprint()` does not handle `%Lg` correctly but `sprintf()` does. ## 3.13.7 - 2021-09-16 - The JSON gem allows invalid unicode so Oj, when mimicing JSON now allows it as well. Use `:allow_invalid_unicode` to change that. ## 3.13.6 - 2021-09-11 - Fixed unicode UTF 8 parsing in string values. - Fixed hash key allocation issue. - The `Oj::Parser.new()` function now allows optional arguments that set the allowed options for the mode. As an example `Oj::Parser.new(:usual, cache_keys: true)`. ## 3.13.5 - 2021-09-08 - Assure value strings of zero length are not always cached. ## 3.13.4 - 2021-09-04 - Fixed concurrent GC issue in the cache. ## 3.13.3 - 2021-08-30 - Caches are now self adjusting and clearing so less used entries are expunged to avoid memory growth. - When mimicking the JSON gem the JSON::State now handles all Hash methods. While this is different than the actually JSON gem it avoids failing due to errors in Rails code and other gems. ## 3.13.2 - 2021-08-11 - Fixed C99 compiler errors. ## 3.13.1 - 2021-08-09 - Fixed failing build on Windows. ## 3.13.0 - 2021-08-08 - Added `Oj::Parser`, a faster parser with better option management. - Watson1978 increasd dump performance ever more and is now a collaborator on Oj! ## 3.12.3 - 2021-08-02 - Update documents for the `:cache_keys` and `:cache_strings`. - Watson1978 increased dump performance for rails mode. ## 3.12.2 - 2021-07-25 - Thanks to Watson1978 for the dump performance for time values. ## 3.12.1 - 2021-07-09 - Fixed `:cache_keys` not being honored in compat mode. - Increased the cache size. ## 3.12.0 - 2021-07-05 - Added string and symbol caching options that give Oj about a 20% parse performance boost. ## 3.11.8 - 2021-07-03 - Fixed or reverted change that set the default mode when optimize_Rails was called. ## 3.11.7 - 2021-06-22 - Fixed exception type when parsing after `Oj::Rails.mimic_JSON` is called. ## 3.11.6 - 2021-06-14 - Fixed bug where `Oj::Doc#fetch` on an empty Hash or Array did not return `nil`. - Added an `Oj::Doc#exists?` method. - Deprecated `Oj::Doc#where?` in favor `Oj::Doc#where` or the alias, `Oj::Doc#path`. ## 3.11.5 - 2021-04-15 - Oj.generate fix introduced in previous bug fix. Oj.mimic_JSON is forced if Oj.generate is called. ## 3.11.4 - 2021-04-14 - Code re-formatted with clang-format. Thanks goes to BuonOmo for suggesting and encouraging the use of a formatter and getting the effort started. - Added support for `GC.compact` on `Oj::Doc` - Fixed compatibility issue with Rails and the JSON gem that requires a special case when using JSON.generate versus call `to_json` on an object. ## 3.11.3 - 2021-03-09 - Fixed `respond_to?` method on `Oj::EasyHash`. ## 3.11.2 - 2021-01-27 - Fixed JSON gem `:decimal_class` option. ## 3.11.1 - 2021-01-24 - XrXr fixed Ruby 3.0.0 object movement issue. ## 3.11.0 - 2021-01-12 - Added `:compat_bigdecimal` to support the JSON gem `:decimal_class` undocumented option. - Reverted the use of `:bigdecimal_load` for `:compat` mode. ## 3.10.18 - 2020-12-25 - Fix modes table by marking compat mode `:bigdecimal_load` instead of `:bigdecimal_as_decimal`. - Added `:custom` mode setting that are the same as `:compat` mode. There are some minor differences between the two setting. ## 3.10.17 - 2020-12-15 - The undocumented JSON gem option of `:decimal_class` is now supported and the default option of `:bigdecimal_load` is also honored in JSON.parse() and in compat mode. - Invalid encoding detection bug fixed for rails. ## 3.10.16 - 2020-11-10 - Allow escaping any character in :compat mode to match the json gem behavior. ## 3.10.15 - 2020-10-16 Remove license from code. ## 3.10.14 - 2020-09-05 - Updated float test to check a range. ## 3.10.13 - 2020-08-28 - Removed explicit dependency on bigdecimal. ## 3.10.12 - 2020-08-20 - Another try at bracketing bigdecimal versions to 1.0 and 3. ## 3.10.11 - 2020-08-20 - Bracketed bigdecimal dependency to 1.0 and 3. ## 3.10.10 - 2020-08-20 - Backed off bigdecimal dependency to 1.0. ## 3.10.9 - 2020-08-17 - Add bigdecimal dependency to gemfile. ## 3.10.8 - 2020-07-24 - New fast option for float parsing. - Fixes a float parse error. ## 3.10.7 - 2020-07-13 - Faster float parsing and an adjust to more closely match Ruby. ## 3.10.6 - 2020-04-04 - Correct object dump code to continue instead of return on an _ attribnute. ## 3.10.5 - 2020-03-03 - Fix test ## 3.10.4 - 2020-03-03 - Another adjustment to get Ruby floats to match Oj thanks to klaxit. ## 3.10.3 - 2020-03-01 - Fixed difference between some unicode character encoding in Rails mode. ## 3.10.2 - 2020-01-30 - Fixed circular array reference load. ## 3.10.1 - 2020-01-14 - Fixed bug where setting `ActiveSupport::JSON::Encoding.use_standard_json_time_format` before calling `Oj.optimize_rails` did not have an effect on the time format. - Worked around the Active Support hack that branched in `to_json` depending on the class of the option argument. - Updated for Ruby 2.7.0 ## 3.10.0 - 2019-11-28 - Fixed custom mode load_file to use custom mode. - Fixed rails mode output of IO objects - Fixed issue #567. Second precision is forced if set to the correct number of decimals even if zero nanoseconds. - Fixed issue #568. Dumping bigdecimal in `:rails' mode made more consistent. - Fixed issue #569. `:compat` mode not restricts the escape mode as indicated in the documentation. - Fixed issue #570. In `:strict` mode number parsing follows the JSON specification more closely as intended. - Added `:ignore_under` which when true will ignore attributes that begin with a `_` when dumping in `:object` or `:custom` mode. ## 3.9.2 - 2019-10-01 - Fixed wrong exception type when mimicking the JSON gem. ## 3.9.1 - 2019-08-31 - Raise an exception on an invalid time represented as a decimal in `:object` mode. ## 3.9.0 - 2019-08-18 - Changed custom behavior when `:create_additions` is true and `:create_id` is set to nil. Now Range, Regexp, Rational, and Complex are output as strings instead of a JSON object with members. Setting any other value for `:create_id`, even an empty string will result in an object being dumped. - Detection of pthread mutex support was changed. ## 3.8.1 - 2019-07-22 - Fix replacement of JSON::Parse thanks to paracycle. ## 3.8.0 - 2019-07-17 - Fixed a buffer allocation bug for `JSON.pretty_generate`. - Added mimic `safe` option to not include the complete JSON in a parse error message. - Added `use_raw_json` option for `:compat` and `:rails` mode to allow better performance on dump. StringWriter in particular has been optimized for this option. ## 3.7.12 - 2019-04-14 - The `:omit_nil` option did not work in `:rails` mode. That has been fixed. ## 3.7.11 - 2019-03-19 - Fix to Rails optimize that missed initializing the mimic JSON `:symbolize_names` value. ## 3.7.10 - 2019-03-14 - Corrected time dump so that the none-leap years after a 400 year period are correct. ## 3.7.9 - 2019-02-18 - Return correct value in `create_opt` C function. - Return `Oj::ParseError` if an invalid big decimal string is encountered instead of an argument error ## 3.7.8 - 2019-01-21 - Replace `gmtime` with a custom function. - Oj::Doc crash fix. - Mark odd args to avoid GC. ## 3.7.7 - 2019-01-14 - Exception with other than a single argument initializer can now be decoded. - StreamWriter bug fixed that forces UTF-8 when appending to a stream. Ruby likes to convert to ASCII-8BIT but forcing the append to be UTF-8 avoids that issue. ## 3.7.6 - 2018-12-30 - Changed time encoding for 32 bit to work around a Ruby bug in `rb_time_timespec()` that fails for times before 1970. - Addressed issue #514 by changing reserved identifiers. - Addressed issue #515 by adding return value checks on `strdup()` and `pthread_mutex_init()`. ## 3.7.5 - 2018-12-27 - Address issue #517 with a special escape table for mimicking the JSON gem. ## 3.7.4 - 2018-11-29 - Allow `+` in front of numbers in parse as well as stream parse **EXCEPT** when mimicking the JSON gem. ## 3.7.3 - 2018-11-29 - Allow `+` in front of numbers in parse as well as stream parse. ## 3.7.2 - 2018-11-29 - More tolerant float parsing to allow `123.`. - Parse exceptions raised by user code now preserve the message content of the exception. ## 3.7.1 - 2018-11-09 - Updated to support TruffleRuby. ## 3.7.0 - 2018-10-29 - Thanks to Ziaw for adding a integer range where integers outside that range are written as strings. ## 3.6.13 - 2018-10-25 - Fixed issue where exceptions were not being cleared on parsing. - Added addition unicode dump error information. ## 3.6.12 - 2018-10-16 - Fixed random `:omit_nil` setting with StringWriter and StreamWriter. ## 3.6.11 - 2018-09-26 - Added the JSON path to parse error messages. - BigDecimal parse errors now return Oj::ParseError instead of ArgumentError. ## 3.6.10 - 2018-09-13 - Additional occurrences of `SYM2ID(sym)` replaced. ## 3.6.9 - 2018-09-12 - `SYM2ID(sym)` causes a memory leak. A work around is now used. ## 3.6.8 - 2018-09-08 - Stopped setting the default options when optimize rails is called as the documentation has indicated. - In custom mode `Date` and `DateTime` instances default to use the `:time_format` option is the `:create_additions` option is false. ## 3.6.7 - 2018-08-26 - Fixed incorrect check in StreamWriter when adding raw JSON. ## 3.6.6 - 2018-08-16 - Fixed Debian build issues on several platforms. - `oj_slash_string` is now frozen. ## 3.6.5 - 2018-07-26 - Fixed GC issue with Oj::Doc. - Fixed issue with time encoding with Windows. ## 3.6.4 - 2018-07-10 - JSON.generate() now sets the `to_json` option as expected. - Show `:create_additions` in the default options. It did not appear before. ## 3.6.3 - 2018-06-21 - Fixed compat dump compilation error on Windows. ## 3.6.2 - 2018-05-30 - Regex encoded correctly for rails when using `to_json`. - ActiveSupport::TimeWithZone optimization fixed. ## 3.6.1 - 2018-05-16 - Fixed realloc bug in rails dump. ## 3.6.0 - 2018-05-01 - Add optimization for Rails ActiveRecord::Result encoding. ## 3.5.1 - 2018-04-14 - Fixed issue with \u0000 terminating keys early. - Add trace for calls to `to_json` and 'as_json`. ## 3.5.0 - 2018-03-04 - A trace option is now available in all modes. The format roughly follows the Ruby trace format. ## 3.4.0 - 2018-01-24 - Option to ignore specific classes in :object and :custom mode added. ## 3.3.10 - 2017-12-30 - Re-activated the bigdecimal_as_decimal option in custom mode and made the selection active for Rails mode. - Fixed bug in optimize_rails that did not optimize all classes as expected. - Fixed warnings for Ruby 2.5.0. ## 3.3.9 - 2017-10-27 - Fixed bug where empty strings were sometimes marked as invalid. ## 3.3.8 - 2017-10-04 - Fixed Rail mimic precision bug. ## 3.3.7 - 2017-10-02 - Handle invalid unicode characters better for single byte strings. - Parsers for empty strings handle errors more consistently. ## 3.3.6 - 2017-09-22 - Numerous fixes and cleanup to support Ruby 2.4.2. ## 3.3.5 - 2017-08-11 - Fixed a memory error when using rails string encoding of certain unicode characters. ## 3.3.4 - 2017-08-03 - Oj::Rails.mimic_JSON now uses Float for JSON.parse for large decimals instead of BigDecimal. ## 3.3.3 - 2017-08-01 - Allow nil as a second argument to parse when mimicking the json gem. This is a special case where the gem does not raise an exception on a non-Hash second argument. ## 3.3.2 - 2017-07-11 - Fixed Windows compile issue regarding timegm(). ## 3.3.1 - 2017-07-06 - Some exceptions such as NoMethodError have an invisible attribute where the key name is NULL. Not an empty string but NULL. That is now detected and dealt with. ## 3.3.0 - 2017-07-05 - Added the :wab mode to support the [WABuR](https://github.com/ohler55/wabur) project. The :wab mode only allows the indent option and is faster due to not having to check the multitude of options the other modes support. ## 3.2.1 - 2017-07-04 - Made json gem NaN dumping more consistent. - Fixed :null mode to not revert to strict mode. ## 3.2.0 - 2017-06-20 - A buffer_size option was added to StringWriter and StreamWriter. That option also sets the suggested flush limit for StreamWriter. ## 3.1.4 - 2017-06-16 - :symbolize_names now works with Oj::Rails.mimic_JSON. ## 3.1.3 - 2017-06-13 - JSON.dump used the default mode instead of :compat mode. That has been fixed. - Updated docs on Oj.mimic_JSON to note the :encoding is set to :unicode_xss and not :ascii. ## 3.1.2 - 2017-06-10 - StringWriter was passing incorrect arguments to to_json and as_json. Fixed. - StringWriter now accepts the mode option. This means it also defaults to the default_options mode instead of the custom mode. - JSON.pretty_generate when in rails mode now loads the JSON::State class correctly. ## 3.1.1 - 2017-06-09 - Custom mode now honors the :create_additions option for time. ## 3.1.0 - 2017-06-04 - Added Oj::Rails.mimic_JSON which mimics the json gem when used with ActiveSupport which monkey patches the same to_json methods does. Basically this changes the JSON.parse and other JSON entry points for encoding and decoding. - Oj.optimize_rails now calls Oj::Rails.mimic_JSON. ## 3.0.11 - 2017-06-02 - If rails in required and Oj.mimic_JSON is called without calling Oj.optimize_rails nil.to_json failed. That has been fixed. - Fixed crash on setting create_id to nil after mimic_JSON. ## 3.0.10 - 2017-05-22 - Oj.optimize_rails failed to include Hash and Array in the optimization. It does now. ## 3.0.9 - 2017-05-17 - Missed catching invalid unicodes beginning with \xE2. Fixed. - In :compat mode a non-string and non IO value is converted to a string with a call to to_s. ## 3.0.8 - 2017-05-16 - Float significant digits now match Ruby's unless optimized and then significant digits are set to 16. - Rails Hash post merging of identical keys after calling as_json is preserved only for un-optimized Hashes. - Raise an exception to match json gem behavior on invalid unicode. ## 3.0.7 - 2017-05-10 - Changed JSON.pretty_generate options to a State instead of a Hash to get the json gen to_json method to accept formatting options. - Added optimization for ActionController::Parameters ## 3.0.6 - 2017-05-07 - Added Oj.optimize_rails(). - Fixed argument passing bug on calls to to_json(). ## 3.0.5 - 2017-05-02 - No change in the Oj::Rails.optimize() API but additional classes are now supported. - Added ActiveSupport::TimeWithZone optimization to rails mode. - Added ActiveRecord::Base optimization to rails mode which allows optimization of any class inheriting from ActiveRecord::Base. ## 3.0.4 - 2017-05-01 - Fixed compile problem on Windows again. - Fixed issue with TimeWithZone not being encoded correctly when a member of an object. ## 3.0.3 - 2017-04-28 - Improved compatibility with a json gem and Rails combination when json adds are used. - Fixed compile problem on Windows. ## 3.0.2 - 2017-04-27 - Fixed crash due to unset var with dump to Rails mode. ## 3.0.1 - 2017-04-25 - Fixed compile error with help from Dylan Johnson. ## 3.0.0 - 2017-04-24 - Major changes focused on json gem and Rails compatibility. A new :custom mode was added as well. Starting with this release the json gem tests are being used to test the :compat mode and the ActiveSupport 5 tests are being used to test the :rails mode. - Please give stereobooster a thank you for getting the tests set up and helping out with json gem and Rails understanding. He also has some benchmarks of Oj versus other options [here](https://github.com/stereobooster/ruby-json-benchmark). ## 2.18.3 - 2017-03-14 - Changed to use long doubles for parsing to minimize round off errors. So PI will be accurate to more places for PI day. ## 2.18.2 - 2017-03-01 - Strict mode now allows symbol keys in hashes. - Fixed omit_nil bug discovered by ysmazda. ## 2.18.1 - 2017-01-09 - Missing argument to dump now raises the correct arg exception and with mimic does not crash on missing argument. - Oj::Doc paths can now contain escaped path separators so "a\/b" can match an lement name with a slash in it. ## 2.18.0 - 2016-11-26 - Rubinius compilation fixes. - Added a separate option for as_json instead of piggy backing on the use_to_json. This changes the API slightly. - Ready for Ruby 2.4. - Thanks to faucct for fixing mimic to not redefine JSON::ParserError. ## 2.17.5 - 2016-10-19 - Added additional code to check for as_json arguments and recursive calls. ## 2.17.4 - 2016-09-04 - Added the ascii_only option to JSON.generate when in mimic_JSON mode so that it is consistent with the undocumented feature in the json gem. ## 2.17.3 - 2016-08-16 - Updated mimic_JSON to monkey patch OpenStruct, Range, Rational, Regexp, Struct, Symbol, and Time like the JSON gem does. Also added the JSON.create_id accessor. ## 2.17.2 - 2016-08-13 - Worked around a problem with DateTime and ActiveSupport that causes a hang when hour, minute, second, and some other methods are called from C. ## 2.17.1 - 2016-07-08 - Added an option provide an alternative Hash class for loading. - Added the Oj::EasyHash class. - Fixed test failures on 32 bit machines. - Sped up mimic_JSON. - Added an option to omit Hash and Object attributes with nil values. ## 2.16.1 - 2016-06-21 - Thanks to hsbt for fixing a compile issue with Ruby 2.4.0-preview1. ## 2.16.0 - 2016-06-19 - Added option to allow invalid unicode characters. This is not a suggested option in a majority of the cases. - Fixed float parsing for 32 bit systems so that it does not roll over to BigDecimal until more than 15 significant digits. ## 2.15.1 - 2016-05-26 - Fixed bug with activerecord when to_json returns an array instead of a string. ## 2.15.0 - 2016-03-28 - Fixed bug where encoded strings could be GCed. - :nan option added for dumping Infinity, -Infinity, and NaN. This is an edition to the API. The default value for the :non option is :auto which uses the previous NaN handling on dumping of non-object modes. ## 2.14.7 - 2016-03-20 - Fixed bug where a comment before another JSON element caused an error. Comments are not part of the spec but this keep support consistent. ## 2.14.6 - 2016-02-26 - Changed JSON::ParserError to inherit from JSON::JSONError which inherits from StandardError. ## 2.14.5 - 2016-02-19 - Parse errors in mimic mode are not a separate class than Oj.ParseError so the displayed name is JSON::ParserError instead. ## 2.14.4 - 2016-02-04 - When not in quirks mode strings are now raise an exception if they are the top level JSON value. ## 2.14.3 - 2015-12-31 - Updated to support Ruby 2.3.0. ## 2.14.2 - 2015-12-19 - Non-UTF-8 string input is now converted to UTF-8. ## 2.14.1 - 2015-12-15 - Fixed bug that reverted to BigDecimal when a decimal had preceding zeros after the decimal point. ## 2.14.0 - 2015-12-04 - More tweaking on decimal rounding. - Made dump options available in the default options and not only in the mimic generate calls. ## 2.13.1 - 2015-11-16 - Thanks to Comboy for the fix to a large number rounding bug. ## 2.13.0 - 2015-10-19 - Oj no longer raises an exception if the to_hash method of an object does not return a Hash. ActiveRecord has decided that to_hash should return an Array instead so Oj now encodes what ever is returned. - Added a register_odd_raw function that allows odd marshal functions to return raw JSON as a string to be included in the dumped JSON. - The register_odd function now supports modules in additions to classes. ## 2.12.14 - 2015-09-14 - Change the failure mode for objects that can not be serialized such as a socket. Now in compat mode the to_s of the object is placed in the output instead of raising an exception. ## 2.12.13 - 2015-09-02 - Added a check for the second argument to load() is a Hash. - Yet another attempt to make floating point numbers display better. - Thanks to mkillianey for getting the extconf.rb and gemspec file updated. ## 2.12.12 - 2015-08-09 - Thanks to asurin for adding support for arguments to to_json() that rails uses. ## 2.12.11 - 2015-08-02 - Oj::ParseError is now thrown instead of SyntaxError when there are multiple JSON documents in a string or file and there is no proc or block associated with the parse call. ## 2.12.10 - 2015-07-12 - An exception is now raised if there are multiple JSON documents in a string or file if there is no proc or block associated with the parse call. ## 2.12.9 - 2015-05-30 - Fixed failing test when using Rubinius. - Changed mimic_JSON to support the global/kernel function JSON even when the json gem is loaded first. ## 2.12.8 - 2015-05-16 - mimic_JSON now supports the global/kernel JSON function that will either parse a string argument or dump an array or object argument. ## 2.12.7 - 2015-05-07 - Fixed compile error with 32 bit HAS_NANO_TIME extra paren bug. ## 2.12.6 - 2015-05-05 - Fixed a number of 32bit related bugs. ## 2.12.5 - 2015-04-27 - In :null mode Oj now dumps Infinity and NaN as null. ## 2.12.4 - 2015-04-20 - Fixed memory leak in Oj::Doc when not using a given proc. ## 2.12.3 - 2015-04-16 - Fixed bug when trying to resolve an invalid class path in object mode load. ## 2.12.2 - 2015-04-13 - Fixed Oj::Doc bug that causes a crash on local name lookups. - Fixed Oj::Doc unicode parsing. ## 2.12.1 - 2015-03-12 - Changed the unix_zone encoded format to be the utc epoch. ## 2.12.0 - 2015-03-06 - String formats for UTC time are now explicitly UTC instead of offset of zero. This fixes a problem with pre-2.2.0 Rubies that automatically convert zero offset times to local times. - Added :unix_zone time_format option for formatting numeric time. This option is the same as the :unix time option but the UTC offset is included as an exponent to the number time value. A value of 86400 is an indication of UTC time. ## 2.11.5 - 2015-02-25 - Fixed issue with rails as_json not being called for Structs. - Added support for anonymous Structs with object mode encoding. Note that this will result in a new anonymous Struct for each instance. ## 2.11.4 - 2015-01-20 - DateTime second encoding is now always a Rational to preserve accuracy. - Fixed buf in the Oj.load() callback feature that caused an endless loop when a StringIO was used with a JSON that was a number. ## 2.11.3 - 2015-01-18 - DateTime encoding now includes nanoseconds. ## 2.11.2 - 2015-01-10 - Changed the defaults for mimic_JSON to use 16 significant digits instead of the default 15. - Fixed bug where a subclass of Array would be serialized as if in object mode instead of compat when in compat mode. ## 2.11.1 - 2014-11-09 - Changed the use_to_json option to still allow as_json even when set to false. ## 2.11.0 - 2014-11-02 - Restricted strict dump to not dump NaN nor Infinity but instead raise an exception. - Changed compat mode so that the :bigdecimal_as_decimal option over-rides the to_json method if the option is true. The default for mimic_JSON is to leave the option off. - Added support for Module encoding in compat mode. - Added ActiveSupportHelper so that require 'active_support_helper' will added a helper for serializing ActiveSupport::TimeWithZone. - Added float_precision option to control the number of digits used for floats when dumping. ## 2.10.4 - 2014-10-22 - Fixed Range encoding in compat mode to not use the object mode encoding. - Fixed serialization problem with timestamps. - Fixed compat parser to accept NaN and Infinity. ## 2.10.3 - 2014-10-04 - Using the xmlschema option with :object mode now saves time as a string and preserves the timezone. - Rational recursive loop caused by active support fixed. - Time in mimic_JSON mode are now the ruby string representation of a date. ## 2.10.2 - 2014-08-20 - Fixed string corruption bug due to an uncommented assignment used for debugging. ## 2.10.1 - 2014-08-17 - Changed parse argument error to be a Ruby ArgError instead of a general Exception. ## 2.10.0 - 2014-08-03 - Using an indent of less than zero will not place newline characters between JSON elements when using the string or stream writer. - A new options callback method was added to the Simple Callback Parser. If defined the prepare_key() method will be called when an JSON object ket is first encountered. The return value is then used as the key in the key-value pair. - Increased significants digits to 16 from 15. On occasion there may be unexpected round off results. Tou avoid those use the bigdecimal options. ## 2.9.9 - 2014-07-07 - Missed a character map entry. / in ascii mode is now output as / and not \/ - Fixed SC parser to not treat all IO that respond to fileno as a file. It not checks stat and asks if it is a file. - Tightened object parsing of non-string hash keys so that just "^#x" is parsed as a hash pair and not "~#x". - Using echo to STDIN as an IO input works around the exception raised when asking the IO for it's position (IO.pos). - Simple Callback Parser now uses the new stream parser for handling files and IO so that larger files are handled more efficiently and streams are read as data arrives instead of on close. - Handles file FIFO pipes correctly now. ## 2.9.8 - 2014-06-25 - Changed escaping back to previous release and added a new escape mode. - Strict mode and compat mode no longer parse Infinity or NaN as a valid number. Both are valid in object mode still. ## 2.9.7 - 2014-06-24 - Changed dump to use raw / and raw \n in output instead of escaping. - Changed the writer to always put a new line at the end of a top level JSON object. It makes output easier to view and edit with minimal impact on size. - Worked around the file.gets look ahead caching as long as it is not called while parsing (of course). - Thanks to lautis for a new parse option. quirks_mode allows Oj to behave quirky like the JSON gem. Actually the JSON gem has it backwards with quirky mode supporting the JSON spec and non-quirky limiting parsing to objects and arrays. Oj stays consistent with the JSON gem to avoid confusion. - Fixed problem with sc_parse not terminating the string when loaded from a file. - Thanks go to dchelimsky for expanding the code sample for the ScHandler. ## 2.9.6 - 2014-06-15 - Fixed bug using StringIO with SCParser. - Tightened up JSON mimic to raise an exception if JSON.parse is called on a JSON documents that returns a primitive type. ## 2.9.5 - 2014-06-07 - Mimic mode now monkey patches Object like JSON. - A big thanks to krasnoukhov for reorganizing test and helping get Oj more rails compatible. - Another thanks goes out to lautis for a pull request that provided some optimization and fixed the return exception for an embedded null in a string. - Fixed a bug with zip reader streams where EOF was not handled nicely. ## 2.9.4 - 2014-05-28 - In mimic mode parse errors now match the JSON::ParserError. ## 2.9.3 - 2014-05-15 - Fixed IO read error that showed up in IO objects that return nil instead of raising an EOF error when read is done. ## 2.9.2 - 2014-05-14 - Fixed link error with Windows. ## 2.9.1 - 2014-05-14 - Fixed mimic load when given a block to evalate. It conflicted with the new load option. - Added a true stream that is used when the input argument to load is an IO object such as a stream or file. This is slightly slower for smaller files but allows reading of huge files that will not fit in memory and is more efficient on even larger files that would fit into memory. The load_file() method uses the new stream parser so multiple GB files can be processed without used execessive memory. ## 2.9.0 - 2014-05-01 - Added support for detection and handling of String, Array, and Hash subclasses. - Oj.load() can now take a block which will be yielded to on every object parsed when used with a file or string with multiple JSON entries. ## 2.8.1 - 2014-04-21 - Added additional argument to the register_odd function. - Fixed bug that failed to load on some uses of STDIN. ## 2.8.0 - 2014-04-20 - Added support for registering special encoding and decoding rules for specific classes. This the ActiveSupport subclass of the String class for safe strings. For an example look at the `test_object.rb` file, `test_odd_string` test. ## 2.7.3 - 2014-04-11 - Fixed bug where load and dump of Structs in modules did not work correctly. ## 2.7.2 - 2014-04-06 - Added option return nil if nil is provided as input to load. ## 2.7.1 - 2014-03-30 - Fixed bug in new push_key which caused duplicate characters. ## 2.7.0 - 2014-03-29 - Added the push_key() method to the StringWriter and StreamWriter classes. ## 2.6.1 - 2014-03-21 - Set a limit on the maximum nesting depth to 1000. An exception is raised instead of a segfault unless a reduced stack is used which could trigger the segfault due to an out of memory condition. ## 2.6.0 - 2014-03-11 - Added the :use_to_json option for Oj.dump(). If this option is set to false the to_json() method on objects will not be called when dumping. This is the default behavior. The reason behind the option and change is to better support Rails and ActiveSupport. Previous works arounds have been removed. ## 2.5.5 - 2014-02-18 - Worked around the Rubinius failure to load bigdecimal from a require within the C code. ## 2.5.4 - 2014-01-14 - Fixed bug where unterminated JSON did not raise an exception. ## 2.5.3 - 2014-01-03 - Added support for blocks with StringWriter. ## 2.5.2 - 2014-01-02 - Fixed indent problem with StringWriter so it now indents properly. ## 2.5.1 - 2013-12-18 - Added push_json() to the StringWriter and StreamWriter to allow raw JSON to be added to a JSON document being constructed. ## 2.4.3 - 2013-12-16 - Made include pthread.h conditional for Windows. ## 2.4.2 - 2013-12-14 - Thanks to Patrik Rak for a fix to a buffer short copy when dumping with 0 indent. ## 2.4.1 - 2013-12-10 - Fixed Windows version by removing class cache test. ## 2.4.0 - 2013-12-08 - Merged in a PR to again allow strings with embedded nulls. - Implemented StreamWriter to compliment the StringWriter. - Fixed bug in the class cache hash function that showed up with the sparc compiler. ## 2.3.0 - 2013-12-01 - JRuby is no longer supported. - Thanks to Stefan Kaes the support for structs has been optimized. - Better support for Rubinous. - Added option to disable GG during parsing. - Added StringWriter that allows building a JSON document one element at a time. ## 2.2.3 - 2013-11-19 - Fixed struct segfault on load. - Added option to force float on load if a decimal number. ## 2.2.2 - 2013-11-17 - Added mutex support for Windows. - Protected SCP parser for GC. ## 2.2.1 - 2013-11-15 - Made all VALUEs in parse volatile to avoid garbage collection while in use. ## 2.2.0 - 2013-11-11 - All 1.8.x versions of Ruby now have require 'rational' called. - Removed the temporary GC disable and implemented mark strategy instead. - Added new character encoding mode to support the Rails 4 escape characters of &, as xss_safe mode. The :encoding option replaces the :ascii_only option. - Change parsing of NaN to not use math.h which on older systems does not define NAN. ## 2.1.8 - 2013-10-28 - All 1.8.x versions of Ruby now have require 'rational' called. - Removed the temporary GC disable and implemented mark strategy instead. ## 2.1.7 - 2013-10-19 - Added support for NaN and -NaN to address issue #102. This is not according to the JSON spec but seems to be expected. - Added require for rational if the Ruby version is 1.8.7 to address issue #104. - Added Rails re-call of Oj.dump in the to_json() method which caused loops with Rational objects to fix issue #108 and #105. ## 2.1.6 - 2013-10-07 - Added Oj.to_stream() for dumping JSON to an IO object. ## 2.1.5 - 2013-07-21 - Allow exception dumping magic with Windows. ## 2.1.4 - 2013-07-04 - Fixed Linux 32 bit rounding bug. ## 2.1.3 - 2013-06-30 - Fixed bug that did not deserialize all attributes in an Exception subclass. - Added a sample to demonstrate how to write Exception subclasses that will automatically serialize and deserialize. ## 2.1.2 - 2013-06-19 - Fixed support for Windows. ## 2.1.1 - 2013-06-17 - Fixed off by 1 error in buffer for escaped strings. ## 2.1.0 - 2013-06-16 - This version is a major rewrite of the parser. The parser now uses a constant stack size no matter how deeply nested the JSON document is. The parser is also slightly faster for larger documents and 30% faster for object parsing. - Oj.strict_load() was renamed to Oj.safe_load() to better represent its functionality. A new Oj.strict_load() is simply Oj.load() with :mode set to :strict. - Oj.compat_load() and Oj.object_load() added. - A new Simple Callback Parser was added invoked by Oj.sc_parse(). - Eliminated :max_stack option as it is no longer needed. - Handle cleanup after exceptions better. ## 2.0.14 - 2013-05-26 - Fixed bug in Oj::Doc.each_leaf that caused an incorrect where path to be created and also added a check for where path maximum length. - Updated the documentation to note that invalid JSON documents, which includes an empty string or file, will cause an exception to be raised. ## 2.0.13 - 2013-05-17 - Changed dump to put closing array brackets and closing object curlies on the line following the last element if :indent is set to greater than zero. ## 2.0.12 - 2013-04-28 - Another fix for mimic. - mimic_JSON now can now be called after loading the json gem. This will replace the json gem methods after loading. This may be more compatible in many cases. ## 2.0.11 - 2013-04-23 - Fixed mimic issue with Debian - Added option to not cache classes when loading. This should be used when classes are dynamically unloaded and the redefined. - Float rounding improved by limiting decimal places to 15 places. - Fixed xml time dumping test. ## 2.0.10 - 2013-03-10 - Tweaked dump calls by reducing preallocation. Speeds are now several times faster for smaller objects. - Fixed Windows compile error with Ruby 2.0.0. ## 2.0.9 - 2013-03-04 - Fixed problem with INFINITY with CentOS and Ruby 2.0.0. There are some header file conflicts so a different INFINITY was used. ## 2.0.8 - 2013-03-01 - Added :bigdecimal_load option that forces all decimals in a JSON string to be read as BigDecimals instead of as Floats. This is useful if precision is important. - Worked around bug in active_support 2.3.x where BigDecimal.as_json() returned self instead of a JSON primitive. Of course that creates a loop and blows the stack. Oj ignores the as_json() for any object that returns itself and instead encodes the object as it sees fit which is usually what is expected. - All tests pass with Ruby 2.0.0-p0. Had to modify Exception encoding slightly. ## 2.0.7 - 2013-02-18 - Fixed bug where undefined classes specified in a JSON document would freeze Ruby instead of raising an exception when the auto_define option was not set. (It seems that Ruby freezes on trying to add variables to nil.) ## 2.0.6 - 2013-02-18 - Worked around an undocumented feature in linux when using make that misreports the stack limits. ## 2.0.5 - 2013-02-16 - DateTimes are now output the same in compat mode for both 1.8.7 and 1.9.3 even though they are implemented differently in each Ruby. - Objects implemented as data structs can now change the encoding by implemented either to_json(), as_json(), or to_hash(). - Added an option to allow BigDecimals to be dumped as either a string or as a number. There was no agreement on which was the best or correct so both are possible with the correct option setting. ## 2.0.4 - 2013-02-11 - Fixed bug related to long class names. - Change the default for the auto_define option. - Added Oj.strict_load() method that sets the options to public safe options. This should be safe for loading JSON documents from a public unverified source. It does not eleviate to need for reasonable programming practices of course. See the section on the proper use of Oj in a public exposure. ## 2.0.3 - 2013-02-03 - Fixed round off error in time format when rounding up to the next second. ## 2.0.2 - 2013-01-23 - Fixed bug in Oj.load where loading a hash with symbold keys and also turning on symbolize keys would try to symbolize a symbol. ## 2.0.1 - 2013-01-15 - BigDecimals now dump to a string in compat mode thanks to cgriego. - High precision time (nano time) can be turned off for better compatibility with other JSON parsers. - Times before 1970 now encode correctly. ## 2.0.0 - 2012-12-18 - Thanks to yuki24 Floats are now output with a decimal even if they are an integer value. - The Simple API for JSON (SAJ) API has been added. Read more about it on the Oj::Saj page. ## 1.4.7 - 2012-12-09 - In compat mode non-String keys are converted to Strings instead of raising and error. (issue #52) ## 1.4.6 - 2012-12-03 - Silently ignores BOM on files and Strings. - Now works with JRuby 1.7.0 to the extent possible with the unsupported C extension in JRuby. ## 1.4.5 - 2012-11-19 - Adds the old deprecated methods of unparse(), fast_unparse(), and pretty_unparse() to JSON_mimic. ## 1.4.4 - 2012-11-07 - Fixed bug in mimic that missed mimicking json_pure. ## 1.4.3 - 2012-10-19 - Fixed Exception encoding in Windows version. ## 1.4.2 - 2012-10-17 - Fixed dump and load of BigDecimals in :object mode. - BigDecimals are now dumped and loaded in all modes. ## 1.4.1 - 2012-10-17 - Windows RubyInstaller and TCS-Ruby now supported thanks to Jarmo Pertman. Thanks Jarmo. ## 1.4.0 - 2012-10-11 - Parse errors now raise an Exception that inherites form Oj::Error which inherits from StandardError. Some other Exceptions were changed as well to make it easier to rescue errors. ## 1.3.7 - 2012-10-05 - Added header file for linux builds. ## 1.3.6 - 2012-10-04 - Oj.load() now raises a SystemStackError if a JSON is too deeply nested. The loading is allowed to use on 75% of the stack. - Oj::Doc.open() now raises a SystemStackError if a JSON is too deeply nested. The loading is allowed to use on 75% of the stack. Oj::Doc.open will allow much deeper nesting than Oj.load(). ## 1.3.5 - 2012-09-25 - Fixed mimic_JSON so it convinces Ruby that the ALL versions of the json gem are already loaded. ## 1.3.4 - 2012-08-12 - Fixed mimic_JSON so it convinces Ruby that the json gem is already loaded. ## 1.3.2 - 2012-07-28 - Fixed compile problems with native Ruby on OS X 10.8 (Mountain Lion) ## 1.3.1 - 2012-07-01 - Fixed time zone issue with :xmlschema date format. ## 1.3.0 - 2012-07-09 - extconf.rb fixed to not pause on some OSs when checking CentOS special case. - Added an option to control the time format output when in :compat mode. ## 1.2.13 - 2012-07-08 - Fixed double free bug in Oj::Doc that showed up for larger documents. ## 1.2.12 - 2012-07-06 - Fixed GC bug in Oj::Doc, the fast parser. - Serialization of Exceptions in Ruby 1.8.7 now includes message and backtrace. ## 1.2.11 - 2012-06-21 - Added :max_stack option to limit the size of string allocated on the stack. ## 1.2.10 - 2012-06-20 - Added check for circular on loading of circular dumped JSON. - Added support for direct serialization of BigDecimal, Rational, Date, and DateTime. - Added json.rb to $" in mimic mode to avoid pulling in the real JSON by accident. - Oj is now thread safe for all functions. - The / (solidus) character is now placed in strings without being escaped. ## 1.2.9 - 2012-05-29 ## 1.2.8 - 2012-05-03 - Included a contribution by nevans to fix a math.h issue with an old fedora linux machine. - Included a fix to the documentation found by mat. ## 1.2.7 - 2012-04-22 - Fixed bug where a float with too many characters would generate an error. It is not parsed as accuractly as Ruby will support. - Cleaned up documentation errors. - Added support for OS X Ruby 1.8.7. ## 1.2.6 - 2012-04-22 - Cleaned up documentation errors. - Added support for OS X Ruby 1.8.7. ## 1.2.5 - 2012-04-18 - Added support for create_id in Oj and in mimic_JSON mode ## 1.2.4 - 2012-04-13 - Removed all use of math.h to get around CentOS 5.4 compile problem. ## 1.2.3 - 2012-04-09 - Fixed compile error for the latest RBX on Travis. ## 1.2.2 - 2012-04-02 - minor bug fixes for different rubies along with test updates - Oj::Doc will now automatically close on GC. ## 1.2.1 - 2012-03-30 - Organized compile configuration better. - as_json() support now more flexible thanks to a contribution by sauliusg. ## 1.2.0 - 2012-03-30 - Removed the encoding option and fixed a misunderstanding of the string encoding. Unicode code points are now used instead of byte codes. This is not compatible with previous releases but is compliant with RFC4627. - Time encoding in :object mode is faster and higher nanosecond precision. ## 1.1.1 - 2012-03-27 - The encoding option can now be an Encoding Object or a String. - Fixed Rubinius errors. ## 1.1.0 - 2012-03-27 - Errors are not longer raised when comments are encountered in JSON documents. - Oj can now mimic JSON. With some exceptions calling JSON.mimic_JSON will allow all JSON calls to use OJ instead of JSON. This gives a speedup of more than 2x on parsing and 5x for generating over the JSON::Ext module. - Oj::Doc now allows a document to be left open and then closed with the Oj::Doc.close() class. - Changed the default encoding to UTF-8 instead of the Ruby default String encoding. ## 1.0.6 - 2012-03-20 - Gave Oj::Doc a speed increase. It is now 8 times fast than JSON::Ext. ## 1.0.5 - 2012-03-17 - Added :ascii_only options for dumping JSON where all high-bit characters are encoded as escaped sequences. ## 1.0.4 - 2012-03-16 - Fixed bug that did not allow symbols as keys in :compat mode. ## 1.0.3 - 2012-03-15 - Added :symbol_keys option to convert String hash keys into Symbols. - The load() method now supports IO Objects as input as well as Strings. ## 1.0.2 - 2012-03-15 - Added RSTRING_NOT_MODIFIED for Rubinius optimization. ## 1.0.1 - 2012-03-15 - Fixed compatibility problems with Ruby 1.8.7. ## 1.0.0 - 2012-03-13 - The screaming fast Oj::Doc parser added. ## 0.9.0 - 2012-03-05 - Added support for circular references. ## 0.8.0 - 2012-02-27 - Auto creation of data classes when unmarshalling Objects if the Class is not defined ## 0.7.0 - 2012-02-26 - changed the object JSON format - serialized Ruby Objects can now be deserialized - improved performance testing ## 0.6.0 - 2012-02-22 - supports arbitrary Object dumping/serialization - to_hash() method called if the Object responds to to_hash and the result is converted to JSON - to_json() method called if the Object responds to to_json - almost any Object can be dumped, including Exceptions (not including Thread, Mutex and Objects that only make sense within a process) - default options have been added ## 0.5.2 - 2012-02-19 - Release 0.5.2 fixes encoding and float encoding. - This is the first release sith a version of 0.5 indicating it is only half done. Basic load() and dump() is supported for Hash, Array, NilClass, TrueClass, FalseClass, Fixnum, Float, Symbol, and String Objects. ## 0.5.1 - 2012-02-19 ## 0.5 - 2012-02-19 - This is the first release with a version of 0.5 indicating it is only half done. Basic load() and dump() is supported for Hash, Array, NilClass, TrueClass, FalseClass, Fixnum, Float, Symbol, and String Objects. oj-3.13.9/LICENSE0000644000004100000410000000206614136373754013275 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2012 Peter Ohler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. oj-3.13.9/RELEASE_NOTES.md0000644000004100000410000000431514136373754014641 0ustar www-datawww-data# RELEASE NOTES The release notes here are organized by release. For a list of changes see the See [{file:CHANGELOG.md}](CHANGELOG.md) file. In this file are the steps to take to aid in keeping things rolling after updating to the latest version. ## 3.13.7 The default for JSON when mimicked by Oj is now to set `:allow_invalid_unicode`. To change that behavior JSON.load, set that option to false. ## 3.13.x This release included a new cache that performs better than the earlier cache and a new high performance parser. ### Cache The new cache includes a least recently used expiration to reduce memory use. The cache is also self adjusting and will expand as needed for better performance. It also handles Hash keys and string values with two options, `:cache_keys`, a boolean and `:cache_str` an integer. The `:cache_str` if set to more than zero is the limit for the length of string values to cache. The maximum value is 35 which allows strings up to 34 bytes to be cached. One interesting aspect of the cache is not so much the string caching which performs similar to the Ruby intern functions but the caching of symbols and object attribute names. There is a significant gain for symbols and object attributes. If the cache is not desired then setting the default options to turn it off can be done with this line: ``` ruby Oj.default_options = { cache_keys: false, cache_str: 0 } ``` ### Oj::Parser The new parser uses a different core that follows the approach taken by [OjC](https://github.com/ohler55/ojc) and [OjG](https://github.com/ohler55/ojg). It also takes advantage of the bulk Array and Hash functions. Another issue the new parser addresses is option management. Instead of a single global default_options each parser instance maintains it's own options. There is a price to be paid when using the Oj::Parser. The API is not the same the older parser. A single parser can only be used in a single thread. This allows reuse of internal buffers for additional improvements in performance. The performane advantage of the Oj::Parse is that it is more than 3 times faster than the Oj::compat_load call and 6 times faster than the JSON gem. ### Dump Performance Thanks to Watson1978 Oj.dump also received a speed boost. oj-3.13.9/lib/0000755000004100000410000000000014136373754013032 5ustar www-datawww-dataoj-3.13.9/lib/oj/0000755000004100000410000000000014136373754013442 5ustar www-datawww-dataoj-3.13.9/lib/oj/mimic.rb0000644000004100000410000002007014136373754015064 0ustar www-datawww-data require 'bigdecimal' begin require 'ostruct' rescue Exception # ignore end module Oj ## # Custom mode can be used to emulate the compat mode with some minor # differences. These are the options that setup the custom mode to be like # the compat mode. CUSTOM_MIMIC_JSON_OPTIONS = { allow_gc: true, allow_invalid_unicode: false, allow_nan: false, array_class: nil, array_nl: nil, auto_define: false, bigdecimal_as_decimal: false, bigdecimal_load: :auto, circular: false, class_cache: false, cache_keys: true, cache_str: 5, create_additions: false, create_id: "json_class", empty_string: false, escape_mode: :unicode_xss, float_precision: 0, hash_class: nil, ignore: nil, ignore_under: false, indent: 0, integer_range: nil, mode: :custom, nan: :raise, nilnil: false, object_nl: nil, omit_nil: false, quirks_mode: true, safe: false, second_precision: 3, space: nil, space_before: nil, symbol_keys: false, time_format: :ruby, trace: false, use_as_json: false, use_raw_json: false, use_to_hash: false, use_to_json: true, } # A bit hack-ish but does the trick. The JSON.dump_default_options is a Hash # but in mimic we use a C struct to store defaults. This class creates a view # onto that struct. class MimicDumpOption < Hash def initialize() oo = Oj.default_options self.store(:max_nesting, false) self.store(:allow_nan, true) self.store(:quirks_mode, oo[:quirks_mode]) self.store(:ascii_only, (:ascii == oo[:escape_mode])) end def []=(key, value) case key when :quirks_mode Oj.default_options = {:quirks_mode => value} when :ascii_only Oj.default_options = {:ascii_only => value} end end end # Loads mimic-ed JSON paths. Used by Oj.mimic_JSON(). # @param mimic_paths [Array] additional paths to add to the Ruby loaded features. def self.mimic_loaded(mimic_paths=[]) $LOAD_PATH.each do |d| next unless File.exist?(d) jfile = File.join(d, 'json.rb') $LOADED_FEATURES << jfile unless $LOADED_FEATURES.include?(jfile) if File.exist?(jfile) Dir.glob(File.join(d, 'json', '**', '*.rb')).each do |file| # allow json/add/xxx to be loaded. User can override with Oj.add_to_json(xxx). $LOADED_FEATURES << file unless $LOADED_FEATURES.include?(file) unless file.include?('add') end end mimic_paths.each { |p| $LOADED_FEATURES << p } $LOADED_FEATURES << 'json' unless $LOADED_FEATURES.include?('json') require 'oj/json' if Object.const_defined?('OpenStruct') OpenStruct.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) name = self.class.name.to_s raise JSON::JSONError, "Only named structs are supported!" if 0 == name.length { JSON.create_id => name, 't' => table } end end def self.json_create(h) new(h['t'] || h[:t]) end end end BigDecimal.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'BigDecimal', 'b' => _dump } end end def self.json_create(h) BigDecimal._load(h['b']) end end Complex.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'Complex', 'r' => real, 'i' => imag } end end def self.json_create(h) Complex(h['r'], h['i']) end end DateTime.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) { JSON.create_id => 'DateTime', 'y' => year, 'm' => month, 'd' => day, 'H' => hour, 'M' => min, 'S' => sec, 'of' => offset.to_s, 'sg' => start } end end def self.json_create(h) # offset is a rational as a string as, bs = h['of'].split('/') a = as.to_i b = bs.to_i if 0 == b off = a else off = Rational(a, b) end civil(h['y'], h['m'], h['d'], h['H'], h['M'], h['S'], off, h['sg']) end end Date.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) { JSON.create_id => 'Date', 'y' => year, 'm' => month, 'd' => day, 'sg' => start } end end def self.json_create(h) civil(h['y'], h['m'], h['d'], h['sg']) end end Exception.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => self.class.name, 'm' => message, 'b' => backtrace } end end def self.json_create(h) e = new(h['m']) e.set_backtrace(h['b']) e end end Range.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'Range', 'a' => [first, last, exclude_end?]} end end def self.json_create(h) new(*h['a']) end end Rational.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'Rational', 'n' => numerator, 'd' => denominator } end end def self.json_create(h) Rational(h['n'], h['d']) end end Regexp.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'Regexp', 'o' => options, 's' => source } end end def self.json_create(h) new(h['s'], h['o']) end end Struct.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) name = self.class.name.to_s raise JSON::JSONError, "Only named structs are supported!" if 0 == name.length { JSON.create_id => name, 'v' => values } end end def self.json_create(h) new(*h['v']) end end Symbol.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) {JSON.create_id => 'Symbol', 's' => to_s } end end def self.json_create(h) h['s'].to_sym end end Time.class_eval do # Both the JSON gem and Rails monkey patch as_json. Let them battle it out. unless defined?(self.as_json) def as_json(*) nsecs = [ tv_usec * 1000 ] nsecs << tv_nsec if respond_to?(:tv_nsec) nsecs = nsecs.max { JSON.create_id => 'Time', 's' => tv_sec, 'n' => nsecs } end end def self.json_create(h) if usec = h.delete('u') h['n'] = usec * 1000 end if instance_methods.include?(:tv_nsec) at(h['s'], Rational(h['n'], 1000)) else at(h['s'], h['n'] / 1000) end end end end # self.mimic_loaded end # Oj # More monkey patches. class String def to_json_raw_object { JSON.create_id => self.class.name, 'raw' => self.bytes } end def to_json_raw(*) to_json_raw_object().to_json() end def self.json_create(obj) s = '' s.encode!(Encoding::ASCII_8BIT) if s.respond_to?(:encode!) raw = obj['raw'] if raw.is_a? Array raw.each { |v| s << v } end s end end oj-3.13.9/lib/oj/version.rb0000644000004100000410000000010714136373754015452 0ustar www-datawww-data module Oj # Current version of the module. VERSION = '3.13.9' end oj-3.13.9/lib/oj/schandler.rb0000644000004100000410000000757014136373754015743 0ustar www-datawww-datamodule Oj # A Simple Callback Parser (SCP) for JSON. The Oj::ScHandler class should be # subclassed and then used with the Oj.sc_parse() method. The Scp methods will # then be called as the file is parsed. The handler does not have to be a # subclass of the ScHandler class as long as it responds to the desired # methods. # # @example # # require 'oj' # # class MyHandler < ::Oj::ScHandler # def hash_start # {} # end # # def hash_set(h,k,v) # h[k] = v # end # # def array_start # [] # end # # def array_append(a,v) # a << v # end # # def add_value(v) # p v # end # # def error(message, line, column) # p "ERROR: #{message}" # end # end # # File.open('any.json', 'r') do |f| # Oj.sc_parse(MyHandler.new, f) # end # # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. # # def hash_start(); end # def hash_end(); end # def hash_key(key); end # def hash_set(h, key, value); end # def array_start(); end # def array_end(); end # def array_append(a, value); end # def add_value(value); end # # As certain elements of a JSON document are reached during parsing the # callbacks are called. The parser helps by keeping track of objects created # by the callbacks but does not create those objects itself. # # hash_start # # When a JSON object element starts the hash_start() callback is called if # public. It should return what ever Ruby Object is to be used as the element # that will later be included in the hash_set() callback. # # hash_end # # When a hash key is encountered the hash_key method is called with the parsed # hash value key. The return value from the call is then used as the key in # the key-value pair that follows. # # hash_key # # At the end of a JSON object element the hash_end() callback is called if public. # # hash_set # # When a key value pair is encountered during parsing the hash_set() callback # is called if public. The first element will be the object returned from the # enclosing hash_start() callback. The second argument is the key and the last # is the value. # # array_start # # When a JSON array element is started the array_start() callback is called if # public. It should return what ever Ruby Object is to be used as the element # that will later be included in the array_append() callback. # # array_end # # At the end of a JSON array element the array_end() callback is called if public. # # array_append # # When a element is encountered that is an element of an array the # array_append() callback is called if public. The first argument to the # callback is the Ruby object returned from the enclosing array_start() # callback. # # add_value # # The handler is expected to handle multiple JSON elements in one stream, # file, or string. When a top level JSON has been read completely the # add_value() callback is called. Even if only one element was ready this # callback returns the Ruby object that was constructed during the parsing. # class ScHandler # Create a new instance of the ScHandler class. def initialize() end # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. private def hash_start() end def hash_end() end def hash_key(key) key end def hash_set(h, key, value) end def array_start() end def array_end() end def add_value(value) end def array_append(a, value) end end # ScHandler end # Oj oj-3.13.9/lib/oj/error.rb0000644000004100000410000000116714136373754015125 0ustar www-datawww-data module Oj # Inherit Error class from StandardError. Error = Class.new(StandardError) # Following classes inherit from the Error class. # ----------------------------------------------- # An Exception that is raised as a result of a parse error while parsing a JSON document. ParseError = Class.new(Error) # An Exception that is raised as a result of a path being too deep. DepthError = Class.new(Error) # An Exception that is raised if a file fails to load. LoadError = Class.new(Error) # An Exception that is raised if there is a conflict with mimicking JSON MimicError = Class.new(Error) end # Oj oj-3.13.9/lib/oj/json.rb0000644000004100000410000001301714136373754014742 0ustar www-datawww-data require 'ostruct' require 'oj/state' module JSON NaN = 0.0/0.0 unless defined?(::JSON::NaN) Infinity = 1.0/0.0 unless defined?(::JSON::Infinity) MinusInfinity = -1.0/0.0 unless defined?(::JSON::MinusInfinity) # Taken from the unit test. Note that items like check_circular? are not # present. PRETTY_STATE_PROTOTYPE = Ext::Generator::State.from_state({ :allow_nan => false, :array_nl => "\n", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => " ", :max_nesting => 100, :object_nl => "\n", :space => " ", :space_before => "", }) unless defined?(::JSON::PRETTY_STATE_PROTOTYPE) SAFE_STATE_PROTOTYPE = Ext::Generator::State.from_state({ :allow_nan => false, :array_nl => "", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => "", :max_nesting => 100, :object_nl => "", :space => "", :space_before => "", }) unless defined?(::JSON::SAFE_STATE_PROTOTYPE) FAST_STATE_PROTOTYPE = Ext::Generator::State.from_state({ :allow_nan => false, :array_nl => "", :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :indent => "", :max_nesting => 0, :object_nl => "", :space => "", :space_before => "", }) unless defined?(::JSON::FAST_STATE_PROTOTYPE) def self.dump_default_options Oj::MimicDumpOption.new end def self.dump_default_options=(h) m = Oj::MimicDumpOption.new h.each do |k,v| m[k] = v end end def self.parser=(p) @@parser = p end def self.parser() @@parser end def self.generator=(g) @@generator = g end def self.generator() @@generator end module Ext class Parser def initialize(src) raise TypeError.new("already initialized") unless @source.nil? @source = src end def source() raise TypeError.new("already initialized") if @source.nil? @source end def parse() raise TypeError.new("already initialized") if @source.nil? JSON.parse(@source) end end # Parser end # Ext State = ::JSON::Ext::Generator::State unless defined?(::JSON::State) begin send(:remove_const, :Parser) rescue end Parser = ::JSON::Ext::Parser unless defined?(::JSON::Parser) self.parser = ::JSON::Ext::Parser self.generator = ::JSON::Ext::Generator # Taken directly from the json gem. Shamelessly copied. It is similar in # some ways to the Oj::Bag class or the Oj::EasyHash class. class GenericObject < OpenStruct class << self alias [] new def json_creatable? @json_creatable end attr_writer :json_creatable def json_create(data) data = data.dup data.delete JSON.create_id self[data] end def from_hash(object) case when object.respond_to?(:to_hash) result = new object.to_hash.each do |key, value| result[key] = from_hash(value) end result when object.respond_to?(:to_ary) object.to_ary.map { |a| from_hash(a) } else object end end def load(source, proc = nil, opts = {}) result = ::JSON.load(source, proc, opts.merge(:object_class => self)) result.nil? ? new : result end def dump(obj, *args) ::JSON.dump(obj, *args) end end # self self.json_creatable = false def to_hash table end def [](name) __send__(name) end unless method_defined?(:[]) def []=(name, value) __send__("#{name}=", value) end unless method_defined?(:[]=) def |(other) self.class[other.to_hash.merge(to_hash)] end def as_json(*) { JSON.create_id => self.class.name }.merge to_hash end def to_json(*a) as_json.to_json(*a) end end end # JSON oj-3.13.9/lib/oj/saj.rb0000644000004100000410000000277414136373754014556 0ustar www-datawww-datamodule Oj # A SAX style parse handler for JSON hence the acronym SAJ for Simple API for # JSON. The Oj::Saj handler class should be subclassed and then used with the # Oj::Saj key_parse() method. The Saj methods will then be called as the file # is parsed. # # @example # # require 'oj' # # class MySaj < ::Oj::Saj # def initialize() # @hash_cnt = 0 # end # # def hash_start(key) # @hash_cnt += 1 # end # end # # cnt = MySaj.new() # File.open('any.json', 'r') do |f| # Oj.saj_parse(cnt, f) # end # # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. # # def hash_start(key); end # def hash_end(key); end # def array_start(key); end # def array_end(key); end # def add_value(value, key); end # def error(message, line, column); end # class Saj # Create a new instance of the Saj handler class. def initialize() end # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. private def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end def error(message, line, column) end end # Saj end # Oj oj-3.13.9/lib/oj/state.rb0000644000004100000410000001026114136373754015107 0ustar www-datawww-data module JSON module Ext module Generator unless defined?(::JSON::Ext::Generator::State) # This class exists for json gem compatibility only. While it can be # used as the options for other than compatibility a simple Hash is # recommended as it is simpler and performs better. The only bit # missing by not using a state object is the depth availability which # may be the depth during dumping or maybe not since it can be set and # the docs for depth= is the same as max_nesting. Note: Had to make # this a subclass of Object instead of Hash like EashyHash due to # conflicts with the json gem. class State def self.from_state(opts) s = self.new() s.clear() s.merge(opts) s end def initialize(opts = {}) @attrs = {} # Populate with all vars then merge in opts. This class deviates from # the json gem in that any of the options can be set with the opts # argument. The json gem limits the opts use to 7 of the options. @attrs[:indent] = '' @attrs[:space] = '' @attrs[:space_before] = '' @attrs[:array_nl] = '' @attrs[:object_nl] = '' @attrs[:allow_nan] = false @attrs[:buffer_initial_length] = 1024 # completely ignored by Oj @attrs[:depth] = 0 @attrs[:max_nesting] = 100 @attrs[:check_circular?] = true @attrs[:ascii_only] = false @attrs.merge!(opts) end def to_h() return @attrs.dup end def to_hash() return @attrs.dup end def allow_nan?() @attrs[:allow_nan] end def ascii_only?() @attrs[:ascii_only] end def configure(opts) raise TypeError.new('expected a Hash') unless opts.respond_to?(:to_h) @attrs.merge!(opts.to_h) end def generate(obj) JSON.generate(obj) end def merge(opts) @attrs.merge!(opts) end # special rule for this. def buffer_initial_length=(len) len = 1024 if 0 >= len @attrs[:buffer_initial_length] = len end # Replaces the Object.respond_to?() method. # @param [Symbol] m method symbol # @return [Boolean] true for any method that matches an instance # variable reader, otherwise false. def respond_to?(m) return true if super return true if has_key?(key) return true if has_key?(key.to_s) has_key?(key.to_sym) end def [](key) key = key.to_sym @attrs.fetch(key, nil) end def []=(key, value) key = key.to_sym @attrs[key] = value end def clear() @attrs.clear() end def has_key?(k) @attrs.has_key?(key.to_sym) end # Handles requests for Hash values. Others cause an Exception to be raised. # @param [Symbol|String] m method symbol # @return [Boolean] the value of the specified instance variable. # @raise [ArgumentError] if an argument is given. Zero arguments expected. # @raise [NoMethodError] if the instance variable is not defined. def method_missing(m, *args, &block) if m.to_s.end_with?('=') raise ArgumentError.new("wrong number of arguments (#{args.size} for 1 with #{m}) to method #{m}") if args.nil? or 1 != args.length m = m.to_s[0..-2] m = m.to_sym return @attrs.store(m, args[0]) end if @attrs.has_key?(m.to_sym) raise ArgumentError.new("wrong number of arguments (#{args.size} for 0 with #{m}) to method #{m}") unless args.nil? or args.empty? return @attrs[m.to_sym] end return @attrs.send(m, *args, &block) end end # State end # defined check end # Generator end # Ext end # JSON oj-3.13.9/lib/oj/bag.rb0000644000004100000410000000661114136373754014524 0ustar www-datawww-data# frozen_string_literal: true module Oj # A generic class that is used only for storing attributes. It is the base # Class for auto-generated classes in the storage system. Instance variables # are added using the instance_variable_set() method. All instance variables # can be accessed using the variable name (without the @ prefix). No setters # are provided as the Class is intended for reading only. class Bag # The initializer can take multiple arguments in the form of key values # where the key is the variable name and the value is the variable # value. This is intended for testing purposes only. # @example Oj::Bag.new(:@x => 42, :@y => 57) # @param [Hash] args instance variable symbols and their values def initialize(args = {}) args.each do |k,v| self.instance_variable_set(k, v) end end # Replaces the Object.respond_to?() method. # @param [Symbol] m method symbol # @return [Boolean] true for any method that matches an instance # variable reader, otherwise false. def respond_to?(m) return true if super instance_variables.include?(:"@#{m}") end # Handles requests for variable values. Others cause an Exception to be # raised. # @param [Symbol] m method symbol # @return [Boolean] the value of the specified instance variable. # @raise [ArgumentError] if an argument is given. Zero arguments expected. # @raise [NoMethodError] if the instance variable is not defined. def method_missing(m, *args, &block) raise ArgumentError.new("wrong number of arguments (#{args.size} for 0) to method #{m}") unless args.nil? or args.empty? at_m = :"@#{m}" raise NoMethodError.new("undefined method #{m}", m) unless instance_variable_defined?(at_m) instance_variable_get(at_m) end # Replaces eql?() with something more reasonable for this Class. # @param [Object] other Object to compare self to # @return [Boolean] true if each variable and value are the same, otherwise false. def eql?(other) return false if (other.nil? or self.class != other.class) ova = other.instance_variables iv = instance_variables return false if ova.size != iv.size iv.all? { |vid| instance_variable_get(vid) != other.instance_variable_get(vid) } end alias == eql? # Define a new class based on the Oj::Bag class. This is used internally in # the Oj module and is available to service wrappers that receive XML # requests that include Objects of Classes not defined in the storage # process. # @param [String] classname Class name or symbol that includes Module names. # @return [Object] an instance of the specified Class. # @raise [NameError] if the classname is invalid. def self.define_class(classname) classname = classname.to_s unless classname.is_a?(String) tokens = classname.split('::').map(&:to_sym) raise NameError.new("Invalid classname '#{classname}") if tokens.empty? m = Object tokens[0..-2].each do |sym| if m.const_defined?(sym) m = m.const_get(sym) else c = Module.new m.const_set(sym, c) m = c end end sym = tokens[-1] if m.const_defined?(sym) c = m.const_get(sym) else c = Class.new(Oj::Bag) m.const_set(sym, c) end c end end # Bag end # Oj oj-3.13.9/lib/oj/active_support_helper.rb0000644000004100000410000000202514136373754020374 0ustar www-datawww-data require 'active_support/time' module Oj # Exists only to handle the ActiveSupport::TimeWithZone. class ActiveSupportHelper def self.createTimeWithZone(utc, zone) ActiveSupport::TimeWithZone.new(utc - utc.gmt_offset, ActiveSupport::TimeZone[zone]) end end end Oj.register_odd(ActiveSupport::TimeWithZone, Oj::ActiveSupportHelper, :createTimeWithZone, :utc, 'time_zone.name') # This is a hack to work around an oddness with DateTime and the ActiveSupport # that causes a hang when some methods are called from C. Hour, min(ute), # sec(ond) and other methods are special but they can be called from C until # activesupport/time is required. After that they can not be even though # resond_to? returns true. By defining methods to call super the problem goes # away. There is obviously some magic going on under the covers that I don't # understand. class DateTime def hour() super end def min() super end def sec() super end def sec_fraction() super end def offset() super end end oj-3.13.9/lib/oj/easy_hash.rb0000644000004100000410000000403314136373754015733 0ustar www-datawww-data module Oj # A Hash subclass that normalizes the hash keys to allow lookup by the # key.to_s or key.to_sym. It also supports looking up hash values by methods # that match the keys. class EasyHash < Hash # Initializes the instance to an empty Hash. def initialize() end # Replaces the Object.respond_to?() method. # @param [Symbol] m method symbol # @param [Boolean] include_all whether to include private and protected methods in the search # @return [Boolean] true for any method that matches an instance # variable reader, otherwise false. def respond_to?(m, include_all = false) return true if super return true if has_key?(m) return true if has_key?(m.to_s) has_key?(m.to_sym) end def [](key) return fetch(key, nil) if has_key?(key) return fetch(key.to_s, nil) if has_key?(key.to_s) fetch(key.to_sym, nil) end # Handles requests for Hash values. Others cause an Exception to be raised. # @param [Symbol|String] m method symbol # @return [Boolean] the value of the specified instance variable. # @raise [ArgumentError] if an argument is given. Zero arguments expected. # @raise [NoMethodError] if the instance variable is not defined. def method_missing(m, *args, &block) if m.to_s.end_with?('=') raise ArgumentError.new("wrong number of arguments (#{args.size} for 1 with #{m}) to method #{m}") if args.nil? or 1 != args.length m = m[0..-2] return store(m.to_s, args[0]) if has_key?(m.to_s) return store(m.to_sym, args[0]) if has_key?(m.to_sym) return store(m, args[0]) else raise ArgumentError.new("wrong number of arguments (#{args.size} for 0 with #{m}) to method #{m}") unless args.nil? or args.empty? return fetch(m, nil) if has_key?(m) return fetch(m.to_s, nil) if has_key?(m.to_s) return fetch(m.to_sym, nil) if has_key?(m.to_sym) end raise NoMethodError.new("undefined method #{m}", m) end end # EasyHash end # Oj oj-3.13.9/lib/oj.rb0000644000004100000410000000027214136373754013770 0ustar www-datawww-data module Oj end require 'oj/version' require 'oj/bag' require 'oj/easy_hash' require 'oj/error' require 'oj/mimic' require 'oj/saj' require 'oj/schandler' require 'oj/oj' # C extension oj-3.13.9/oj.gemspec0000644000004100000410000002731214136373754014246 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: oj 3.13.9 ruby lib # stub: ext/oj/extconf.rb Gem::Specification.new do |s| s.name = "oj".freeze s.version = "3.13.9" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/ohler55/oj/issues", "changelog_uri" => "https://github.com/ohler55/oj/blob/master/CHANGELOG.md", "documentation_uri" => "http://www.ohler.com/oj/doc/index.html", "homepage_uri" => "http://www.ohler.com/oj/", "source_code_uri" => "https://github.com/ohler55/oj", "wiki_uri" => "https://github.com/ohler55/oj/wiki" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Peter Ohler".freeze] s.date = "2021-10-06" s.description = "The fastest JSON parser and object serializer.".freeze s.email = "peter@ohler.com".freeze s.extensions = ["ext/oj/extconf.rb".freeze] s.extra_rdoc_files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "RELEASE_NOTES.md".freeze, "pages/Advanced.md".freeze, "pages/Compatibility.md".freeze, "pages/Custom.md".freeze, "pages/Encoding.md".freeze, "pages/JsonGem.md".freeze, "pages/Modes.md".freeze, "pages/Options.md".freeze, "pages/Parser.md".freeze, "pages/Rails.md".freeze, "pages/Security.md".freeze, "pages/WAB.md".freeze] s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "RELEASE_NOTES.md".freeze, "ext/oj/buf.h".freeze, "ext/oj/cache.c".freeze, "ext/oj/cache.h".freeze, "ext/oj/cache8.c".freeze, "ext/oj/cache8.h".freeze, "ext/oj/circarray.c".freeze, "ext/oj/circarray.h".freeze, "ext/oj/code.c".freeze, "ext/oj/code.h".freeze, "ext/oj/compat.c".freeze, "ext/oj/custom.c".freeze, "ext/oj/debug.c".freeze, "ext/oj/dump.c".freeze, "ext/oj/dump.h".freeze, "ext/oj/dump_compat.c".freeze, "ext/oj/dump_leaf.c".freeze, "ext/oj/dump_object.c".freeze, "ext/oj/dump_strict.c".freeze, "ext/oj/encode.h".freeze, "ext/oj/err.c".freeze, "ext/oj/err.h".freeze, "ext/oj/extconf.rb".freeze, "ext/oj/fast.c".freeze, "ext/oj/intern.c".freeze, "ext/oj/intern.h".freeze, "ext/oj/mimic_json.c".freeze, "ext/oj/object.c".freeze, "ext/oj/odd.c".freeze, "ext/oj/odd.h".freeze, "ext/oj/oj.c".freeze, "ext/oj/oj.h".freeze, "ext/oj/parse.c".freeze, "ext/oj/parse.h".freeze, "ext/oj/parser.c".freeze, "ext/oj/parser.h".freeze, "ext/oj/rails.c".freeze, "ext/oj/rails.h".freeze, "ext/oj/reader.c".freeze, "ext/oj/reader.h".freeze, "ext/oj/resolve.c".freeze, "ext/oj/resolve.h".freeze, "ext/oj/rxclass.c".freeze, "ext/oj/rxclass.h".freeze, "ext/oj/saj.c".freeze, "ext/oj/saj2.c".freeze, "ext/oj/scp.c".freeze, "ext/oj/sparse.c".freeze, "ext/oj/stream_writer.c".freeze, "ext/oj/strict.c".freeze, "ext/oj/string_writer.c".freeze, "ext/oj/trace.c".freeze, "ext/oj/trace.h".freeze, "ext/oj/usual.c".freeze, "ext/oj/util.c".freeze, "ext/oj/util.h".freeze, "ext/oj/val_stack.c".freeze, "ext/oj/val_stack.h".freeze, "ext/oj/validate.c".freeze, "ext/oj/wab.c".freeze, "lib/oj.rb".freeze, "lib/oj/active_support_helper.rb".freeze, "lib/oj/bag.rb".freeze, "lib/oj/easy_hash.rb".freeze, "lib/oj/error.rb".freeze, "lib/oj/json.rb".freeze, "lib/oj/mimic.rb".freeze, "lib/oj/saj.rb".freeze, "lib/oj/schandler.rb".freeze, "lib/oj/state.rb".freeze, "lib/oj/version.rb".freeze, "pages/Advanced.md".freeze, "pages/Compatibility.md".freeze, "pages/Custom.md".freeze, "pages/Encoding.md".freeze, "pages/JsonGem.md".freeze, "pages/Modes.md".freeze, "pages/Options.md".freeze, "pages/Parser.md".freeze, "pages/Rails.md".freeze, "pages/Security.md".freeze, "pages/WAB.md".freeze, "test/_test_active.rb".freeze, "test/_test_active_mimic.rb".freeze, "test/_test_mimic_rails.rb".freeze, "test/activerecord/result_test.rb".freeze, "test/activesupport4/decoding_test.rb".freeze, "test/activesupport4/encoding_test.rb".freeze, "test/activesupport4/test_helper.rb".freeze, "test/activesupport5/abstract_unit.rb".freeze, "test/activesupport5/decoding_test.rb".freeze, "test/activesupport5/encoding_test.rb".freeze, "test/activesupport5/encoding_test_cases.rb".freeze, "test/activesupport5/test_helper.rb".freeze, "test/activesupport5/time_zone_test_helpers.rb".freeze, "test/activesupport6/abstract_unit.rb".freeze, "test/activesupport6/decoding_test.rb".freeze, "test/activesupport6/encoding_test.rb".freeze, "test/activesupport6/encoding_test_cases.rb".freeze, "test/activesupport6/test_common.rb".freeze, "test/activesupport6/test_helper.rb".freeze, "test/activesupport6/time_zone_test_helpers.rb".freeze, "test/bar.rb".freeze, "test/baz.rb".freeze, "test/bug.rb".freeze, "test/files.rb".freeze, "test/foo.rb".freeze, "test/helper.rb".freeze, "test/isolated/shared.rb".freeze, "test/isolated/test_mimic_after.rb".freeze, "test/isolated/test_mimic_alone.rb".freeze, "test/isolated/test_mimic_as_json.rb".freeze, "test/isolated/test_mimic_before.rb".freeze, "test/isolated/test_mimic_define.rb".freeze, "test/isolated/test_mimic_rails_after.rb".freeze, "test/isolated/test_mimic_rails_before.rb".freeze, "test/isolated/test_mimic_redefine.rb".freeze, "test/json_gem/json_addition_test.rb".freeze, "test/json_gem/json_common_interface_test.rb".freeze, "test/json_gem/json_encoding_test.rb".freeze, "test/json_gem/json_ext_parser_test.rb".freeze, "test/json_gem/json_fixtures_test.rb".freeze, "test/json_gem/json_generator_test.rb".freeze, "test/json_gem/json_generic_object_test.rb".freeze, "test/json_gem/json_parser_test.rb".freeze, "test/json_gem/json_string_matching_test.rb".freeze, "test/json_gem/test_helper.rb".freeze, "test/mem.rb".freeze, "test/perf.rb".freeze, "test/perf_compat.rb".freeze, "test/perf_fast.rb".freeze, "test/perf_file.rb".freeze, "test/perf_object.rb".freeze, "test/perf_once.rb".freeze, "test/perf_parser.rb".freeze, "test/perf_saj.rb".freeze, "test/perf_scp.rb".freeze, "test/perf_simple.rb".freeze, "test/perf_strict.rb".freeze, "test/perf_wab.rb".freeze, "test/prec.rb".freeze, "test/sample.rb".freeze, "test/sample/change.rb".freeze, "test/sample/dir.rb".freeze, "test/sample/doc.rb".freeze, "test/sample/file.rb".freeze, "test/sample/group.rb".freeze, "test/sample/hasprops.rb".freeze, "test/sample/layer.rb".freeze, "test/sample/line.rb".freeze, "test/sample/oval.rb".freeze, "test/sample/rect.rb".freeze, "test/sample/shape.rb".freeze, "test/sample/text.rb".freeze, "test/sample_json.rb".freeze, "test/test_compat.rb".freeze, "test/test_custom.rb".freeze, "test/test_debian.rb".freeze, "test/test_fast.rb".freeze, "test/test_file.rb".freeze, "test/test_gc.rb".freeze, "test/test_generate.rb".freeze, "test/test_hash.rb".freeze, "test/test_integer_range.rb".freeze, "test/test_null.rb".freeze, "test/test_object.rb".freeze, "test/test_parser.rb".freeze, "test/test_parser_saj.rb".freeze, "test/test_parser_usual.rb".freeze, "test/test_rails.rb".freeze, "test/test_saj.rb".freeze, "test/test_scp.rb".freeze, "test/test_strict.rb".freeze, "test/test_various.rb".freeze, "test/test_wab.rb".freeze, "test/test_writer.rb".freeze, "test/tests.rb".freeze, "test/tests_mimic.rb".freeze, "test/tests_mimic_addition.rb".freeze, "test/zoo.rb".freeze] s.homepage = "http://www.ohler.com/oj".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--title".freeze, "Oj".freeze, "--main".freeze, "README.md".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.4".freeze) s.rubygems_version = "2.7.6.2".freeze s.summary = "A fast JSON parser and serializer.".freeze s.test_files = ["test/_test_active.rb".freeze, "test/_test_active_mimic.rb".freeze, "test/_test_mimic_rails.rb".freeze, "test/activerecord/result_test.rb".freeze, "test/activesupport4/decoding_test.rb".freeze, "test/activesupport4/encoding_test.rb".freeze, "test/activesupport4/test_helper.rb".freeze, "test/activesupport5/abstract_unit.rb".freeze, "test/activesupport5/decoding_test.rb".freeze, "test/activesupport5/encoding_test.rb".freeze, "test/activesupport5/encoding_test_cases.rb".freeze, "test/activesupport5/test_helper.rb".freeze, "test/activesupport5/time_zone_test_helpers.rb".freeze, "test/activesupport6/abstract_unit.rb".freeze, "test/activesupport6/decoding_test.rb".freeze, "test/activesupport6/encoding_test.rb".freeze, "test/activesupport6/encoding_test_cases.rb".freeze, "test/activesupport6/test_common.rb".freeze, "test/activesupport6/test_helper.rb".freeze, "test/activesupport6/time_zone_test_helpers.rb".freeze, "test/bar.rb".freeze, "test/baz.rb".freeze, "test/bug.rb".freeze, "test/files.rb".freeze, "test/foo.rb".freeze, "test/helper.rb".freeze, "test/isolated/shared.rb".freeze, "test/isolated/test_mimic_after.rb".freeze, "test/isolated/test_mimic_alone.rb".freeze, "test/isolated/test_mimic_as_json.rb".freeze, "test/isolated/test_mimic_before.rb".freeze, "test/isolated/test_mimic_define.rb".freeze, "test/isolated/test_mimic_rails_after.rb".freeze, "test/isolated/test_mimic_rails_before.rb".freeze, "test/isolated/test_mimic_redefine.rb".freeze, "test/json_gem/json_addition_test.rb".freeze, "test/json_gem/json_common_interface_test.rb".freeze, "test/json_gem/json_encoding_test.rb".freeze, "test/json_gem/json_ext_parser_test.rb".freeze, "test/json_gem/json_fixtures_test.rb".freeze, "test/json_gem/json_generator_test.rb".freeze, "test/json_gem/json_generic_object_test.rb".freeze, "test/json_gem/json_parser_test.rb".freeze, "test/json_gem/json_string_matching_test.rb".freeze, "test/json_gem/test_helper.rb".freeze, "test/mem.rb".freeze, "test/perf.rb".freeze, "test/perf_compat.rb".freeze, "test/perf_fast.rb".freeze, "test/perf_file.rb".freeze, "test/perf_object.rb".freeze, "test/perf_once.rb".freeze, "test/perf_parser.rb".freeze, "test/perf_saj.rb".freeze, "test/perf_scp.rb".freeze, "test/perf_simple.rb".freeze, "test/perf_strict.rb".freeze, "test/perf_wab.rb".freeze, "test/prec.rb".freeze, "test/sample.rb".freeze, "test/sample/change.rb".freeze, "test/sample/dir.rb".freeze, "test/sample/doc.rb".freeze, "test/sample/file.rb".freeze, "test/sample/group.rb".freeze, "test/sample/hasprops.rb".freeze, "test/sample/layer.rb".freeze, "test/sample/line.rb".freeze, "test/sample/oval.rb".freeze, "test/sample/rect.rb".freeze, "test/sample/shape.rb".freeze, "test/sample/text.rb".freeze, "test/sample_json.rb".freeze, "test/test_compat.rb".freeze, "test/test_custom.rb".freeze, "test/test_debian.rb".freeze, "test/test_fast.rb".freeze, "test/test_file.rb".freeze, "test/test_gc.rb".freeze, "test/test_generate.rb".freeze, "test/test_hash.rb".freeze, "test/test_integer_range.rb".freeze, "test/test_null.rb".freeze, "test/test_object.rb".freeze, "test/test_parser.rb".freeze, "test/test_parser_saj.rb".freeze, "test/test_parser_usual.rb".freeze, "test/test_rails.rb".freeze, "test/test_saj.rb".freeze, "test/test_scp.rb".freeze, "test/test_strict.rb".freeze, "test/test_various.rb".freeze, "test/test_wab.rb".freeze, "test/test_writer.rb".freeze, "test/tests.rb".freeze, "test/tests_mimic.rb".freeze, "test/tests_mimic_addition.rb".freeze, "test/zoo.rb".freeze] if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 5"]) s.add_development_dependency(%q.freeze, ["< 2.0", ">= 0.9"]) s.add_development_dependency(%q.freeze, ["~> 3.0"]) s.add_development_dependency(%q.freeze, ["~> 0"]) else s.add_dependency(%q.freeze, ["~> 5"]) s.add_dependency(%q.freeze, ["< 2.0", ">= 0.9"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 0"]) end else s.add_dependency(%q.freeze, ["~> 5"]) s.add_dependency(%q.freeze, ["< 2.0", ">= 0.9"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 0"]) end end oj-3.13.9/ext/0000755000004100000410000000000014136373754013064 5ustar www-datawww-dataoj-3.13.9/ext/oj/0000755000004100000410000000000014136373754013474 5ustar www-datawww-dataoj-3.13.9/ext/oj/strict.c0000644000004100000410000001501314136373754015150 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include "encode.h" #include "err.h" #include "intern.h" #include "oj.h" #include "parse.h" #include "trace.h" VALUE oj_cstr_to_value(const char *str, size_t len, size_t cache_str) { volatile VALUE rstr = Qnil; if (len < cache_str) { rstr = oj_str_intern(str, len); } else { rstr = rb_str_new(str, len); rstr = oj_encode(rstr); } return rstr; } VALUE oj_calc_hash_key(ParseInfo pi, Val parent) { volatile VALUE rkey = parent->key_val; if (Qundef != rkey) { return rkey; } if (Yes != pi->options.cache_keys) { if (Yes == pi->options.sym_key) { rkey = ID2SYM(rb_intern3(parent->key, parent->klen, oj_utf8_encoding)); } else { rkey = rb_str_new(parent->key, parent->klen); rkey = oj_encode(rkey); OBJ_FREEZE(rkey); // frozen when used as a Hash key anyway } return rkey; } if (Yes == pi->options.sym_key) { rkey = oj_sym_intern(parent->key, parent->klen); } else { rkey = oj_str_intern(parent->key, parent->klen); } return rkey; } static void hash_end(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static void array_end(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_array_end(pi, __FILE__, __LINE__); } } static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) { return Qundef; } static void add_value(ParseInfo pi, VALUE val) { if (Yes == pi->options.trace) { oj_trace_parse_call("add_value", pi, __FILE__, __LINE__, val); } pi->stack.head->val = val; } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); pi->stack.head->val = rstr; if (Yes == pi->options.trace) { oj_trace_parse_call("add_string", pi, __FILE__, __LINE__, rstr); } } static void add_num(ParseInfo pi, NumInfo ni) { if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } pi->stack.head->val = oj_num_as_value(ni); if (Yes == pi->options.trace) { oj_trace_parse_call("add_number", pi, __FILE__, __LINE__, pi->stack.head->val); } } static VALUE start_hash(ParseInfo pi) { if (Qnil != pi->options.hash_class) { return rb_class_new_instance(0, NULL, pi->options.hash_class); } if (Yes == pi->options.trace) { oj_trace_parse_in("start_hash", pi, __FILE__, __LINE__); } return rb_hash_new(); } static void hash_set_cstr(ParseInfo pi, Val parent, const char *str, size_t len, const char *orig) { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), rstr); if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rstr); } } static void hash_set_num(ParseInfo pi, Val parent, NumInfo ni) { volatile VALUE v; if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } v = oj_num_as_value(ni); rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), v); if (Yes == pi->options.trace) { oj_trace_parse_call("set_number", pi, __FILE__, __LINE__, v); } } static void hash_set_value(ParseInfo pi, Val parent, VALUE value) { rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), value); if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value); } } static VALUE start_array(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_in("start_array", pi, __FILE__, __LINE__); } return rb_ary_new(); } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); rb_ary_push(stack_peek(&pi->stack)->val, rstr); if (Yes == pi->options.trace) { oj_trace_parse_call("append_string", pi, __FILE__, __LINE__, rstr); } } static void array_append_num(ParseInfo pi, NumInfo ni) { volatile VALUE v; if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } v = oj_num_as_value(ni); rb_ary_push(stack_peek(&pi->stack)->val, v); if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, v); } } static void array_append_value(ParseInfo pi, VALUE value) { rb_ary_push(stack_peek(&pi->stack)->val, value); if (Yes == pi->options.trace) { oj_trace_parse_call("append_value", pi, __FILE__, __LINE__, value); } } void oj_set_strict_callbacks(ParseInfo pi) { pi->start_hash = start_hash; pi->end_hash = hash_end; pi->hash_key = noop_hash_key; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->start_array = start_array; pi->end_array = array_end; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; pi->array_append_value = array_append_value; pi->add_cstr = add_cstr; pi->add_num = add_num; pi->add_value = add_value; pi->expect_value = 1; } VALUE oj_strict_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_strict_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, true); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_strict_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_strict_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, json, len, true); } oj-3.13.9/ext/oj/val_stack.c0000644000004100000410000000472514136373754015617 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "val_stack.h" #include #include "odd.h" #include "oj.h" static void mark(void *ptr) { ValStack stack = (ValStack)ptr; Val v; if (0 == ptr) { return; } #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_lock(&stack->mutex); #else rb_mutex_lock(stack->mutex); rb_gc_mark(stack->mutex); #endif for (v = stack->head; v < stack->tail; v++) { if (Qnil != v->val && Qundef != v->val) { rb_gc_mark(v->val); } if (Qnil != v->key_val && Qundef != v->key_val) { rb_gc_mark(v->key_val); } if (NULL != v->odd_args) { VALUE *a; int i; for (i = v->odd_args->odd->attr_cnt, a = v->odd_args->args; 0 < i; i--, a++) { if (Qnil != *a) { rb_gc_mark(*a); } } } } #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_unlock(&stack->mutex); #else rb_mutex_unlock(stack->mutex); #endif } VALUE oj_stack_init(ValStack stack) { #ifdef HAVE_PTHREAD_MUTEX_INIT int err; if (0 != (err = pthread_mutex_init(&stack->mutex, 0))) { rb_raise(rb_eException, "failed to initialize a mutex. %s", strerror(err)); } #else stack->mutex = rb_mutex_new(); #endif stack->head = stack->base; stack->end = stack->base + sizeof(stack->base) / sizeof(struct _val); stack->tail = stack->head; stack->head->val = Qundef; stack->head->key = NULL; stack->head->key_val = Qundef; stack->head->classname = NULL; stack->head->odd_args = NULL; stack->head->clas = Qundef; stack->head->klen = 0; stack->head->clen = 0; stack->head->next = NEXT_NONE; return Data_Wrap_Struct(oj_cstack_class, mark, 0, stack); } const char *oj_stack_next_string(ValNext n) { switch (n) { case NEXT_ARRAY_NEW: return "array element or close"; case NEXT_ARRAY_ELEMENT: return "array element"; case NEXT_ARRAY_COMMA: return "comma"; case NEXT_HASH_NEW: return "hash pair or close"; case NEXT_HASH_KEY: return "hash key"; case NEXT_HASH_COLON: return "colon"; case NEXT_HASH_VALUE: return "hash value"; case NEXT_HASH_COMMA: return "comma"; case NEXT_NONE: break; default: break; } return "nothing"; } oj-3.13.9/ext/oj/code.c0000644000004100000410000001516614136373754014563 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "code.h" #include "dump.h" inline static VALUE resolve_classname(VALUE mod, const char *classname) { VALUE clas = Qundef; ID ci = rb_intern(classname); if (rb_const_defined_at(mod, ci)) { clas = rb_const_get_at(mod, ci); } return clas; } static VALUE path2class(const char *name) { char class_name[1024]; VALUE clas; char * end = class_name + sizeof(class_name) - 1; char * s; const char *n = name; clas = rb_cObject; for (s = class_name; '\0' != *n; n++) { if (':' == *n) { *s = '\0'; n++; if (':' != *n) { return Qundef; } if (Qundef == (clas = resolve_classname(clas, class_name))) { return Qundef; } s = class_name; } else if (end <= s) { return Qundef; } else { *s++ = *n; } } *s = '\0'; return resolve_classname(clas, class_name); } bool oj_code_dump(Code codes, VALUE obj, int depth, Out out) { VALUE clas = rb_obj_class(obj); Code c = codes; for (; NULL != c->name; c++) { if (Qundef == c->clas) { // indicates not defined continue; } if (Qnil == c->clas) { c->clas = path2class(c->name); } if (clas == c->clas && c->active) { c->encode(obj, depth, out); return true; } } return false; } VALUE oj_code_load(Code codes, VALUE clas, VALUE args) { Code c = codes; for (; NULL != c->name; c++) { if (Qundef == c->clas) { // indicates not defined continue; } if (Qnil == c->clas) { c->clas = path2class(c->name); } if (clas == c->clas) { if (NULL == c->decode) { break; } return c->decode(clas, args); } } return Qnil; } void oj_code_set_active(Code codes, VALUE clas, bool active) { Code c = codes; for (; NULL != c->name; c++) { if (Qundef == c->clas) { // indicates not defined continue; } if (Qnil == c->clas) { c->clas = path2class(c->name); } if (clas == c->clas || Qnil == clas) { c->active = active; if (Qnil != clas) { break; } } } } bool oj_code_has(Code codes, VALUE clas, bool encode) { Code c = codes; for (; NULL != c->name; c++) { if (Qundef == c->clas) { // indicates not defined continue; } if (Qnil == c->clas) { c->clas = path2class(c->name); } if (clas == c->clas) { if (encode) { return c->active && NULL != c->encode; } else { return c->active && NULL != c->decode; } } } return false; } void oj_code_attrs(VALUE obj, Attr attrs, int depth, Out out, bool with_class) { int d2 = depth + 1; int d3 = d2 + 1; size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; const char *classname = rb_obj_classname(obj); size_t len = strlen(classname); size_t size = d2 * out->indent + 10 + len + out->opts->create_id_len + sep_len; bool no_comma = true; assure_size(out, size); *out->cur++ = '{'; if (with_class) { fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, len); out->cur += len; *out->cur++ = '"'; no_comma = false; } size = d3 * out->indent + 2; for (; NULL != attrs->name; attrs++) { assure_size(out, size + attrs->len + sep_len + 2); if (no_comma) { no_comma = false; } else { *out->cur++ = ','; } fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, attrs->name, attrs->len); out->cur += attrs->len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } if (Qundef == attrs->value) { if (Qundef != attrs->time) { switch (out->opts->time_format) { case RubyTime: oj_dump_ruby_time(attrs->time, out); break; case XmlTime: oj_dump_xml_time(attrs->time, out); break; case UnixZTime: oj_dump_time(attrs->time, out, true); break; case UnixTime: default: oj_dump_time(attrs->time, out, false); break; } } else { char buf[32]; char *b = buf + sizeof(buf) - 1; int neg = 0; long num = attrs->num; if (0 > num) { neg = 1; num = -num; } *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } if (neg) { *b = '-'; } else { b++; } } else { *b = '0'; } assure_size(out, (sizeof(buf) - (b - buf))); for (; '\0' != *b; b++) { *out->cur++ = *b; } } } else { oj_dump_compat_val(attrs->value, d3, out, true); } } assure_size(out, depth * out->indent + 2); fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } oj-3.13.9/ext/oj/trace.c0000644000004100000410000000425014136373754014737 0ustar www-datawww-data// Copyright (c) 2018 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "trace.h" #include "parse.h" #define MAX_INDENT 256 static void fill_indent(char *indent, int depth) { if (MAX_INDENT <= depth) { depth = MAX_INDENT - 1; } else if (depth < 0) { depth = 0; } if (0 < depth) { memset(indent, ' ', depth); } indent[depth] = '\0'; } void oj_trace(const char *func, VALUE obj, const char *file, int line, int depth, TraceWhere where) { char fmt[64]; char indent[MAX_INDENT]; depth *= 2; fill_indent(indent, depth); sprintf(fmt, "#0:%%13s:%%3d:Oj:%c:%%%ds %%s %%s\n", where, depth); printf(fmt, file, line, indent, func, rb_obj_classname(obj)); } void oj_trace_parse_call(const char *func, ParseInfo pi, const char *file, int line, VALUE obj) { char fmt[64]; char indent[MAX_INDENT]; int depth = (int)(stack_size(&pi->stack) * 2); fill_indent(indent, depth); sprintf(fmt, "#0:%%13s:%%3d:Oj:-:%%%ds %%s %%s\n", depth); printf(fmt, file, line, indent, func, rb_obj_classname(obj)); } void oj_trace_parse_in(const char *func, ParseInfo pi, const char *file, int line) { char fmt[64]; char indent[MAX_INDENT]; int depth = (int)(stack_size(&pi->stack) * 2); fill_indent(indent, depth); sprintf(fmt, "#0:%%13s:%%3d:Oj:}:%%%ds %%s\n", depth); printf(fmt, file, line, indent, func); } void oj_trace_parse_hash_end(ParseInfo pi, const char *file, int line) { char fmt[64]; char indent[MAX_INDENT]; int depth = (int)(stack_size(&pi->stack) * 2 - 2); Val v = stack_peek(&pi->stack); VALUE obj = v->val; fill_indent(indent, depth); sprintf(fmt, "#0:%%13s:%%3d:Oj:{:%%%ds hash_end %%s\n", depth); printf(fmt, file, line, indent, rb_obj_classname(obj)); } void oj_trace_parse_array_end(ParseInfo pi, const char *file, int line) { char fmt[64]; char indent[MAX_INDENT]; int depth = (int)(stack_size(&pi->stack) * 2); fill_indent(indent, depth); sprintf(fmt, "#0:%%13s:%%3d:Oj:{:%%%ds array_ned\n", depth); printf(fmt, file, line, indent); } oj-3.13.9/ext/oj/parser.h0000644000004100000410000000423214136373754015142 0ustar www-datawww-data// Copyright (c) 2021 Peter Ohler, All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_PARSER_H #define OJ_PARSER_H #include #include #include "buf.h" #define TOP_FUN 0 #define ARRAY_FUN 1 #define OBJECT_FUN 2 typedef uint8_t byte; typedef enum { OJ_NONE = '\0', OJ_NULL = 'n', OJ_TRUE = 't', OJ_FALSE = 'f', OJ_INT = 'i', OJ_DECIMAL = 'd', OJ_BIG = 'b', // indicates parser buf is used OJ_STRING = 's', OJ_OBJECT = 'o', OJ_ARRAY = 'a', } ojType; typedef struct _num { long double dub; int64_t fixnum; // holds all digits uint32_t len; int16_t div; // 10^div int16_t exp; uint8_t shift; // shift of fixnum to get decimal bool neg; bool exp_neg; // for numbers as strings, reuse buf } * Num; struct _ojParser; typedef struct _funcs { void (*add_null)(struct _ojParser *p); void (*add_true)(struct _ojParser *p); void (*add_false)(struct _ojParser *p); void (*add_int)(struct _ojParser *p); void (*add_float)(struct _ojParser *p); void (*add_big)(struct _ojParser *p); void (*add_str)(struct _ojParser *p); void (*open_array)(struct _ojParser *p); void (*close_array)(struct _ojParser *p); void (*open_object)(struct _ojParser *p); void (*close_object)(struct _ojParser *p); } * Funcs; typedef struct _ojParser { const char * map; const char * next_map; int depth; unsigned char stack[1024]; // value data struct _num num; struct _buf key; struct _buf buf; struct _funcs funcs[3]; // indexed by XXX_FUN defines void (*start)(struct _ojParser *p); VALUE (*option)(struct _ojParser *p, const char *key, VALUE value); VALUE (*result)(struct _ojParser *p); void (*free)(struct _ojParser *p); void (*mark)(struct _ojParser *p); void *ctx; VALUE reader; char token[8]; long line; long col; int ri; uint32_t ucode; ojType type; // valType bool just_one; } * ojParser; #endif /* OJ_PARSER_H */ oj-3.13.9/ext/oj/dump_object.c0000644000004100000410000006165714136373754016152 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "dump.h" #include "odd.h" #include "trace.h" static const char hex_chars[17] = "0123456789abcdef"; static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out); static void dump_time(VALUE obj, Out out) { switch (out->opts->time_format) { case RubyTime: case XmlTime: oj_dump_xml_time(obj, out); break; case UnixZTime: oj_dump_time(obj, out, 1); break; case UnixTime: default: oj_dump_time(obj, out, 0); break; } } static void dump_data(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { assure_size(out, 6); *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 't'; *out->cur++ = '"'; *out->cur++ = ':'; dump_time(obj, out); *out->cur++ = '}'; *out->cur = '\0'; } else { if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char * str = RSTRING_PTR(rstr); int len = (int)RSTRING_LEN(rstr); if (No != out->opts->bigdec_as_num) { oj_dump_raw(str, len, out); } else if (0 == strcasecmp("Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len); oj_dump_raw(str, len, out); } else if (0 == strcasecmp("-Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len); oj_dump_raw(str, len, out); } else { oj_dump_cstr(str, len, 0, 0, out); } } else { long id = oj_check_circular(obj, out); if (0 <= id) { dump_obj_attrs(obj, clas, id, depth, out); } } } } static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char * str = RSTRING_PTR(rstr); int len = (int)RSTRING_LEN(rstr); if (0 == strcasecmp("Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len); oj_dump_raw(str, len, out); } else if (0 == strcasecmp("-Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len); oj_dump_raw(str, len, out); } else { oj_dump_raw(str, len, out); } } else { long id = oj_check_circular(obj, out); if (0 <= id) { dump_obj_attrs(obj, clas, id, depth, out); } } } static void dump_class(VALUE obj, int depth, Out out, bool as_ok) { const char *s = rb_class2name(obj); size_t len = strlen(s); assure_size(out, 6); *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'c'; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_cstr(s, len, 0, 0, out); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_array_class(VALUE a, VALUE clas, int depth, Out out) { size_t size; int i, cnt; int d2 = depth + 1; long id = oj_check_circular(a, out); if (id < 0) { return; } if (Qundef != clas && rb_cArray != clas && ObjectMode == out->opts->mode) { dump_obj_attrs(a, clas, 0, depth, out); return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; if (0 < id) { assure_size(out, d2 * out->indent + 16); fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; dump_ulong(id, out); *out->cur++ = '"'; } size = 2; assure_size(out, 2); if (0 == cnt) { *out->cur++ = ']'; } else { if (0 < id) { *out->cur++ = ','; } if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_obj_val(rb_ary_entry(a, i), d2, out); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { // printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, // out->opts->dump_opts->indent); if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static void dump_array(VALUE obj, int depth, Out out, bool as_ok) { dump_array_class(obj, rb_obj_class(obj), depth, out); } static void dump_str_class(VALUE obj, VALUE clas, int depth, Out out) { if (Qundef != clas && rb_cString != clas) { dump_obj_attrs(obj, clas, 0, depth, out); } else { const char *s = RSTRING_PTR(obj); size_t len = (int)RSTRING_LEN(obj); char s1 = s[1]; oj_dump_cstr(s, len, 0, (':' == *s || ('^' == *s && ('r' == s1 || 'i' == s1))), out); } } static void dump_str(VALUE obj, int depth, Out out, bool as_ok) { dump_str_class(obj, rb_obj_class(obj), depth, out); } static void dump_sym(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE s = rb_sym2str(obj); oj_dump_cstr(RSTRING_PTR(s), (int)RSTRING_LEN(s), 1, 0, out); } static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; long size = depth * out->indent + 1; if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } assure_size(out, size); fill_indent(out, depth); if (rb_type(key) == T_STRING) { dump_str_class(key, Qundef, depth, out); *out->cur++ = ':'; oj_dump_obj_val(value, depth, out); } else if (rb_type(key) == T_SYMBOL) { dump_sym(key, 0, out, false); *out->cur++ = ':'; oj_dump_obj_val(value, depth, out); } else { int d2 = depth + 1; long s2 = size + out->indent + 1; int i; int started = 0; uint8_t b; assure_size(out, s2 + 15); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = '#'; out->hash_cnt++; for (i = 28; 0 <= i; i -= 4) { b = (uint8_t)((out->hash_cnt >> i) & 0x0000000F); if ('\0' != b) { started = 1; } if (started) { *out->cur++ = hex_chars[b]; } } *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; fill_indent(out, d2); oj_dump_obj_val(key, d2, out); assure_size(out, s2); *out->cur++ = ','; fill_indent(out, d2); oj_dump_obj_val(value, d2, out); assure_size(out, size); fill_indent(out, depth); *out->cur++ = ']'; } out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash_class(VALUE obj, VALUE clas, int depth, Out out) { int cnt; size_t size; if (Qundef != clas && rb_cHash != clas) { dump_obj_attrs(obj, clas, 0, depth, out); return; } cnt = (int)RHASH_SIZE(obj); size = depth * out->indent + 2; assure_size(out, 2); if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { long id = oj_check_circular(obj, out); if (0 > id) { return; } *out->cur++ = '{'; if (0 < id) { assure_size(out, size + 16); fill_indent(out, depth + 1); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); *out->cur++ = ','; } out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, size); fill_indent(out, depth); } else { size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1; assure_size(out, size); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } #ifdef HAVE_RB_IVAR_FOREACH static int dump_attr_cb(ID key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; size_t size = depth * out->indent + 1; const char *attr = rb_id2name(key); if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } // Some exceptions such as NoMethodError have an invisible attribute where // the key name is NULL. Not an empty string but NULL. if (NULL == attr) { attr = ""; } else if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) { return ST_CONTINUE; } if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) { return ST_CONTINUE; } assure_size(out, size); fill_indent(out, depth); if ('@' == *attr) { attr++; oj_dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; oj_dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; oj_dump_obj_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } #endif static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { dump_hash_class(obj, rb_obj_class(obj), depth, out); } static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) { ID * idp; AttrGetFunc * fp; volatile VALUE v; const char * name; size_t size; int d2 = depth + 1; assure_size(out, 2); *out->cur++ = '{'; if (Qundef != clas) { const char *class_name = rb_class2name(clas); int clen = (int)strlen(class_name); size = d2 * out->indent + clen + 10; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'O'; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_cstr(class_name, clen, 0, 0, out); *out->cur++ = ','; } if (odd->raw) { v = rb_funcall(obj, *odd->attrs, 0); if (Qundef == v || T_STRING != rb_type(v)) { rb_raise(rb_eEncodingError, "Invalid type for raw JSON."); } else { const char *s = RSTRING_PTR(v); int len = (int)RSTRING_LEN(v); const char *name = rb_id2name(*odd->attrs); size_t nlen = strlen(name); size = len + d2 * out->indent + nlen + 10; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, name, nlen); out->cur += nlen; *out->cur++ = '"'; *out->cur++ = ':'; memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } } else { size = d2 * out->indent + 1; for (idp = odd->attrs, fp = odd->attrFuncs; 0 != *idp; idp++, fp++) { size_t nlen; assure_size(out, size); name = rb_id2name(*idp); nlen = strlen(name); if (0 != *fp) { v = (*fp)(obj); } else if (0 == strchr(name, '.')) { v = rb_funcall(obj, *idp, 0); } else { char nbuf[256]; char *n2 = nbuf; char *n; char *end; ID i; if (sizeof(nbuf) <= nlen) { if (NULL == (n2 = strdup(name))) { rb_raise(rb_eNoMemError, "for attribute name."); } } else { strcpy(n2, name); } n = n2; v = obj; while (0 != (end = strchr(n, '.'))) { *end = '\0'; i = rb_intern(n); v = rb_funcall(v, i, 0); n = end + 1; } i = rb_intern(n); v = rb_funcall(v, i, 0); if (nbuf != n2) { free(n2); } } fill_indent(out, d2); oj_dump_cstr(name, nlen, 0, 0, out); *out->cur++ = ':'; oj_dump_obj_val(v, d2, out); assure_size(out, 2); *out->cur++ = ','; } out->cur--; } *out->cur++ = '}'; *out->cur = '\0'; } static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) { size_t size = 0; int d2 = depth + 1; int type = rb_type(obj); Odd odd; if (0 != (odd = oj_get_odd(clas))) { dump_odd(obj, odd, clas, depth + 1, out); return; } assure_size(out, 2); *out->cur++ = '{'; if (Qundef != clas) { const char *class_name = rb_class2name(clas); int clen = (int)strlen(class_name); assure_size(out, d2 * out->indent + clen + 10); fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'o'; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_cstr(class_name, clen, 0, 0, out); } if (0 < id) { assure_size(out, d2 * out->indent + 16); *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); } switch (type) { case T_STRING: assure_size(out, d2 * out->indent + 14); *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur++ = 'l'; *out->cur++ = 'f'; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_cstr(RSTRING_PTR(obj), (int)RSTRING_LEN(obj), 0, 0, out); break; case T_ARRAY: assure_size(out, d2 * out->indent + 14); *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur++ = 'l'; *out->cur++ = 'f'; *out->cur++ = '"'; *out->cur++ = ':'; dump_array_class(obj, Qundef, depth + 1, out); break; case T_HASH: assure_size(out, d2 * out->indent + 14); *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur++ = 'l'; *out->cur++ = 'f'; *out->cur++ = '"'; *out->cur++ = ':'; dump_hash_class(obj, Qundef, depth + 1, out); break; default: break; } { int cnt; #ifdef HAVE_RB_IVAR_COUNT cnt = (int)rb_ivar_count(obj); #else volatile VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0); VALUE * np = RARRAY_PTR(vars); ID vid; const char * attr; int i; int first = 1; cnt = (int)RARRAY_LEN(vars); #endif if (Qundef != clas && 0 < cnt) { *out->cur++ = ','; } if (0 == cnt && Qundef == clas) { // Might be something special like an Enumerable. if (Qtrue == rb_obj_is_kind_of(obj, oj_enumerable_class)) { out->cur--; oj_dump_obj_val(rb_funcall(obj, rb_intern("entries"), 0), depth, out); return; } } out->depth = depth + 1; #ifdef HAVE_RB_IVAR_FOREACH rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } #else size = d2 * out->indent + 1; for (i = cnt; 0 < i; i--, np++) { VALUE value; vid = rb_to_id(*np); attr = rb_id2name(vid); if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) { continue; } value = rb_ivar_get(obj, vid); if (oj_dump_ignore(out->opts, value)) { continue; } if (out->omit_nil && Qnil == value) { continue; } if (first) { first = 0; } else { *out->cur++ = ','; } assure_size(out, size); fill_indent(out, d2); if ('@' == *attr) { attr++; oj_dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; oj_dump_cstr(buf, strlen(attr) + 1, 0, 0, out); } *out->cur++ = ':'; oj_dump_obj_val(value, d2, out); assure_size(out, 2); } #endif if (rb_obj_is_kind_of(obj, rb_eException)) { volatile VALUE rv; if (',' != *(out->cur - 1)) { *out->cur++ = ','; } // message assure_size(out, size); fill_indent(out, d2); oj_dump_cstr("~mesg", 5, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("message"), 0, 0); oj_dump_obj_val(rv, d2, out); assure_size(out, 2); *out->cur++ = ','; // backtrace assure_size(out, size); fill_indent(out, d2); oj_dump_cstr("~bt", 3, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0); oj_dump_obj_val(rv, d2, out); assure_size(out, 2); } out->depth = depth; } fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) { dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out); } static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); const char *class_name = rb_class2name(clas); int i; int d2 = depth + 1; int d3 = d2 + 1; size_t len = strlen(class_name); size_t size = d2 * out->indent + d3 * out->indent + 10 + len; assure_size(out, size); *out->cur++ = '{'; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'u'; *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; if ('#' == *class_name) { VALUE ma = rb_struct_s_members(clas); const char *name; int cnt = (int)RARRAY_LEN(ma); *out->cur++ = '['; for (i = 0; i < cnt; i++) { volatile VALUE s = rb_sym2str(rb_ary_entry(ma, i)); name = RSTRING_PTR(s); len = (int)RSTRING_LEN(s); size = len + 3; assure_size(out, size); if (0 < i) { *out->cur++ = ','; } *out->cur++ = '"'; memcpy(out->cur, name, len); out->cur += len; *out->cur++ = '"'; } *out->cur++ = ']'; } else { fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, class_name, len); out->cur += len; *out->cur++ = '"'; } *out->cur++ = ','; size = d3 * out->indent + 2; #ifdef RSTRUCT_LEN { VALUE v; int cnt; #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)NUM2LONG(RSTRUCT_LEN(obj)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)RSTRUCT_LEN(obj); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT for (i = 0; i < cnt; i++) { v = RSTRUCT_GET(obj, i); if (oj_dump_ignore(out->opts, v)) { v = Qnil; } assure_size(out, size); fill_indent(out, d3); oj_dump_obj_val(v, d3, out); *out->cur++ = ','; } } #else { // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. int slen = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0)); for (i = 0; i < slen; i++) { assure_size(out, size); fill_indent(out, d3); if (oj_dump_ignore(out->opts, v)) { v = Qnil; } oj_dump_obj_val(rb_struct_aref(obj, INT2FIX(i)), d3, out, 0, 0, true); *out->cur++ = ','; } } #endif out->cur--; *out->cur++ = ']'; *out->cur++ = '}'; *out->cur = '\0'; } static void dump_complex(VALUE obj, int depth, Out out, bool as_ok) { dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out); } static void dump_rational(VALUE obj, int depth, Out out, bool as_ok) { dump_obj_attrs(obj, rb_obj_class(obj), 0, depth, out); } static DumpFunc obj_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, dump_class, // RUBY_T_CLASS = 0x02, dump_class, // RUBY_T_MODULE = 0x03, oj_dump_float, // RUBY_T_FLOAT = 0x04, dump_str, // RUBY_T_STRING = 0x05, dump_regexp, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, dump_struct, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_data, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, dump_complex, // RUBY_T_COMPLEX = 0x0e, dump_rational, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_obj_val(VALUE obj, int depth, Out out) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = obj_funcs[type]; if (NULL != f) { f(obj, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } oj-3.13.9/ext/oj/rails.c0000644000004100000410000013423114136373754014756 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "rails.h" #include "code.h" #include "encode.h" #include "trace.h" #include "util.h" #define OJ_INFINITY (1.0 / 0.0) // TBD keep static array of strings and functions to help with rails optimization typedef struct _encoder { struct _rOptTable ropts; struct _options opts; VALUE arg; } * Encoder; bool oj_rails_hash_opt = false; bool oj_rails_array_opt = false; bool oj_rails_float_opt = false; extern void oj_mimic_json_methods(VALUE json); static void dump_rails_val(VALUE obj, int depth, Out out, bool as_ok); extern VALUE Oj; static struct _rOptTable ropts = {0, 0, NULL}; static VALUE encoder_class = Qnil; static bool escape_html = true; static bool xml_time = true; static ROpt create_opt(ROptTable rot, VALUE clas); ROpt oj_rails_get_opt(ROptTable rot, VALUE clas) { if (NULL == rot) { rot = &ropts; } if (0 < rot->len) { int lo = 0; int hi = rot->len - 1; int mid; VALUE v; if (clas < rot->table->clas || rot->table[hi].clas < clas) { return NULL; } if (rot->table[lo].clas == clas) { return rot->table; } if (rot->table[hi].clas == clas) { return &rot->table[hi]; } while (2 <= hi - lo) { mid = (hi + lo) / 2; v = rot->table[mid].clas; if (v == clas) { return &rot->table[mid]; } if (v < clas) { lo = mid; } else { hi = mid; } } } return NULL; } static ROptTable copy_opts(ROptTable src, ROptTable dest) { dest->len = src->len; dest->alen = src->alen; if (NULL == src->table) { dest->table = NULL; } else { dest->table = ALLOC_N(struct _rOpt, dest->alen); memcpy(dest->table, src->table, sizeof(struct _rOpt) * dest->alen); } return NULL; } static int dump_attr_cb(ID key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; size_t size = depth * out->indent + 1; const char *attr = rb_id2name(key); // Some exceptions such as NoMethodError have an invisible attribute where // the key name is NULL. Not an empty string but NULL. if (NULL == attr) { attr = ""; } if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) { return ST_CONTINUE; } assure_size(out, size); fill_indent(out, depth); if ('@' == *attr) { attr++; oj_dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; oj_dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; dump_rails_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_obj_attrs(VALUE obj, int depth, Out out, bool as_ok) { assure_size(out, 2); *out->cur++ = '{'; out->depth = depth + 1; rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } out->depth = depth; fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) { int d3 = depth + 2; size_t size = d3 * out->indent + 2; size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; volatile VALUE ma; volatile VALUE v; int cnt; int i; int len; const char * name; #ifdef RSTRUCT_LEN #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)NUM2LONG(RSTRUCT_LEN(obj)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)RSTRUCT_LEN(obj); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT #else // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. cnt = FIX2INT(rb_funcall(obj, oj_length_id, 0)); #endif ma = rb_struct_s_members(rb_obj_class(obj)); assure_size(out, 2); *out->cur++ = '{'; for (i = 0; i < cnt; i++) { volatile VALUE s = rb_sym2str(rb_ary_entry(ma, i)); name = RSTRING_PTR(s); len = (int)RSTRING_LEN(s); assure_size(out, size + sep_len + 6); if (0 < i) { *out->cur++ = ','; } fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, name, len); out->cur += len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } #ifdef RSTRUCT_LEN v = RSTRUCT_GET(obj, i); #else v = rb_struct_aref(obj, INT2FIX(i)); #endif dump_rails_val(v, d3, out, true); } fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static ID to_a_id = 0; static void dump_enumerable(VALUE obj, int depth, Out out, bool as_ok) { if (0 == to_a_id) { to_a_id = rb_intern("to_a"); } dump_rails_val(rb_funcall(obj, to_a_id, 0), depth, out, false); } static void dump_bigdecimal(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char * str = RSTRING_PTR(rstr); if ('I' == *str || 'N' == *str || ('-' == *str && 'I' == str[1])) { oj_dump_nil(Qnil, depth, out, false); } else if (out->opts->int_range_max != 0 || out->opts->int_range_min != 0) { oj_dump_cstr(str, (int)RSTRING_LEN(rstr), 0, 0, out); } else if (Yes == out->opts->bigdec_as_num) { oj_dump_raw(str, (int)RSTRING_LEN(rstr), out); } else { oj_dump_cstr(str, (int)RSTRING_LEN(rstr), 0, 0, out); } } static void dump_sec_nano(VALUE obj, int64_t sec, long nsec, Out out) { char buf[64]; struct _timeInfo ti; long one = 1000000000; long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0)); int tzhour, tzmin; char tzsign = '+'; int len; if (out->end - out->cur <= 36) { assure_size(out, 36); } if (9 > out->opts->sec_prec) { int i; // Rails does not round when reducing precision but instead floors, for (i = 9 - out->opts->sec_prec; 0 < i; i--) { nsec = nsec / 10; one /= 10; } if (one <= nsec) { nsec -= one; sec++; } } // 2012-01-05T23:58:07.123456000+09:00 or 2012/01/05 23:58:07 +0900 sec += tzsecs; sec_as_time(sec, &ti); if (0 > tzsecs) { tzsign = '-'; tzhour = (int)(tzsecs / -3600); tzmin = (int)(tzsecs / -60) - (tzhour * 60); } else { tzhour = (int)(tzsecs / 3600); tzmin = (int)(tzsecs / 60) - (tzhour * 60); } if (!xml_time) { len = sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d %c%02d%02d", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, tzsign, tzhour, tzmin); } else if (0 == out->opts->sec_prec) { if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec); } else { len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, tzsign, tzhour, tzmin); } } else if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ"; len = 30; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; len -= 9 - out->opts->sec_prec; } len = sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, nsec); } else { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d"; len = 35; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; len -= 9 - out->opts->sec_prec; } len = sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, nsec, tzsign, tzhour, tzmin); } oj_dump_cstr(buf, len, 0, 0, out); } static void dump_time(VALUE obj, int depth, Out out, bool as_ok) { long long sec; long long nsec; #ifdef HAVE_RB_TIME_TIMESPEC if (16 <= sizeof(struct timespec)) { struct timespec ts = rb_time_timespec(obj); sec = (long long)ts.tv_sec; nsec = ts.tv_nsec; } else { sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } #else sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #endif dump_sec_nano(obj, sec, nsec, out); } static void dump_timewithzone(VALUE obj, int depth, Out out, bool as_ok) { int64_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); long long nsec = 0; if (rb_respond_to(obj, oj_tv_nsec_id)) { nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } else if (rb_respond_to(obj, oj_tv_usec_id)) { nsec = rb_num2ll(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000; } dump_sec_nano(obj, sec, nsec, out); } static void dump_to_s(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), 0, 0, out); } static ID parameters_id = 0; typedef struct _strLen { const char *str; int len; } * StrLen; static void dump_actioncontroller_parameters(VALUE obj, int depth, Out out, bool as_ok) { if (0 == parameters_id) { parameters_id = rb_intern("@parameters"); } out->argc = 0; dump_rails_val(rb_ivar_get(obj, parameters_id), depth, out, true); } static StrLen columns_array(VALUE rcols, int *ccnt) { volatile VALUE v; StrLen cp; StrLen cols; int i; int cnt = (int)RARRAY_LEN(rcols); *ccnt = cnt; cols = ALLOC_N(struct _strLen, cnt); for (i = 0, cp = cols; i < cnt; i++, cp++) { v = rb_ary_entry(rcols, i); if (T_STRING != rb_type(v)) { v = rb_funcall(v, oj_to_s_id, 0); } cp->str = StringValuePtr(v); cp->len = (int)RSTRING_LEN(v); } return cols; } static void dump_row(VALUE row, StrLen cols, int ccnt, int depth, Out out) { size_t size; int d2 = depth + 1; int i; assure_size(out, 2); *out->cur++ = '{'; size = depth * out->indent + 3; for (i = 0; i < ccnt; i++, cols++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_cstr(cols->str, cols->len, 0, 0, out); *out->cur++ = ':'; dump_rails_val(rb_ary_entry(row, i), depth, out, true); if (i < ccnt - 1) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = '}'; } static ID rows_id = 0; static ID columns_id = 0; static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE rows; StrLen cols; int ccnt = 0; int i, rcnt; size_t size; int d2 = depth + 1; if (0 == rows_id) { rows_id = rb_intern("@rows"); columns_id = rb_intern("@columns"); } out->argc = 0; cols = columns_array(rb_ivar_get(obj, columns_id), &ccnt); rows = rb_ivar_get(obj, rows_id); rcnt = (int)RARRAY_LEN(rows); assure_size(out, 2); *out->cur++ = '['; if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } assure_size(out, 2); for (i = 0; i < rcnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } dump_row(rb_ary_entry(rows, i), cols, ccnt, d2, out); if (i < rcnt - 1) { *out->cur++ = ','; } } xfree(cols); size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } typedef struct _namedFunc { const char *name; DumpFunc func; } * NamedFunc; static void dump_as_string(VALUE obj, int depth, Out out, bool as_ok) { if (oj_code_dump(oj_compat_codes, obj, depth, out)) { out->argc = 0; return; } oj_dump_obj_to_s(obj, out); } static void dump_as_json(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE ja; if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } // Some classes elect to not take an options argument so check the arity // of as_json. if (0 == rb_obj_method_arity(obj, oj_as_json_id)) { ja = rb_funcall(obj, oj_as_json_id, 0); } else { ja = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } out->argc = 0; if (ja == obj || !as_ok) { // Once as_json is called it should never be called again on the same // object with as_ok. dump_rails_val(ja, depth, out, false); } else { int type = rb_type(ja); if (T_HASH == type || T_ARRAY == type) { dump_rails_val(ja, depth, out, true); } else { dump_rails_val(ja, depth, out, true); } } } static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) { if (as_ok && rb_respond_to(obj, oj_as_json_id)) { dump_as_json(obj, depth, out, false); return; } dump_as_string(obj, depth, out, as_ok); } static struct _namedFunc dump_map[] = { {"ActionController::Parameters", dump_actioncontroller_parameters}, {"ActiveRecord::Result", dump_activerecord_result}, {"ActiveSupport::TimeWithZone", dump_timewithzone}, {"BigDecimal", dump_bigdecimal}, {"Range", dump_to_s}, {"Regexp", dump_regexp}, //{ "Regexp", dump_to_s }, {"Time", dump_time}, {NULL, NULL}, }; static VALUE activerecord_base = Qundef; static ID attributes_id = 0; static void dump_activerecord(VALUE obj, int depth, Out out, bool as_ok) { if (0 == attributes_id) { attributes_id = rb_intern("@attributes"); } out->argc = 0; dump_rails_val(rb_ivar_get(obj, attributes_id), depth, out, true); } static ROpt create_opt(ROptTable rot, VALUE clas) { ROpt ro; NamedFunc nf; const char *classname = rb_class2name(clas); int olen = rot->len; rot->len++; if (NULL == rot->table) { rot->alen = 256; rot->table = ALLOC_N(struct _rOpt, rot->alen); memset(rot->table, 0, sizeof(struct _rOpt) * rot->alen); } else if (rot->alen <= rot->len) { rot->alen *= 2; REALLOC_N(rot->table, struct _rOpt, rot->alen); memset(rot->table + olen, 0, sizeof(struct _rOpt) * olen); } if (0 == olen) { ro = rot->table; } else if (rot->table[olen - 1].clas < clas) { ro = &rot->table[olen]; } else { int i; for (i = 0, ro = rot->table; i < olen; i++, ro++) { if (clas < ro->clas) { memmove(ro + 1, ro, sizeof(struct _rOpt) * (olen - i)); break; } } } ro->clas = clas; ro->on = true; ro->dump = dump_obj_attrs; for (nf = dump_map; NULL != nf->name; nf++) { if (0 == strcmp(nf->name, classname)) { ro->dump = nf->func; break; } } if (ro->dump == dump_obj_attrs) { if (Qundef == activerecord_base) { // If not defined let an exception be raised. VALUE ar = rb_const_get_at(rb_cObject, rb_intern("ActiveRecord")); if (Qundef != ar) { activerecord_base = rb_const_get_at(ar, rb_intern("Base")); } } if (Qundef != activerecord_base && Qtrue == rb_class_inherited_p(clas, activerecord_base)) { ro->dump = dump_activerecord; } else if (Qtrue == rb_class_inherited_p(clas, rb_cStruct)) { // check before enumerable ro->dump = dump_struct; } else if (Qtrue == rb_class_inherited_p(clas, rb_mEnumerable)) { ro->dump = dump_enumerable; } else if (Qtrue == rb_class_inherited_p(clas, rb_eException)) { ro->dump = dump_to_s; } } return ro; } static void encoder_free(void *ptr) { if (NULL != ptr) { Encoder e = (Encoder)ptr; if (NULL != e->ropts.table) { xfree(e->ropts.table); } xfree(ptr); } } static void encoder_mark(void *ptr) { if (NULL != ptr) { Encoder e = (Encoder)ptr; if (Qnil != e->arg) { rb_gc_mark(e->arg); } } } /* Document-method: new * call-seq: new(options=nil) * * Creates a new Encoder. * - *options* [_Hash_] formatting options */ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) { Encoder e = ALLOC(struct _encoder); e->opts = oj_default_options; e->arg = Qnil; copy_opts(&ropts, &e->ropts); if (1 <= argc && Qnil != *argv) { oj_parse_options(*argv, &e->opts); e->arg = *argv; } return Data_Wrap_Struct(encoder_class, encoder_mark, encoder_free, e); } static VALUE resolve_classpath(const char *name) { char class_name[1024]; VALUE clas; char * end = class_name + sizeof(class_name) - 1; char * s; const char *n = name; ID cid; clas = rb_cObject; for (s = class_name; '\0' != *n; n++) { if (':' == *n) { *s = '\0'; n++; if (':' != *n) { return Qnil; } cid = rb_intern(class_name); if (!rb_const_defined_at(clas, cid)) { return Qnil; } clas = rb_const_get_at(clas, cid); s = class_name; } else if (end <= s) { return Qnil; } else { *s++ = *n; } } *s = '\0'; cid = rb_intern(class_name); if (!rb_const_defined_at(clas, cid)) { return Qnil; } clas = rb_const_get_at(clas, cid); return clas; } static void optimize(int argc, VALUE *argv, ROptTable rot, bool on) { ROpt ro; if (0 == argc) { int i; NamedFunc nf; VALUE clas; oj_rails_hash_opt = on; oj_rails_array_opt = on; oj_rails_float_opt = on; for (nf = dump_map; NULL != nf->name; nf++) { if (Qnil != (clas = resolve_classpath(nf->name))) { if (NULL == oj_rails_get_opt(rot, clas)) { create_opt(rot, clas); } } } for (i = 0; i < rot->len; i++) { rot->table[i].on = on; } } for (; 0 < argc; argc--, argv++) { if (rb_cHash == *argv) { oj_rails_hash_opt = on; } else if (rb_cArray == *argv) { oj_rails_array_opt = on; } else if (rb_cFloat == *argv) { oj_rails_float_opt = on; } else if (oj_string_writer_class == *argv) { string_writer_optimized = on; } else if (NULL != (ro = oj_rails_get_opt(rot, *argv)) || NULL != (ro = create_opt(rot, *argv))) { ro->on = on; } } } /* Document-method optimize * call-seq: optimize(*classes) * * Use Oj rails optimized routines to encode the specified classes. This * ignores the as_json() method on the class and uses an internal encoding * instead. Passing in no classes indicates all should use the optimized * version of encoding for all previously optimized classes. Passing in the * Object class set a global switch that will then use the optimized behavior * for all classes. * * - *classes* [_Class_] a list of classes to optimize */ static VALUE encoder_optimize(int argc, VALUE *argv, VALUE self) { Encoder e = (Encoder)DATA_PTR(self); optimize(argc, argv, &e->ropts, true); return Qnil; } /* Document-method: optimize * call-seq: optimize(*classes) * * Use Oj rails optimized routines to encode the specified classes. This * ignores the as_json() method on the class and uses an internal encoding * instead. Passing in no classes indicates all should use the optimized * version of encoding for all previously optimized classes. Passing in the * Object class set a global switch that will then use the optimized behavior * for all classes. * * - *classes* [_Class_] a list of classes to optimize */ static VALUE rails_optimize(int argc, VALUE *argv, VALUE self) { optimize(argc, argv, &ropts, true); string_writer_optimized = true; return Qnil; } /* Document-module: mimic_JSON * call-seq: mimic_JSON() * * Sets the JSON method to use Oj similar to Oj.mimic_JSON except with the * ActiveSupport monkey patches instead of the json gem monkey patches. */ VALUE rails_mimic_json(VALUE self) { VALUE json; if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) { json = rb_const_get_at(rb_cObject, rb_intern("JSON")); } else { json = rb_define_module("JSON"); } oj_mimic_json_methods(json); // Setting the default mode breaks the prmoise in the docs not to. //oj_default_options.mode = RailsMode; return Qnil; } /* Document-method: deoptimize * call-seq: deoptimize(*classes) * * Turn off Oj rails optimization on the specified classes. * * - *classes* [_Class_] a list of classes to deoptimize */ static VALUE encoder_deoptimize(int argc, VALUE *argv, VALUE self) { Encoder e = (Encoder)DATA_PTR(self); optimize(argc, argv, &e->ropts, false); return Qnil; } /* Document-method: deoptimize * call-seq: deoptimize(*classes) * * Turn off Oj rails optimization on the specified classes. * * - *classes* [_Class_] a list of classes to deoptimize */ static VALUE rails_deoptimize(int argc, VALUE *argv, VALUE self) { optimize(argc, argv, &ropts, false); string_writer_optimized = false; return Qnil; } /* Document-method:optimized? * call-seq: optimized?(clas) * * - *clas* [_Class_] Class to check * * @return true if the class is being optimized for rails and false otherwise */ static VALUE encoder_optimized(VALUE self, VALUE clas) { Encoder e = (Encoder)DATA_PTR(self); ROpt ro = oj_rails_get_opt(&e->ropts, clas); if (NULL == ro) { return Qfalse; } return (ro->on) ? Qtrue : Qfalse; } /* Document-method: optimized? * call-seq: optimized?(clas) * * Returns true if the specified Class is being optimized. */ static VALUE rails_optimized(VALUE self, VALUE clas) { ROpt ro = oj_rails_get_opt(&ropts, clas); if (NULL == ro) { return Qfalse; } return (ro->on) ? Qtrue : Qfalse; } typedef struct _oo { Out out; VALUE obj; } * OO; static VALUE protect_dump(VALUE ov) { OO oo = (OO)ov; dump_rails_val(oo->obj, 0, oo->out, true); return Qnil; } static VALUE encode(VALUE obj, ROptTable ropts, Options opts, int argc, VALUE *argv) { char buf[4096]; struct _out out; struct _options copts = *opts; volatile VALUE rstr = Qnil; struct _oo oo; int line = 0; oo.out = &out; oo.obj = obj; copts.str_rx.head = NULL; copts.str_rx.tail = NULL; copts.mode = RailsMode; if (escape_html) { copts.escape_mode = RailsXEsc; } else { copts.escape_mode = RailsEsc; } out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.omit_nil = copts.dump_opts.omit_nil; out.caller = 0; out.cur = out.buf; out.circ_cnt = 0; out.opts = &copts; out.hash_cnt = 0; out.indent = copts.indent; out.argc = argc; out.argv = argv; out.ropts = ropts; if (Yes == copts.circular) { oj_cache8_new(&out.circ_cache); } // dump_rails_val(*argv, 0, &out, true); rb_protect(protect_dump, (VALUE)&oo, &line); if (0 == line) { if (0 < out.indent) { switch (*(out.cur - 1)) { case ']': case '}': assure_size(&out, 2); *out.cur++ = '\n'; default: break; } } *out.cur = '\0'; if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); } if (Yes == copts.circular) { oj_cache8_delete(out.circ_cache); } if (out.allocated) { xfree(out.buf); } if (0 != line) { rb_jump_tag(line); } return rstr; } /* Document-method: encode * call-seq: encode(obj) * * - *obj* [_Object_] object to encode * * Returns encoded object as a JSON string. */ static VALUE encoder_encode(VALUE self, VALUE obj) { Encoder e = (Encoder)DATA_PTR(self); if (Qnil != e->arg) { VALUE argv[1] = {e->arg}; return encode(obj, &e->ropts, &e->opts, 1, argv); } return encode(obj, &e->ropts, &e->opts, 0, NULL); } /* Document-method: encode * call-seq: encode(obj, opts=nil) * * Encode obj as a JSON String. * * - *obj* [_Object_|Hash|Array] object to convert to a JSON String * - *opts* [_Hash_] options * * Returns [_String_] */ static VALUE rails_encode(int argc, VALUE *argv, VALUE self) { if (1 > argc) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)."); } if (1 == argc) { return encode(*argv, NULL, &oj_default_options, 0, NULL); } else { return encode(*argv, NULL, &oj_default_options, argc - 1, argv + 1); } } static VALUE rails_use_standard_json_time_format(VALUE self, VALUE state) { if (Qtrue == state || Qfalse == state) { // no change needed } else if (Qnil == state) { state = Qfalse; } else { state = Qtrue; } rb_iv_set(self, "@use_standard_json_time_format", state); xml_time = Qtrue == state; return state; } static VALUE rails_use_standard_json_time_format_get(VALUE self) { return xml_time ? Qtrue : Qfalse; } static VALUE rails_escape_html_entities_in_json(VALUE self, VALUE state) { rb_iv_set(self, "@escape_html_entities_in_json", state); escape_html = Qtrue == state; return state; } static VALUE rails_escape_html_entities_in_json_get(VALUE self) { return escape_html ? Qtrue : Qfalse; } static VALUE rails_time_precision(VALUE self, VALUE prec) { rb_iv_set(self, "@time_precision", prec); oj_default_options.sec_prec = NUM2INT(prec); oj_default_options.sec_prec_set = true; return prec; } /* Document-method: set_encoder * call-seq: set_encoder() * * Sets the ActiveSupport.encoder to Oj::Rails::Encoder and wraps some of the * formatting globals used by ActiveSupport to allow the use of those globals * in the Oj::Rails optimizations. */ static VALUE rails_set_encoder(VALUE self) { VALUE active; VALUE json; VALUE encoding; VALUE pv; VALUE verbose; VALUE enc = resolve_classpath("ActiveSupport::JSON::Encoding"); if (Qnil != enc) { escape_html = Qtrue == rb_iv_get(self, "@escape_html_entities_in_json"); xml_time = Qtrue == rb_iv_get(enc, "@use_standard_json_time_format"); } if (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport"))) { active = rb_const_get_at(rb_cObject, rb_intern("ActiveSupport")); } else { rb_raise(rb_eStandardError, "ActiveSupport not loaded."); } rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class); json = rb_const_get_at(active, rb_intern("JSON")); encoding = rb_const_get_at(json, rb_intern("Encoding")); // rb_undef_method doesn't work for modules or maybe sometimes // doesn't. Anyway setting verbose should hide the warning. verbose = rb_gv_get("$VERBOSE"); rb_gv_set("$VERBOSE", Qfalse); rb_undef_method(encoding, "use_standard_json_time_format="); rb_define_module_function(encoding, "use_standard_json_time_format=", rails_use_standard_json_time_format, 1); rb_undef_method(encoding, "use_standard_json_time_format"); rb_define_module_function(encoding, "use_standard_json_time_format", rails_use_standard_json_time_format_get, 0); pv = rb_iv_get(encoding, "@escape_html_entities_in_json"); escape_html = Qtrue == pv; rb_undef_method(encoding, "escape_html_entities_in_json="); rb_define_module_function(encoding, "escape_html_entities_in_json=", rails_escape_html_entities_in_json, 1); rb_undef_method(encoding, "escape_html_entities_in_json"); rb_define_module_function(encoding, "escape_html_entities_in_json", rails_escape_html_entities_in_json_get, 0); pv = rb_iv_get(encoding, "@time_precision"); oj_default_options.sec_prec = NUM2INT(pv); oj_default_options.sec_prec_set = true; rb_undef_method(encoding, "time_precision="); rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1); rb_gv_set("$VERBOSE", verbose); return Qnil; } /* Document-method: set_decoder * call-seq: set_decoder() * * Sets the JSON.parse function to be the Oj::parse function which is json gem * compatible. */ static VALUE rails_set_decoder(VALUE self) { VALUE json; VALUE json_error; VALUE verbose; if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) { json = rb_const_get_at(rb_cObject, rb_intern("JSON")); } else { json = rb_define_module("JSON"); } if (rb_const_defined_at(json, rb_intern("JSONError"))) { json_error = rb_const_get(json, rb_intern("JSONError")); } else { json_error = rb_define_class_under(json, "JSONError", rb_eStandardError); } if (rb_const_defined_at(json, rb_intern("ParserError"))) { oj_json_parser_error_class = rb_const_get(json, rb_intern("ParserError")); } else { oj_json_parser_error_class = rb_define_class_under(json, "ParserError", json_error); } // rb_undef_method doesn't work for modules or maybe sometimes // doesn't. Anyway setting verbose should hide the warning. verbose = rb_gv_get("$VERBOSE"); rb_gv_set("$VERBOSE", Qfalse); rb_undef_method(json, "parse"); rb_define_module_function(json, "parse", oj_mimic_parse, -1); rb_gv_set("$VERBOSE", verbose); return Qnil; } /* Document-module: Oj.optimize_rails() * * Sets the Oj as the Rails encoder and decoder. Oj::Rails.optimize is also * called. */ VALUE oj_optimize_rails(VALUE self) { rails_set_encoder(self); rails_set_decoder(self); rails_optimize(0, NULL, self); rails_mimic_json(self); return Qnil; } /* Document-module: Oj::Rails * * Module that provides rails and active support compatibility. */ /* Document-class: Oj::Rails::Encoder * * The Oj ActiveSupport compliant encoder. */ void oj_mimic_rails_init() { VALUE rails = rb_define_module_under(Oj, "Rails"); rb_define_module_function(rails, "encode", rails_encode, -1); encoder_class = rb_define_class_under(rails, "Encoder", rb_cObject); rb_define_module_function(encoder_class, "new", encoder_new, -1); rb_define_module_function(rails, "optimize", rails_optimize, -1); rb_define_module_function(rails, "deoptimize", rails_deoptimize, -1); rb_define_module_function(rails, "optimized?", rails_optimized, 1); rb_define_module_function(rails, "mimic_JSON", rails_mimic_json, 0); rb_define_module_function(rails, "set_encoder", rails_set_encoder, 0); rb_define_module_function(rails, "set_decoder", rails_set_decoder, 0); rb_define_method(encoder_class, "encode", encoder_encode, 1); rb_define_method(encoder_class, "optimize", encoder_optimize, -1); rb_define_method(encoder_class, "deoptimize", encoder_deoptimize, -1); rb_define_method(encoder_class, "optimized?", encoder_optimized, 1); } static void dump_to_hash(VALUE obj, int depth, Out out) { dump_rails_val(rb_funcall(obj, oj_to_hash_id, 0), depth, out, true); } static void dump_float(VALUE obj, int depth, Out out, bool as_ok) { char buf[64]; char * b; double d = rb_num2dbl(obj); int cnt = 0; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else { if (isnan(d) || OJ_INFINITY == d || -OJ_INFINITY == d) { strcpy(buf, "null"); cnt = 4; } else if (d == (double)(long long int)d) { cnt = snprintf(buf, sizeof(buf), "%.1f", d); } else if (oj_rails_float_opt) { cnt = oj_dump_float_printf(buf, sizeof(buf), obj, d, "%0.16g"); } else { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); strcpy(buf, RSTRING_PTR(rstr)); cnt = (int)RSTRING_LEN(rstr); } } assure_size(out, cnt); for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; if (Yes == out->opts->circular) { if (0 > oj_check_circular(a, out)) { oj_dump_nil(Qnil, 0, out, false); return; } } // if (!oj_rails_array_opt && as_ok && 0 < out->argc && rb_respond_to(a, oj_as_json_id)) { if (as_ok && 0 < out->argc && rb_respond_to(a, oj_as_json_id)) { dump_as_json(a, depth, out, false); return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; size = 2; assure_size(out, size); if (0 == cnt) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } dump_rails_val(rb_ary_entry(a, i), d2, out, true); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; long size; int rtype = rb_type(key); if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } if (rtype != T_STRING && rtype != T_SYMBOL) { key = rb_funcall(key, oj_to_s_id, 0); rtype = rb_type(key); } if (!out->opts->dump_opts.use) { size = depth * out->indent + 1; assure_size(out, size); fill_indent(out, depth); if (rtype == T_STRING) { oj_dump_str(key, 0, out, false); } else { oj_dump_sym(key, 0, out, false); } *out->cur++ = ':'; } else { size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1; assure_size(out, size); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } if (rtype == T_STRING) { oj_dump_str(key, 0, out, false); } else { oj_dump_sym(key, 0, out, false); } size = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; assure_size(out, size); if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } } dump_rails_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; size_t size; if (Yes == out->opts->circular) { if (0 > oj_check_circular(obj, out)) { oj_dump_nil(Qnil, 0, out, false); return; } } if ((!oj_rails_hash_opt || 0 < out->argc) && as_ok && rb_respond_to(obj, oj_as_json_id)) { dump_as_json(obj, depth, out, false); return; } cnt = (int)RHASH_SIZE(obj); size = depth * out->indent + 2; assure_size(out, 2); *out->cur++ = '{'; if (0 == cnt) { *out->cur++ = '}'; } else { out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, size); fill_indent(out, depth); } else { size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1; assure_size(out, size); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas; if (oj_code_dump(oj_compat_codes, obj, depth, out)) { out->argc = 0; return; } clas = rb_obj_class(obj); if (as_ok) { ROpt ro; if (NULL != (ro = oj_rails_get_opt(out->ropts, clas)) && ro->on) { ro->dump(obj, depth, out, as_ok); } else if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) { oj_dump_raw_json(obj, depth, out); } else if (rb_respond_to(obj, oj_as_json_id)) { dump_as_json(obj, depth, out, true); } else if (rb_respond_to(obj, oj_to_hash_id)) { dump_to_hash(obj, depth, out); } else if (oj_bigdecimal_class == clas) { dump_bigdecimal(obj, depth, out, false); } else { oj_dump_obj_to_s(obj, out); } } else if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) { oj_dump_raw_json(obj, depth, out); } else if (rb_respond_to(obj, oj_to_hash_id)) { // Always attempt to_hash. dump_to_hash(obj, depth, out); } else if (oj_bigdecimal_class == clas) { dump_bigdecimal(obj, depth, out, false); } else { oj_dump_obj_to_s(obj, out); } } static DumpFunc rails_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, oj_dump_class, // RUBY_T_CLASS = 0x02, oj_dump_class, // RUBY_T_MODULE = 0x03, dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, dump_regexp, // RUBY_T_REGEXP = 0x06, // dump_as_string, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, dump_obj, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, dump_as_string, // RUBY_T_FILE = 0x0b, dump_obj, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, // Rails raises a stack error on Complex and Rational. It also corrupts // something which causes a segfault on the next call. Oj will not mimic // that behavior. dump_as_string, // RUBY_T_COMPLEX = 0x0e, dump_as_string, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; static void dump_rails_val(VALUE obj, int depth, Out out, bool as_ok) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = rails_funcs[type]; if (NULL != f) { f(obj, depth, out, as_ok); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } void oj_dump_rails_val(VALUE obj, int depth, Out out) { out->opts->str_rx.head = NULL; out->opts->str_rx.tail = NULL; if (escape_html) { out->opts->escape_mode = RailsXEsc; } else { out->opts->escape_mode = RailsEsc; } dump_rails_val(obj, depth, out, true); } oj-3.13.9/ext/oj/sparse.c0000644000004100000410000010000314136373754015127 0ustar www-datawww-data// Copyright (c) 2013 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #include "buf.h" #include "encode.h" #include "intern.h" // for oj_strndup() #include "oj.h" #include "parse.h" #include "val_stack.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif #define EXP_MAX 100000 #define DEC_MAX 15 static void skip_comment(ParseInfo pi) { char c = reader_get(&pi->rd); if ('*' == c) { while ('\0' != (c = reader_get(&pi->rd))) { if ('*' == c) { c = reader_get(&pi->rd); if ('/' == c) { return; } } } } else if ('/' == c) { while ('\0' != (c = reader_get(&pi->rd))) { switch (c) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid comment format"); } if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "comment not terminated"); return; } } static void add_value(ParseInfo pi, VALUE rval) { Val parent = stack_peek(&pi->stack); if (0 == parent) { // simple add pi->add_value(pi, rval); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_value(pi, rval); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_value(pi, parent, rval); if (parent->kalloc) { xfree((char *)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void add_num_value(ParseInfo pi, NumInfo ni) { Val parent = stack_peek(&pi->stack); if (0 == parent) { pi->add_num(pi, ni); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_num(pi, ni); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_num(pi, parent, ni); if (parent->kalloc) { xfree((char *)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void read_true(ParseInfo pi) { if (0 == reader_expect(&pi->rd, "rue")) { add_value(pi, Qtrue); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected true"); } } static void read_false(ParseInfo pi) { if (0 == reader_expect(&pi->rd, "alse")) { add_value(pi, Qfalse); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected false"); } } static uint32_t read_hex(ParseInfo pi) { uint32_t b = 0; int i; char c; for (i = 0; i < 4; i++) { c = reader_get(&pi->rd); b = b << 4; if ('0' <= c && c <= '9') { b += c - '0'; } else if ('A' <= c && c <= 'F') { b += c - 'A' + 10; } else if ('a' <= c && c <= 'f') { b += c - 'a' + 10; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hex character"); return 0; } } return b; } static void unicode_to_chars(ParseInfo pi, Buf buf, uint32_t code) { if (0x0000007F >= code) { buf_append(buf, (char)code); } else if (0x000007FF >= code) { buf_append(buf, 0xC0 | (code >> 6)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x0000FFFF >= code) { buf_append(buf, 0xE0 | (code >> 12)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x001FFFFF >= code) { buf_append(buf, 0xF0 | (code >> 18)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x03FFFFFF >= code) { buf_append(buf, 0xF8 | (code >> 24)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x7FFFFFFF >= code) { buf_append(buf, 0xFC | (code >> 30)); buf_append(buf, 0x80 | ((code >> 24) & 0x3F)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid Unicode character"); } } // entered at backslash static void read_escaped_str(ParseInfo pi) { struct _buf buf; char c; uint32_t code; Val parent = stack_peek(&pi->stack); buf_init(&buf); if (pi->rd.str < pi->rd.tail) { buf_append_string(&buf, pi->rd.str, pi->rd.tail - pi->rd.str); } while ('\"' != (c = reader_get(&pi->rd))) { if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); buf_cleanup(&buf); return; } else if ('\\' == c) { c = reader_get(&pi->rd); switch (c) { case 'n': buf_append(&buf, '\n'); break; case 'r': buf_append(&buf, '\r'); break; case 't': buf_append(&buf, '\t'); break; case 'f': buf_append(&buf, '\f'); break; case 'b': buf_append(&buf, '\b'); break; case '"': buf_append(&buf, '"'); break; case '/': buf_append(&buf, '/'); break; case '\\': buf_append(&buf, '\\'); break; case 'u': if (0 == (code = read_hex(pi)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; char ch2; c = reader_get(&pi->rd); ch2 = reader_get(&pi->rd); if ('\\' != c || 'u' != ch2) { if (Yes == pi->options.allow_invalid) { unicode_to_chars(pi, &buf, code); reader_backup(&pi->rd); reader_backup(&pi->rd); break; } oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } if (0 == (c2 = read_hex(pi)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } unicode_to_chars(pi, &buf, code); if (err_has(&pi->err)) { buf_cleanup(&buf); return; } break; default: // The json gem claims this is not an error despite the // ECMA-404 indicating it is not valid. if (CompatMode == pi->options.mode) { buf_append(&buf, c); break; } oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } } else { buf_append(&buf, c); } } if (0 == parent) { pi->add_cstr(pi, buf.head, buf_len(&buf), pi->rd.str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, buf.head, buf_len(&buf), pi->rd.str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: if (Qundef == (parent->key_val = pi->hash_key(pi, buf.head, buf_len(&buf)))) { parent->klen = buf_len(&buf); parent->key = malloc(parent->klen + 1); memcpy((char *)parent->key, buf.head, parent->klen); *(char *)(parent->key + parent->klen) = '\0'; } else { parent->key = ""; parent->klen = 0; } parent->k1 = *pi->rd.str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, buf.head, buf_len(&buf), pi->rd.str); if (parent->kalloc) { xfree((char *)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } buf_cleanup(&buf); } static void read_str(ParseInfo pi) { Val parent = stack_peek(&pi->stack); char c; reader_protect(&pi->rd); while ('\"' != (c = reader_get(&pi->rd))) { if ('\0' == c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); return; } else if ('\\' == c) { reader_backup(&pi->rd); read_escaped_str(pi); reader_release(&pi->rd); return; } } if (0 == parent) { // simple add pi->add_cstr(pi, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: parent->klen = pi->rd.tail - pi->rd.str - 1; if (sizeof(parent->karray) <= parent->klen) { parent->key = oj_strndup(pi->rd.str, parent->klen); parent->kalloc = 1; } else { memcpy(parent->karray, pi->rd.str, parent->klen); parent->karray[parent->klen] = '\0'; parent->key = parent->karray; parent->kalloc = 0; } parent->key_val = pi->hash_key(pi, parent->key, parent->klen); parent->k1 = *pi->rd.str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, pi->rd.str, pi->rd.tail - pi->rd.str - 1, pi->rd.str); if (parent->kalloc) { xfree((char *)parent->key); } parent->key = 0; parent->kalloc = 0; parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } reader_release(&pi->rd); } static void read_num(ParseInfo pi) { struct _numInfo ni; char c; reader_protect(&pi->rd); ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 0; ni.neg = 0; ni.has_exp = 0; if (CompatMode == pi->options.mode) { ni.no_big = !pi->options.compat_bigdec; ni.bigdec_load = pi->options.compat_bigdec; } else { ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load || RubyDec == pi->options.bigdec_load); ni.bigdec_load = pi->options.bigdec_load; } c = reader_get(&pi->rd); if ('-' == c) { c = reader_get(&pi->rd); ni.neg = 1; } else if ('+' == c) { c = reader_get(&pi->rd); } if ('I' == c) { if (No == pi->options.allow_nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } else if (0 != reader_expect(&pi->rd, "nfinity")) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } ni.infinity = 1; } else { int dec_cnt = 0; bool zero1 = false; for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { if (0 == ni.i && '0' == c) { zero1 = true; } if (0 < ni.i) { dec_cnt++; } if (ni.big) { ni.big++; } else { int d = (c - '0'); if (0 < d) { if (zero1 && CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); return; } zero1 = false; } ni.i = ni.i * 10 + d; if (INT64_MAX <= ni.i || DEC_MAX < dec_cnt) { ni.big = 1; } } } if ('.' == c) { c = reader_get(&pi->rd); // A trailing . is not a valid decimal but if encountered allow it // except when mimicking the JSON gem. if (CompatMode == pi->options.mode) { if (c < '0' || '9' < c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); } } for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { int d = (c - '0'); if (0 < ni.num || 0 < ni.i) { dec_cnt++; } if (INT64_MAX <= ni.div) { if (!ni.no_big) { ni.big = true; } } else { ni.num = ni.num * 10 + d; ni.div *= 10; ni.di++; if (INT64_MAX <= ni.div || DEC_MAX < dec_cnt) { if (!ni.no_big) { ni.big = true; } } } } } if ('e' == c || 'E' == c) { int eneg = 0; ni.has_exp = 1; c = reader_get(&pi->rd); if ('-' == c) { c = reader_get(&pi->rd); eneg = 1; } else if ('+' == c) { c = reader_get(&pi->rd); } for (; '0' <= c && c <= '9'; c = reader_get(&pi->rd)) { ni.exp = ni.exp * 10 + (c - '0'); if (EXP_MAX <= ni.exp) { ni.big = 1; } } if (eneg) { ni.exp = -ni.exp; } } ni.len = pi->rd.tail - pi->rd.str; if (0 != c) { reader_backup(&pi->rd); } } ni.str = pi->rd.str; ni.len = pi->rd.tail - pi->rd.str; // Check for special reserved values for Infinity and NaN. if (ni.big) { if (0 == strcasecmp(INF_VAL, ni.str)) { ni.infinity = 1; } else if (0 == strcasecmp(NINF_VAL, ni.str)) { ni.infinity = 1; ni.neg = 1; } else if (0 == strcasecmp(NAN_VAL, ni.str)) { ni.nan = 1; } } if (CompatMode == pi->options.mode) { if (pi->options.compat_bigdec) { ni.big = 1; } } else if (BigDec == pi->options.bigdec_load) { ni.big = 1; } add_num_value(pi, &ni); reader_release(&pi->rd); } static void read_nan(ParseInfo pi) { struct _numInfo ni; char c; ni.str = pi->rd.str; ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 1; ni.neg = 0; if (CompatMode == pi->options.mode) { ni.no_big = !pi->options.compat_bigdec; ni.bigdec_load = pi->options.compat_bigdec; } else { ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load || RubyDec == pi->options.bigdec_load); ni.bigdec_load = pi->options.bigdec_load; } if ('a' != reader_get(&pi->rd) || ('N' != (c = reader_get(&pi->rd)) && 'n' != c)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } if (CompatMode == pi->options.mode) { if (pi->options.compat_bigdec) { ni.big = 1; } } else if (BigDec == pi->options.bigdec_load) { ni.big = 1; } add_num_value(pi, &ni); } static void array_start(ParseInfo pi) { VALUE v = pi->start_array(pi); stack_push(&pi->stack, v, NEXT_ARRAY_NEW); } static void array_end(ParseInfo pi) { Val array = stack_pop(&pi->stack); if (0 == array) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected array close"); } else if (NEXT_ARRAY_COMMA != array->next && NEXT_ARRAY_NEW != array->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not an array close", oj_stack_next_string(array->next)); } else { pi->end_array(pi); add_value(pi, array->val); } } static void hash_start(ParseInfo pi) { volatile VALUE v = pi->start_hash(pi); stack_push(&pi->stack, v, NEXT_HASH_NEW); } static void hash_end(ParseInfo pi) { volatile Val hash = stack_peek(&pi->stack); // leave hash on stack until just before if (0 == hash) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected hash close"); } else if (NEXT_HASH_COMMA != hash->next && NEXT_HASH_NEW != hash->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a hash close", oj_stack_next_string(hash->next)); } else { pi->end_hash(pi); stack_pop(&pi->stack); add_value(pi, hash->val); } } static void comma(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 == parent) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } else if (NEXT_ARRAY_COMMA == parent->next) { parent->next = NEXT_ARRAY_ELEMENT; } else if (NEXT_HASH_COMMA == parent->next) { parent->next = NEXT_HASH_KEY; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } } static void colon(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 != parent && NEXT_HASH_COLON == parent->next) { parent->next = NEXT_HASH_VALUE; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected colon"); } } void oj_sparse2(ParseInfo pi) { int first = 1; char c; long start = 0; err_init(&pi->err); while (1) { if (0 < pi->max_depth && pi->max_depth <= pi->stack.tail - pi->stack.head - 1) { VALUE err_clas = oj_get_json_err_class("NestingError"); oj_set_error_at(pi, err_clas, __FILE__, __LINE__, "Too deeply nested."); pi->err_class = err_clas; return; } c = reader_next_non_white(&pi->rd); if (!first && '\0' != c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected characters after the JSON document"); } switch (c) { case '{': hash_start(pi); break; case '}': hash_end(pi); break; case ':': colon(pi); break; case '[': array_start(pi); break; case ']': array_end(pi); break; case ',': comma(pi); break; case '"': read_str(pi); break; case '+': if (CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } pi->cur--; read_num(pi); break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': reader_backup(&pi->rd); read_num(pi); break; case 'I': if (Yes == pi->options.allow_nan) { reader_backup(&pi->rd); read_num(pi); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } break; case 'N': if (Yes == pi->options.allow_nan) { read_nan(pi); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } break; case 't': read_true(pi); break; case 'f': read_false(pi); break; case 'n': c = reader_get(&pi->rd); if ('u' == c) { if (0 == reader_expect(&pi->rd, "ll")) { add_value(pi, Qnil); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected null"); return; } } else if ('a' == c) { struct _numInfo ni; c = reader_get(&pi->rd); if ('N' != c && 'n' != c) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected NaN"); return; } ni.str = pi->rd.str; ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 1; ni.neg = 0; if (CompatMode == pi->options.mode) { ni.no_big = !pi->options.compat_bigdec; ni.bigdec_load = pi->options.compat_bigdec; } else { ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load || RubyDec == pi->options.bigdec_load); ni.bigdec_load = pi->options.bigdec_load; } add_num_value(pi, &ni); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid token"); return; } break; case '/': skip_comment(pi); break; case '\0': return; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character '%c' [0x%02x]", c, c); return; } if (err_has(&pi->err)) { return; } if (stack_empty(&pi->stack)) { if (Qundef != pi->proc) { VALUE args[3]; long len = pi->rd.pos - start; *args = stack_head_val(&pi->stack); args[1] = LONG2NUM(start); args[2] = LONG2NUM(len); if (Qnil == pi->proc) { rb_yield_values2(3, args); } else { rb_proc_call_with_block(pi->proc, 3, args, Qnil); } } else if (!pi->has_callbacks) { first = 0; } start = pi->rd.pos; // TBD break if option set to allow that } } } static VALUE protect_parse(VALUE pip) { oj_sparse2((ParseInfo)pip); return Qnil; } VALUE oj_pi_sparse(int argc, VALUE *argv, ParseInfo pi, int fd) { volatile VALUE input; volatile VALUE wrapped_stack; VALUE result = Qnil; int line = 0; if (argc < 1) { rb_raise(rb_eArgError, "Wrong number of arguments to parse."); } input = argv[0]; if (2 <= argc) { if (T_HASH == rb_type(argv[1])) { oj_parse_options(argv[1], &pi->options); } else if (3 <= argc && T_HASH == rb_type(argv[2])) { oj_parse_options(argv[2], &pi->options); } } if (Qnil == input) { if (Yes == pi->options.nilnil) { return Qnil; } else { rb_raise(rb_eTypeError, "Nil is not a valid JSON source."); } } else if (CompatMode == pi->options.mode && T_STRING == rb_type(input) && No == pi->options.nilnil && 0 == RSTRING_LEN(input)) { rb_raise(oj_json_parser_error_class, "An empty string is not a valid JSON string."); } if (rb_block_given_p()) { pi->proc = Qnil; } else { pi->proc = Qundef; } oj_reader_init(&pi->rd, input, fd, CompatMode == pi->options.mode); pi->json = 0; // indicates reader is in use if (Yes == pi->options.circular) { pi->circ_array = oj_circ_array_new(); } else { pi->circ_array = 0; } if (No == pi->options.allow_gc) { rb_gc_disable(); } // GC can run at any time. When it runs any Object created by C will be // freed. We protect against this by wrapping the value stack in a ruby // data object and providing a mark function for ruby objects on the // value stack (while it is in scope). wrapped_stack = oj_stack_init(&pi->stack); rb_protect(protect_parse, (VALUE)pi, &line); if (Qundef == pi->stack.head->val && !empty_ok(&pi->options)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Empty input"); } result = stack_head_val(&pi->stack); DATA_PTR(wrapped_stack) = 0; if (No == pi->options.allow_gc) { rb_gc_enable(); } if (!err_has(&pi->err)) { // If the stack is not empty then the JSON terminated early. Val v; VALUE err_class = oj_parse_error_class; if (0 != line) { VALUE ec = rb_obj_class(rb_errinfo()); if (rb_eIOError != ec) { goto CLEANUP; } // Sometimes the class of the error is 0 which seems broken. if (rb_eArgError != ec && 0 != ec) { err_class = ec; } } if (0 != (v = stack_peek(&pi->stack))) { switch (v->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: case NEXT_ARRAY_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Array not terminated"); break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COLON: case NEXT_HASH_VALUE: case NEXT_HASH_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Hash/Object not terminated"); break; default: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "not terminated"); } } } CLEANUP: // proceed with cleanup if (0 != pi->circ_array) { oj_circ_array_free(pi->circ_array); } stack_cleanup(&pi->stack); if (0 != fd) { close(fd); } if (err_has(&pi->err)) { rb_set_errinfo(Qnil); if (Qnil != pi->err_class && 0 != pi->err_class) { pi->err.clas = pi->err_class; } if (CompatMode == pi->options.mode && Yes != pi->options.safe) { // The json gem requires the error message be UTF-8 encoded. In // additional the complete JSON source should be returned but that // is not possible without stored all the bytes read and reading // the remaining bytes on the stream. Both seem like a very bad // idea. VALUE args[] = {oj_encode(rb_str_new2(pi->err.msg))}; if (pi->err.clas == oj_parse_error_class) { // The error was an Oj::ParseError so change to a JSON::ParserError. pi->err.clas = oj_json_parser_error_class; } rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas)); } else { oj_err_raise(&pi->err); } oj_err_raise(&pi->err); } else if (0 != line) { rb_jump_tag(line); } return result; } oj-3.13.9/ext/oj/reader.c0000644000004100000410000001571214136373754015110 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #if NEEDS_UIO #include #endif #include #include #include "oj.h" #include "reader.h" #include "ruby.h" #define BUF_PAD 4 static VALUE rescue_cb(VALUE rdr, VALUE err); static VALUE io_cb(VALUE rdr); static VALUE partial_io_cb(VALUE rdr); static int read_from_io(Reader reader); static int read_from_fd(Reader reader); static int read_from_io_partial(Reader reader); // static int read_from_str(Reader reader); void oj_reader_init(Reader reader, VALUE io, int fd, bool to_s) { VALUE io_class = rb_obj_class(io); VALUE stat; VALUE ftype; reader->head = reader->base; *((char *)reader->head) = '\0'; reader->end = reader->head + sizeof(reader->base) - BUF_PAD; reader->tail = reader->head; reader->read_end = reader->head; reader->pro = 0; reader->str = 0; reader->pos = 0; reader->line = 1; reader->col = 0; reader->free_head = 0; if (0 != fd) { reader->read_func = read_from_fd; reader->fd = fd; } else if (rb_cString == io_class) { reader->read_func = 0; reader->in_str = StringValuePtr(io); reader->head = (char *)reader->in_str; reader->tail = reader->head; reader->read_end = reader->head + RSTRING_LEN(io); } else if (oj_stringio_class == io_class) { VALUE s = rb_funcall2(io, oj_string_id, 0, 0); reader->read_func = 0; reader->in_str = StringValuePtr(s); reader->head = (char *)reader->in_str; reader->tail = reader->head; reader->read_end = reader->head + RSTRING_LEN(s); } else if (rb_cFile == io_class && Qnil != (stat = rb_funcall(io, oj_stat_id, 0)) && Qnil != (ftype = rb_funcall(stat, oj_ftype_id, 0)) && 0 == strcmp("file", StringValuePtr(ftype)) && 0 == FIX2INT(rb_funcall(io, oj_pos_id, 0))) { reader->read_func = read_from_fd; reader->fd = FIX2INT(rb_funcall(io, oj_fileno_id, 0)); } else if (rb_respond_to(io, oj_readpartial_id)) { reader->read_func = read_from_io_partial; reader->io = io; } else if (rb_respond_to(io, oj_read_id)) { reader->read_func = read_from_io; reader->io = io; } else if (to_s) { volatile VALUE rstr = rb_funcall(io, oj_to_s_id, 0); reader->read_func = 0; reader->in_str = StringValuePtr(rstr); reader->head = (char *)reader->in_str; reader->tail = reader->head; reader->read_end = reader->head + RSTRING_LEN(rstr); } else { rb_raise(rb_eArgError, "parser io argument must be a String or respond to readpartial() or read().\n"); } } int oj_reader_read(Reader reader) { int err; size_t shift = 0; if (0 == reader->read_func) { return -1; } // if there is not much room to read into, shift or realloc a larger buffer. if (reader->head < reader->tail && 4096 > reader->end - reader->tail) { if (0 == reader->pro) { shift = reader->tail - reader->head; } else { shift = reader->pro - reader->head - 1; // leave one character so we can backup one } if (0 >= shift) { /* no space left so allocate more */ const char *old = reader->head; size_t size = reader->end - reader->head + BUF_PAD; if (reader->head == reader->base) { reader->head = ALLOC_N(char, size * 2); memcpy((char *)reader->head, old, size); } else { REALLOC_N(reader->head, char, size * 2); } reader->free_head = 1; reader->end = reader->head + size * 2 - BUF_PAD; reader->tail = reader->head + (reader->tail - old); reader->read_end = reader->head + (reader->read_end - old); if (0 != reader->pro) { reader->pro = reader->head + (reader->pro - old); } if (0 != reader->str) { reader->str = reader->head + (reader->str - old); } } else { memmove((char *)reader->head, reader->head + shift, reader->read_end - (reader->head + shift)); reader->tail -= shift; reader->read_end -= shift; if (0 != reader->pro) { reader->pro -= shift; } if (0 != reader->str) { reader->str -= shift; } } } err = reader->read_func(reader); *(char *)reader->read_end = '\0'; return err; } static VALUE rescue_cb(VALUE rbuf, VALUE err) { VALUE clas = rb_obj_class(err); if (rb_eTypeError != clas && rb_eEOFError != clas) { Reader reader = (Reader)rbuf; rb_raise(clas, "at line %d, column %d\n", reader->line, reader->col); } return Qfalse; } static VALUE partial_io_cb(VALUE rbuf) { Reader reader = (Reader)rbuf; VALUE args[1]; VALUE rstr; char * str; size_t cnt; args[0] = ULONG2NUM(reader->end - reader->tail); rstr = rb_funcall2(reader->io, oj_readpartial_id, 1, args); if (Qnil == rstr) { return Qfalse; } str = StringValuePtr(rstr); cnt = RSTRING_LEN(rstr); // printf("*** partial read %lu bytes, str: '%s'\n", cnt, str); strcpy(reader->tail, str); reader->read_end = reader->tail + cnt; return Qtrue; } static VALUE io_cb(VALUE rbuf) { Reader reader = (Reader)rbuf; VALUE args[1]; VALUE rstr; char * str; size_t cnt; args[0] = ULONG2NUM(reader->end - reader->tail); rstr = rb_funcall2(reader->io, oj_read_id, 1, args); if (Qnil == rstr) { return Qfalse; } str = StringValuePtr(rstr); cnt = RSTRING_LEN(rstr); // printf("*** read %lu bytes, str: '%s'\n", cnt, str); strcpy(reader->tail, str); reader->read_end = reader->tail + cnt; return Qtrue; } static int read_from_io_partial(Reader reader) { return (Qfalse == rb_rescue(partial_io_cb, (VALUE)reader, rescue_cb, (VALUE)reader)); } static int read_from_io(Reader reader) { return (Qfalse == rb_rescue(io_cb, (VALUE)reader, rescue_cb, (VALUE)reader)); } static int read_from_fd(Reader reader) { ssize_t cnt; size_t max = reader->end - reader->tail; cnt = read(reader->fd, reader->tail, max); if (cnt <= 0) { return -1; } else if (0 != cnt) { reader->read_end = reader->tail + cnt; } return 0; } // This is only called when the end of the string is reached so just return -1. /* static int read_from_str(Reader reader) { return -1; } */ oj-3.13.9/ext/oj/validate.c0000644000004100000410000000161314136373754015432 0ustar www-datawww-data// Copyright (c) 2021, Peter Ohler, All rights reserved. #include "parser.h" static void noop(ojParser p) { } static VALUE option(ojParser p, const char *key, VALUE value) { rb_raise(rb_eArgError, "%s is not an option for the validate delegate", key); return Qnil; } static VALUE result(ojParser p) { return Qnil; } static void dfree(ojParser p) { } static void mark(ojParser p) { } void oj_set_parser_validator(ojParser p) { p->ctx = NULL; Funcs end = p->funcs + 3; Funcs f; for (f = p->funcs; f < end; f++) { f->add_null = noop; f->add_true = noop; f->add_false = noop; f->add_int = noop; f->add_float = noop; f->add_big = noop; f->add_str = noop; f->open_array = noop; f->close_array = noop; f->open_object = noop; f->close_object = noop; } p->option = option; p->result = result; p->free = dfree; p->mark = mark; p->start = noop; } oj-3.13.9/ext/oj/dump.c0000644000004100000410000011021014136373754014600 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "dump.h" #include #include #include #include #include #include #include #include "cache8.h" #include "odd.h" #include "oj.h" #include "trace.h" #include "util.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) #define MAX_DEPTH 1000 static const char inf_val[] = INF_VAL; static const char ninf_val[] = NINF_VAL; static const char nan_val[] = NAN_VAL; typedef unsigned long ulong; static size_t hibit_friendly_size(const uint8_t *str, size_t len); static size_t xss_friendly_size(const uint8_t *str, size_t len); static size_t ascii_friendly_size(const uint8_t *str, size_t len); static const char hex_chars[17] = "0123456789abcdef"; // JSON standard except newlines are no escaped static char newline_friendly_chars[256] = "\ 66666666221622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111"; // JSON standard static char hibit_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111"; // High bit set characters are always encoded as unicode. Worse case is 3 // bytes per character in the output. That makes this conservative. static char ascii_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111116\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333"; // XSS safe mode static char xss_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211161111111121111111111116161\ 11111111111111111111111111112111\ 11111111111111111111111111111116\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333"; // JSON XSS combo static char hixss_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11611111111111111111111111111111"; // Rails XSS combo static char rails_xss_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211161111111111111111111116161\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11611111111111111111111111111111"; // Rails HTML non-escape static char rails_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111"; static void raise_strict(VALUE obj) { rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.", rb_class2name(rb_obj_class(obj))); } inline static size_t newline_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; for (; 0 < i; str++, i--) { size += newline_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; for (; 0 < i; str++, i--) { size += hibit_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t ascii_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; for (; 0 < i; str++, i--) { size += ascii_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t xss_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; for (; 0 < i; str++, i--) { size += xss_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t hixss_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; bool check = false; for (; 0 < i; str++, i--) { size += hixss_friendly_chars[*str]; if (0 != (0x80 & *str)) { check = true; } } return size - len * (size_t)'0' + check; } inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) { long size = 0; size_t i = len; uint8_t hi = 0; for (; 0 < i; str++, i--) { size += rails_xss_friendly_chars[*str]; hi |= *str & 0x80; } if (0 == hi) { return size - len * (size_t)'0'; } return -(size - len * (size_t)'0'); } inline static size_t rails_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; size_t i = len; for (; 0 < i; str++, i--) { size += rails_friendly_chars[*str]; } return size - len * (size_t)'0'; } const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, int *lenp) { const char *str = NULL; if (AutoNan == opt) { switch (mode) { case CompatMode: opt = WordNan; break; case StrictMode: opt = RaiseNan; break; default: break; } } switch (opt) { case RaiseNan: raise_strict(obj); break; case WordNan: if (plus) { str = "Infinity"; *lenp = 8; } else { str = "-Infinity"; *lenp = 9; } break; case NullNan: str = "null"; *lenp = 4; break; case HugeNan: default: if (plus) { str = inf_val; *lenp = sizeof(inf_val) - 1; } else { str = ninf_val; *lenp = sizeof(ninf_val) - 1; } break; } return str; } inline static void dump_hex(uint8_t c, Out out) { uint8_t d = (c >> 4) & 0x0F; *out->cur++ = hex_chars[d]; d = c & 0x0F; *out->cur++ = hex_chars[d]; } static void raise_invalid_unicode(const char *str, int len, int pos) { char c; char code[32]; char * cp = code; int i; uint8_t d; *cp++ = '['; for (i = pos; i < len && i - pos < 5; i++) { c = str[i]; d = (c >> 4) & 0x0F; *cp++ = hex_chars[d]; d = c & 0x0F; *cp++ = hex_chars[d]; *cp++ = ' '; } cp--; *cp++ = ']'; *cp = '\0'; rb_raise(oj_json_generator_error_class, "Invalid Unicode %s at %d", code, pos); } static const char *dump_unicode(const char *str, const char *end, Out out, const char *orig) { uint32_t code = 0; uint8_t b = *(uint8_t *)str; int i, cnt; if (0xC0 == (0xE0 & b)) { cnt = 1; code = b & 0x0000001F; } else if (0xE0 == (0xF0 & b)) { cnt = 2; code = b & 0x0000000F; } else if (0xF0 == (0xF8 & b)) { cnt = 3; code = b & 0x00000007; } else if (0xF8 == (0xFC & b)) { cnt = 4; code = b & 0x00000003; } else if (0xFC == (0xFE & b)) { cnt = 5; code = b & 0x00000001; } else { cnt = 0; raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig)); } str++; for (; 0 < cnt; cnt--, str++) { b = *(uint8_t *)str; if (end <= str || 0x80 != (0xC0 & b)) { raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig)); } code = (code << 6) | (b & 0x0000003F); } if (0x0000FFFF < code) { uint32_t c1; code -= 0x00010000; c1 = ((code >> 10) & 0x000003FF) + 0x0000D800; code = (code & 0x000003FF) + 0x0000DC00; *out->cur++ = '\\'; *out->cur++ = 'u'; for (i = 3; 0 <= i; i--) { *out->cur++ = hex_chars[(uint8_t)(c1 >> (i * 4)) & 0x0F]; } } *out->cur++ = '\\'; *out->cur++ = 'u'; for (i = 3; 0 <= i; i--) { *out->cur++ = hex_chars[(uint8_t)(code >> (i * 4)) & 0x0F]; } return str - 1; } static const char *check_unicode(const char *str, const char *end, const char *orig) { uint8_t b = *(uint8_t *)str; int cnt = 0; if (0xC0 == (0xE0 & b)) { cnt = 1; } else if (0xE0 == (0xF0 & b)) { cnt = 2; } else if (0xF0 == (0xF8 & b)) { cnt = 3; } else if (0xF8 == (0xFC & b)) { cnt = 4; } else if (0xFC == (0xFE & b)) { cnt = 5; } else { raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig)); } str++; for (; 0 < cnt; cnt--, str++) { b = *(uint8_t *)str; if (end <= str || 0x80 != (0xC0 & b)) { raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig)); } } return str; } // Returns 0 if not using circular references, -1 if no further writing is // needed (duplicate), and a positive value if the object was added to the // cache. long oj_check_circular(VALUE obj, Out out) { slot_t id = 0; slot_t *slot; if (Yes == out->opts->circular) { if (0 == (id = oj_cache8_get(out->circ_cache, obj, &slot))) { out->circ_cnt++; id = out->circ_cnt; *slot = id; } else { if (ObjectMode == out->opts->mode) { assure_size(out, 18); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'r'; dump_ulong(id, out); *out->cur++ = '"'; } return -1; } } return (long)id; } void oj_dump_time(VALUE obj, Out out, int withZone) { char buf[64]; char * b = buf + sizeof(buf) - 1; long size; char * dot; int neg = 0; long one = 1000000000; long long sec; long long nsec; #ifdef HAVE_RB_TIME_TIMESPEC // rb_time_timespec as well as rb_time_timeeval have a bug that causes an // exception to be raised if a time is before 1970 on 32 bit systems so // check the timespec size and use the ruby calls if a 32 bit system. if (16 <= sizeof(struct timespec)) { struct timespec ts = rb_time_timespec(obj); sec = (long long)ts.tv_sec; nsec = ts.tv_nsec; } else { sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } #else sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #endif *b-- = '\0'; if (withZone) { long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0)); int zneg = (0 > tzsecs); if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { tzsecs = 86400; } if (zneg) { tzsecs = -tzsecs; } if (0 == tzsecs) { *b-- = '0'; } else { for (; 0 < tzsecs; b--, tzsecs /= 10) { *b = '0' + (tzsecs % 10); } if (zneg) { *b-- = '-'; } } *b-- = 'e'; } if (0 > sec) { neg = 1; sec = -sec; if (0 < nsec) { nsec = 1000000000 - nsec; sec--; } } dot = b - 9; if (0 < out->opts->sec_prec) { if (9 > out->opts->sec_prec) { int i; for (i = 9 - out->opts->sec_prec; 0 < i; i--) { dot++; nsec = (nsec + 5) / 10; one /= 10; } } if (one <= nsec) { nsec -= one; sec++; } for (; dot < b; b--, nsec /= 10) { *b = '0' + (nsec % 10); } *b-- = '.'; } if (0 == sec) { *b-- = '0'; } else { for (; 0 < sec; b--, sec /= 10) { *b = '0' + (sec % 10); } } if (neg) { *b-- = '-'; } b++; size = sizeof(buf) - (b - buf) - 1; assure_size(out, size); memcpy(out->cur, b, size); out->cur += size; *out->cur = '\0'; } void oj_dump_ruby_time(VALUE obj, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), 0, 0, out); } void oj_dump_xml_time(VALUE obj, Out out) { char buf[64]; struct _timeInfo ti; long one = 1000000000; int64_t sec; long long nsec; long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0)); int tzhour, tzmin; char tzsign = '+'; #ifdef HAVE_RB_TIME_TIMESPEC if (16 <= sizeof(struct timespec)) { struct timespec ts = rb_time_timespec(obj); sec = ts.tv_sec; nsec = ts.tv_nsec; } else { sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } #else sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #endif assure_size(out, 36); if (9 > out->opts->sec_prec) { int i; // This is pretty lame but to be compatible with rails and active // support rounding is not done but instead a floor is done when // second precision is 3 just to be like rails. sigh. if (3 == out->opts->sec_prec) { nsec /= 1000000; one = 1000; } else { for (i = 9 - out->opts->sec_prec; 0 < i; i--) { nsec = (nsec + 5) / 10; one /= 10; } if (one <= nsec) { nsec -= one; sec++; } } } // 2012-01-05T23:58:07.123456000+09:00 // tm = localtime(&sec); sec += tzsecs; sec_as_time((int64_t)sec, &ti); if (0 > tzsecs) { tzsign = '-'; tzhour = (int)(tzsecs / -3600); tzmin = (int)(tzsecs / -60) - (tzhour * 60); } else { tzhour = (int)(tzsecs / 3600); tzmin = (int)(tzsecs / 60) - (tzhour * 60); } if ((0 == nsec && !out->opts->sec_prec_set) || 0 == out->opts->sec_prec) { if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { int len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec); oj_dump_cstr(buf, len, 0, 0, out); } else { int len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, tzsign, tzhour, tzmin); oj_dump_cstr(buf, len, 0, 0, out); } } else if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ"; int len; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; } len = sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, (long)nsec); oj_dump_cstr(buf, len, 0, 0, out); } else { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d"; int len; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; } len = sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, (long)nsec, tzsign, tzhour, tzmin); oj_dump_cstr(buf, len, 0, 0, out); } } void oj_dump_obj_to_json(VALUE obj, Options copts, Out out) { oj_dump_obj_to_json_using_params(obj, copts, out, 0, 0); } void oj_dump_obj_to_json_using_params(VALUE obj, Options copts, Out out, int argc, VALUE *argv) { if (0 == out->buf) { out->buf = ALLOC_N(char, 4096); // 1 less than end plus extra for possible errors out->end = out->buf + 4095 - BUFFER_EXTRA; out->allocated = true; } out->cur = out->buf; out->circ_cnt = 0; out->opts = copts; out->hash_cnt = 0; out->indent = copts->indent; out->argc = argc; out->argv = argv; out->ropts = NULL; if (Yes == copts->circular) { oj_cache8_new(&out->circ_cache); } switch (copts->mode) { case StrictMode: oj_dump_strict_val(obj, 0, out); break; case NullMode: oj_dump_null_val(obj, 0, out); break; case ObjectMode: oj_dump_obj_val(obj, 0, out); break; case CompatMode: oj_dump_compat_val(obj, 0, out, Yes == copts->to_json); break; case RailsMode: oj_dump_rails_val(obj, 0, out); break; case CustomMode: oj_dump_custom_val(obj, 0, out, true); break; case WabMode: oj_dump_wab_val(obj, 0, out); break; default: oj_dump_custom_val(obj, 0, out, true); break; } if (0 < out->indent) { switch (*(out->cur - 1)) { case ']': case '}': assure_size(out, 1); *out->cur++ = '\n'; default: break; } } *out->cur = '\0'; if (Yes == copts->circular) { oj_cache8_delete(out->circ_cache); } } void oj_write_obj_to_file(VALUE obj, const char *path, Options copts) { char buf[4096]; struct _out out; size_t size; FILE * f; int ok; out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = false; out.omit_nil = copts->dump_opts.omit_nil; oj_dump_obj_to_json(obj, copts, &out); size = out.cur - out.buf; if (0 == (f = fopen(path, "w"))) { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eIOError, "%s", strerror(errno)); } ok = (size == fwrite(out.buf, 1, size, f)); if (out.allocated) { xfree(out.buf); } fclose(f); if (!ok) { int err = ferror(f); rb_raise(rb_eIOError, "Write failed. [%d:%s]", err, strerror(err)); } } void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts) { char buf[4096]; struct _out out; ssize_t size; VALUE clas = rb_obj_class(stream); #if !IS_WINDOWS int fd; VALUE s; #endif out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = false; out.omit_nil = copts->dump_opts.omit_nil; oj_dump_obj_to_json(obj, copts, &out); size = out.cur - out.buf; if (oj_stringio_class == clas) { rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size)); #if !IS_WINDOWS } else if (rb_respond_to(stream, oj_fileno_id) && Qnil != (s = rb_funcall(stream, oj_fileno_id, 0)) && 0 != (fd = FIX2INT(s))) { if (size != write(fd, out.buf, size)) { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eIOError, "Write failed. [%d:%s]", errno, strerror(errno)); } #endif } else if (rb_respond_to(stream, oj_write_id)) { rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size)); } else { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eArgError, "to_stream() expected an IO Object."); } if (out.allocated) { xfree(out.buf); } } void oj_dump_str(VALUE obj, int depth, Out out, bool as_ok) { rb_encoding *enc = rb_enc_get(obj); if (oj_utf8_encoding != enc) { obj = rb_str_conv_enc(obj, enc, oj_utf8_encoding); } oj_dump_cstr(RSTRING_PTR(obj), (int)RSTRING_LEN(obj), 0, 0, out); } void oj_dump_sym(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE s = rb_sym2str(obj); oj_dump_cstr(RSTRING_PTR(s), (int)RSTRING_LEN(s), 0, 0, out); } static void debug_raise(const char *orig, size_t cnt, int line) { char buf[1024]; char * b = buf; const char *s = orig; const char *s_end = s + cnt; if (32 < s_end - s) { s_end = s + 32; } for (; s < s_end; s++) { b += sprintf(b, " %02x", *s); } *b = '\0'; rb_raise(oj_json_generator_error_class, "Partial character in string. %s @ %d", buf, line); } void oj_dump_raw_json(VALUE obj, int depth, Out out) { if (oj_string_writer_class == rb_obj_class(obj)) { StrWriter sw = (StrWriter)DATA_PTR(obj); size_t len = sw->out.cur - sw->out.buf; if (0 < len) { len--; } oj_dump_raw(sw->out.buf, len, out); } else { volatile VALUE jv; if (Yes == out->opts->trace) { oj_trace("raw_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } jv = rb_funcall(obj, oj_raw_json_id, 2, RB_INT2NUM(depth), RB_INT2NUM(out->indent)); if (Yes == out->opts->trace) { oj_trace("raw_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } oj_dump_raw(RSTRING_PTR(jv), (size_t)RSTRING_LEN(jv), out); } } void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) { size_t size; char * cmap; const char *orig = str; bool has_hi = false; switch (out->opts->escape_mode) { case NLEsc: cmap = newline_friendly_chars; size = newline_friendly_size((uint8_t *)str, cnt); break; case ASCIIEsc: cmap = ascii_friendly_chars; size = ascii_friendly_size((uint8_t *)str, cnt); break; case XSSEsc: cmap = xss_friendly_chars; size = xss_friendly_size((uint8_t *)str, cnt); break; case JXEsc: cmap = hixss_friendly_chars; size = hixss_friendly_size((uint8_t *)str, cnt); break; case RailsXEsc: { long sz; cmap = rails_xss_friendly_chars; sz = rails_xss_friendly_size((uint8_t *)str, cnt); if (sz < 0) { has_hi = true; size = (size_t)-sz; } else { size = (size_t)sz; } break; } case RailsEsc: cmap = rails_friendly_chars; size = rails_friendly_size((uint8_t *)str, cnt); break; case JSONEsc: default: cmap = hibit_friendly_chars; size = hibit_friendly_size((uint8_t *)str, cnt); } assure_size(out, size + BUFFER_EXTRA); *out->cur++ = '"'; if (escape1) { *out->cur++ = '\\'; *out->cur++ = 'u'; *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((uint8_t)*str, out); cnt--; size--; str++; is_sym = 0; // just to make sure } if (cnt == size && !has_hi) { if (is_sym) { *out->cur++ = ':'; } memcpy(out->cur, str, cnt); out->cur += cnt; *out->cur++ = '"'; } else { const char *end = str + cnt; const char *check_start = str; if (is_sym) { *out->cur++ = ':'; } for (; str < end; str++) { switch (cmap[(uint8_t)*str]) { case '1': if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && check_start <= str) { if (0 != (0x80 & (uint8_t)*str)) { if (0xC0 == (0xC0 & (uint8_t)*str)) { check_start = check_unicode(str, end, orig); } else { raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig)); } } } *out->cur++ = *str; break; case '2': *out->cur++ = '\\'; switch (*str) { case '\\': *out->cur++ = '\\'; break; case '\b': *out->cur++ = 'b'; break; case '\t': *out->cur++ = 't'; break; case '\n': *out->cur++ = 'n'; break; case '\f': *out->cur++ = 'f'; break; case '\r': *out->cur++ = 'r'; break; default: *out->cur++ = *str; break; } break; case '3': // Unicode if (0xe2 == (uint8_t)*str && (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 2 <= end - str) { if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) { str = dump_unicode(str, end, out, orig); } else { check_start = check_unicode(str, end, orig); *out->cur++ = *str; } break; } str = dump_unicode(str, end, out, orig); break; case '6': // control characters if (*(uint8_t *)str < 0x80) { *out->cur++ = '\\'; *out->cur++ = 'u'; *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((uint8_t)*str, out); } else { if (0xe2 == (uint8_t)*str && (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 2 <= end - str) { if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) { str = dump_unicode(str, end, out, orig); } else { check_start = check_unicode(str, end, orig); *out->cur++ = *str; } break; } str = dump_unicode(str, end, out, orig); } break; default: break; // ignore, should never happen if the table is correct } } *out->cur++ = '"'; } if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 0 < str - orig && 0 != (0x80 & *(str - 1))) { uint8_t c = (uint8_t) * (str - 1); int i; int scnt = (int)(str - orig); // Last utf-8 characters must be 0x10xxxxxx. The start must be // 0x110xxxxx for 2 characters, 0x1110xxxx for 3, and 0x11110xxx for // 4. if (0 != (0x40 & c)) { debug_raise(orig, cnt, __LINE__); } for (i = 1; i < (int)scnt && i < 4; i++) { c = str[-1 - i]; if (0x80 != (0xC0 & c)) { switch (i) { case 1: if (0xC0 != (0xE0 & c)) { debug_raise(orig, cnt, __LINE__); } break; case 2: if (0xE0 != (0xF0 & c)) { debug_raise(orig, cnt, __LINE__); } break; case 3: if (0xF0 != (0xF8 & c)) { debug_raise(orig, cnt, __LINE__); } break; default: // can't get here break; } break; } } if (i == (int)scnt || 4 <= i) { debug_raise(orig, cnt, __LINE__); } } *out->cur = '\0'; } void oj_dump_class(VALUE obj, int depth, Out out, bool as_ok) { const char *s = rb_class2name(obj); oj_dump_cstr(s, strlen(s), 0, 0, out); } void oj_dump_obj_to_s(VALUE obj, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), 0, 0, out); } void oj_dump_raw(const char *str, size_t cnt, Out out) { assure_size(out, cnt + 10); memcpy(out->cur, str, cnt); out->cur += cnt; *out->cur = '\0'; } void oj_grow_out(Out out, size_t len) { size_t size = out->end - out->buf; long pos = out->cur - out->buf; char * buf = out->buf; size *= 2; if (size <= len * 2 + pos) { size += len; } if (out->allocated) { REALLOC_N(buf, char, (size + BUFFER_EXTRA)); } else { buf = ALLOC_N(char, (size + BUFFER_EXTRA)); out->allocated = true; memcpy(buf, out->buf, out->end - out->buf + BUFFER_EXTRA); } if (0 == buf) { rb_raise(rb_eNoMemError, "Failed to create string. [%d:%s]", ENOSPC, strerror(ENOSPC)); } out->buf = buf; out->end = buf + size; out->cur = out->buf + pos; } void oj_dump_nil(VALUE obj, int depth, Out out, bool as_ok) { assure_size(out, 4); *out->cur++ = 'n'; *out->cur++ = 'u'; *out->cur++ = 'l'; *out->cur++ = 'l'; *out->cur = '\0'; } void oj_dump_true(VALUE obj, int depth, Out out, bool as_ok) { assure_size(out, 4); *out->cur++ = 't'; *out->cur++ = 'r'; *out->cur++ = 'u'; *out->cur++ = 'e'; *out->cur = '\0'; } void oj_dump_false(VALUE obj, int depth, Out out, bool as_ok) { assure_size(out, 5); *out->cur++ = 'f'; *out->cur++ = 'a'; *out->cur++ = 'l'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur = '\0'; } void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) { char buf[32]; char * b = buf + sizeof(buf) - 1; long long num = rb_num2ll(obj); int neg = 0; bool dump_as_string = false; if (out->opts->int_range_max != 0 && out->opts->int_range_min != 0 && (out->opts->int_range_max < num || out->opts->int_range_min > num)) { dump_as_string = true; } if (0 > num) { neg = 1; num = -num; } *b-- = '\0'; if (dump_as_string) { *b-- = '"'; } if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } if (neg) { *b = '-'; } else { b++; } } else { *b = '0'; } if (dump_as_string) { *--b = '"'; } assure_size(out, (sizeof(buf) - (b - buf))); for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } void oj_dump_bignum(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE rs = rb_big2str(obj, 10); int cnt = (int)RSTRING_LEN(rs); bool dump_as_string = false; if (out->opts->int_range_max != 0 || out->opts->int_range_min != 0) { // Bignum cannot be inside of Fixnum range dump_as_string = true; assure_size(out, cnt + 2); *out->cur++ = '"'; } else { assure_size(out, cnt); } memcpy(out->cur, RSTRING_PTR(rs), cnt); out->cur += cnt; if (dump_as_string) { *out->cur++ = '"'; } *out->cur = '\0'; } // Removed dependencies on math due to problems with CentOS 5.4. void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) { char buf[64]; char * b; double d = rb_num2dbl(obj); int cnt = 0; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else if (OJ_INFINITY == d) { if (ObjectMode == out->opts->mode) { strcpy(buf, inf_val); cnt = sizeof(inf_val) - 1; } else { NanDump nd = out->opts->dump_opts.nan_dump; if (AutoNan == nd) { switch (out->opts->mode) { case CompatMode: nd = WordNan; break; case StrictMode: nd = RaiseNan; break; case NullMode: nd = NullNan; break; case CustomMode: nd = NullNan; break; default: break; } } switch (nd) { case RaiseNan: raise_strict(obj); break; case WordNan: strcpy(buf, "Infinity"); cnt = 8; break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, inf_val); cnt = sizeof(inf_val) - 1; break; } } } else if (-OJ_INFINITY == d) { if (ObjectMode == out->opts->mode) { strcpy(buf, ninf_val); cnt = sizeof(ninf_val) - 1; } else { NanDump nd = out->opts->dump_opts.nan_dump; if (AutoNan == nd) { switch (out->opts->mode) { case CompatMode: nd = WordNan; break; case StrictMode: nd = RaiseNan; break; case NullMode: nd = NullNan; break; default: break; } } switch (nd) { case RaiseNan: raise_strict(obj); break; case WordNan: strcpy(buf, "-Infinity"); cnt = 9; break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, ninf_val); cnt = sizeof(ninf_val) - 1; break; } } } else if (isnan(d)) { if (ObjectMode == out->opts->mode) { strcpy(buf, nan_val); cnt = sizeof(ninf_val) - 1; } else { NanDump nd = out->opts->dump_opts.nan_dump; if (AutoNan == nd) { switch (out->opts->mode) { case ObjectMode: nd = HugeNan; break; case StrictMode: nd = RaiseNan; break; case NullMode: nd = NullNan; break; default: break; } } switch (nd) { case RaiseNan: raise_strict(obj); break; case WordNan: strcpy(buf, "NaN"); cnt = 3; break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, nan_val); cnt = sizeof(nan_val) - 1; break; } } } else if (d == (double)(long long int)d) { cnt = snprintf(buf, sizeof(buf), "%.1f", d); } else if (0 == out->opts->float_prec) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); cnt = (int)RSTRING_LEN(rstr); if ((int)sizeof(buf) <= cnt) { cnt = sizeof(buf) - 1; } memcpy(buf, RSTRING_PTR(rstr), cnt); buf[cnt] = '\0'; } else { cnt = oj_dump_float_printf(buf, sizeof(buf), obj, d, out->opts->float_fmt); } assure_size(out, cnt); for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } int oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format) { int cnt = snprintf(buf, blen, format, d); // Round off issues at 16 significant digits so check for obvious ones of // 0001 and 9999. if (17 <= cnt && (0 == strcmp("0001", buf + cnt - 4) || 0 == strcmp("9999", buf + cnt - 4))) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); strcpy(buf, RSTRING_PTR(rstr)); cnt = (int)RSTRING_LEN(rstr); } return cnt; } bool oj_dump_ignore(Options opts, VALUE obj) { if (NULL != opts->ignore && (ObjectMode == opts->mode || CustomMode == opts->mode)) { VALUE *vp = opts->ignore; VALUE clas = rb_obj_class(obj); for (; Qnil != *vp; vp++) { if (clas == *vp) { return true; } } } return false; } oj-3.13.9/ext/oj/util.c0000644000004100000410000001002014136373754014606 0ustar www-datawww-data// Copyright (c) 2019 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "util.h" #include #include #include #include #include #define SECS_PER_DAY 86400LL #define SECS_PER_YEAR 31536000LL #define SECS_PER_LEAP 31622400LL #define SECS_PER_QUAD_YEAR (SECS_PER_YEAR * 3 + SECS_PER_LEAP) #define SECS_PER_CENT (SECS_PER_QUAD_YEAR * 24 + SECS_PER_YEAR * 4) #define SECS_PER_LEAP_CENT (SECS_PER_CENT + SECS_PER_DAY) #define SECS_PER_QUAD_CENT (SECS_PER_CENT * 4 + SECS_PER_DAY) static int64_t eom_secs[] = { 2678400, // January (31) 5097600, // February (28) 2419200 2505600 7776000, // March (31) 10368000, // April (30 2592000 13046400, // May (31) 15638400, // June (30) 18316800, // July (31) 20995200, // August (31) 23587200, // September (30) 26265600, // October (31) 28857600, // November (30) 31536000, // December (31) }; static int64_t eom_leap_secs[] = { 2678400, // January (31) 5184000, // February (28) 2419200 2505600 7862400, // March (31) 10454400, // April (30 2592000 13132800, // May (31) 15724800, // June (30) 18403200, // July (31) 21081600, // August (31) 23673600, // September (30) 26352000, // October (31) 28944000, // November (30) 31622400, // December (31) }; void sec_as_time(int64_t secs, TimeInfo ti) { int64_t qc = 0; int64_t c = 0; int64_t qy = 0; int64_t y = 0; bool leap = false; int64_t *ms; int m; int shift = 0; secs += 62167219200LL; // normalize to first day of the year 0 if (secs < 0) { shift = -secs / SECS_PER_QUAD_CENT; shift++; secs += shift * SECS_PER_QUAD_CENT; } qc = secs / SECS_PER_QUAD_CENT; secs = secs - qc * SECS_PER_QUAD_CENT; if (secs < SECS_PER_LEAP) { leap = true; } else if (secs < SECS_PER_QUAD_YEAR) { if (SECS_PER_LEAP <= secs) { secs -= SECS_PER_LEAP; y = secs / SECS_PER_YEAR; secs = secs - y * SECS_PER_YEAR; y++; leap = false; } } else if (secs < SECS_PER_LEAP_CENT) { // first century in 400 years is a leap century (one // extra day) qy = secs / SECS_PER_QUAD_YEAR; secs = secs - qy * SECS_PER_QUAD_YEAR; if (secs < SECS_PER_LEAP) { leap = true; } else { secs -= SECS_PER_LEAP; y = secs / SECS_PER_YEAR; secs = secs - y * SECS_PER_YEAR; y++; } } else { secs -= SECS_PER_LEAP_CENT; c = secs / SECS_PER_CENT; secs = secs - c * SECS_PER_CENT; c++; if (secs < SECS_PER_YEAR * 4) { y = secs / SECS_PER_YEAR; secs = secs - y * SECS_PER_YEAR; } else { secs -= SECS_PER_YEAR * 4; qy = secs / SECS_PER_QUAD_YEAR; secs = secs - qy * SECS_PER_QUAD_YEAR; qy++; if (secs < SECS_PER_LEAP) { leap = true; } else { secs -= SECS_PER_LEAP; y = secs / SECS_PER_YEAR; secs = secs - y * SECS_PER_YEAR; y++; } } } ti->year = (int)((qc - (int64_t)shift) * 400 + c * 100 + qy * 4 + y); if (leap) { ms = eom_leap_secs; } else { ms = eom_secs; } for (m = 1; m <= 12; m++, ms++) { if (secs < *ms) { if (1 < m) { secs -= *(ms - 1); } ti->mon = m; break; } } ti->day = (int)(secs / 86400LL); secs = secs - (int64_t)ti->day * 86400LL; ti->day++; ti->hour = (int)(secs / 3600LL); secs = secs - (int64_t)ti->hour * 3600LL; ti->min = (int)(secs / 60LL); secs = secs - (int64_t)ti->min * 60LL; ti->sec = (int)secs; } oj-3.13.9/ext/oj/usual.c0000644000004100000410000010631514136373754014777 0ustar www-datawww-data// Copyright (c) 2021, Peter Ohler, All rights reserved. #include "cache.h" #include "oj.h" #include "parser.h" // The Usual delegate builds Ruby objects during parsing. It makes use of // three stacks. The first is the value stack. This is where parsed values are // placed. With the value stack the bulk creation and setting can be used // which is significantly faster than setting Array (15x) or Hash (3x) // elements one at a time. // // The second stack is the collection stack. Each element on the collection // stack marks the start of a Hash, Array, or Object. // // The third stack is the key stack which is used for Hash and Object // members. The key stack elements store the keys that could be used for // either a Hash or Object. Since the decision on whether the parent is a Hash // or Object can not be made until the end of the JSON object the keys remain // as strings until just before setting the Hash or Object members. // // The approach taken with the usual delegate is to configure the delegate for // the parser up front so that the various options are not checked during // parsing and thus avoiding conditionals as much as reasonably possible in // the more time sensitive parsing. Configuration is simply setting the // function pointers to point to the function to be used for the selected // option. #define DEBUG 0 // Used to mark the start of each Hash, Array, or Object. The members point at // positions of the start in the value stack and if not an Array into the key // stack. typedef struct _col { long vi; // value stack index long ki; // key stack index if an hash else -1 for an array } * Col; typedef union _key { struct { int16_t len; char buf[30]; }; struct { int16_t xlen; // should be the same as len char * key; }; } * Key; #define MISS_AUTO 'A' #define MISS_RAISE 'R' #define MISS_IGNORE 'I' typedef struct _delegate { VALUE *vhead; VALUE *vtail; VALUE *vend; Col chead; Col ctail; Col cend; Key khead; Key ktail; Key kend; VALUE (*get_key)(ojParser p, Key kp); struct _cache *key_cache; // same as str_cache or sym_cache struct _cache *str_cache; struct _cache *sym_cache; struct _cache *class_cache; struct _cache *attr_cache; VALUE array_class; VALUE hash_class; char * create_id; uint8_t create_id_len; uint8_t cache_str; uint8_t cache_xrate; uint8_t miss_class; bool cache_keys; bool ignore_json_create; } * Delegate; static ID to_f_id = 0; static ID ltlt_id = 0; static ID hset_id = 0; static char *str_dup(const char *s, size_t len) { char *d = ALLOC_N(char, len + 1); memcpy(d, s, len); d[len] = '\0'; return d; } static VALUE form_str(const char *str, size_t len) { return rb_str_freeze(rb_utf8_str_new(str, len)); } static VALUE form_sym(const char *str, size_t len) { return rb_str_intern(rb_utf8_str_new(str, len)); } static VALUE form_attr(const char *str, size_t len) { char buf[256]; if (sizeof(buf) - 2 <= len) { char *b = ALLOC_N(char, len + 2); ID id; *b = '@'; memcpy(b + 1, str, len); b[len + 1] = '\0'; id = rb_intern3(buf, len + 1, oj_utf8_encoding); xfree(b); return id; } *buf = '@'; memcpy(buf + 1, str, len); buf[len + 1] = '\0'; return (VALUE)rb_intern3(buf, len + 1, oj_utf8_encoding); } static VALUE resolve_classname(VALUE mod, const char *classname, bool auto_define) { VALUE clas; ID ci = rb_intern(classname); if (rb_const_defined_at(mod, ci)) { clas = rb_const_get_at(mod, ci); } else if (auto_define) { clas = rb_define_class_under(mod, classname, oj_bag_class); } else { clas = Qundef; } return clas; } static VALUE resolve_classpath(const char *name, size_t len, bool auto_define) { char class_name[1024]; VALUE clas; char * end = class_name + sizeof(class_name) - 1; char * s; const char *n = name; clas = rb_cObject; for (s = class_name; 0 < len; n++, len--) { if (':' == *n) { *s = '\0'; n++; len--; if (':' != *n) { return Qundef; } if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { return Qundef; } s = class_name; } else if (end <= s) { return Qundef; } else { *s++ = *n; } } *s = '\0'; return resolve_classname(clas, class_name, auto_define); } static VALUE form_class(const char *str, size_t len) { return resolve_classpath(str, len, false); } static VALUE form_class_auto(const char *str, size_t len) { return resolve_classpath(str, len, true); } static void assure_cstack(Delegate d) { if (d->cend <= d->ctail + 1) { size_t cap = d->cend - d->chead; long pos = d->ctail - d->chead; cap *= 2; REALLOC_N(d->chead, struct _col, cap); d->ctail = d->chead + pos; d->cend = d->chead + cap; } } static void push(ojParser p, VALUE v) { Delegate d = (Delegate)p->ctx; if (d->vend <= d->vtail) { size_t cap = d->vend - d->vhead; long pos = d->vtail - d->vhead; cap *= 2; REALLOC_N(d->vhead, VALUE, cap); d->vtail = d->vhead + pos; d->vend = d->vhead + cap; } *d->vtail = v; d->vtail++; } static VALUE cache_key(ojParser p, Key kp) { Delegate d = (Delegate)p->ctx; if ((size_t)kp->len < sizeof(kp->buf)) { return cache_intern(d->key_cache, kp->buf, kp->len); } return cache_intern(d->key_cache, kp->key, kp->len); } static VALUE str_key(ojParser p, Key kp) { if ((size_t)kp->len < sizeof(kp->buf)) { return rb_str_freeze(rb_utf8_str_new(kp->buf, kp->len)); } return rb_str_freeze(rb_utf8_str_new(kp->key, kp->len)); } static VALUE sym_key(ojParser p, Key kp) { if ((size_t)kp->len < sizeof(kp->buf)) { return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->buf, kp->len))); } return rb_str_freeze(rb_str_intern(rb_utf8_str_new(kp->key, kp->len))); } static ID get_attr_id(ojParser p, Key kp) { Delegate d = (Delegate)p->ctx; if ((size_t)kp->len < sizeof(kp->buf)) { return (ID)cache_intern(d->attr_cache, kp->buf, kp->len); } return (ID)cache_intern(d->attr_cache, kp->key, kp->len); } static void push_key(ojParser p) { Delegate d = (Delegate)p->ctx; size_t klen = buf_len(&p->key); const char *key = buf_str(&p->key); if (d->kend <= d->ktail) { size_t cap = d->kend - d->khead; long pos = d->ktail - d->khead; cap *= 2; REALLOC_N(d->khead, union _key, cap); d->ktail = d->khead + pos; d->kend = d->khead + cap; } d->ktail->len = klen; if (klen < sizeof(d->ktail->buf)) { memcpy(d->ktail->buf, key, klen); d->ktail->buf[klen] = '\0'; } else { d->ktail->key = str_dup(key, klen); } d->ktail++; } static void push2(ojParser p, VALUE v) { Delegate d = (Delegate)p->ctx; if (d->vend <= d->vtail + 1) { size_t cap = d->vend - d->vhead; long pos = d->vtail - d->vhead; cap *= 2; REALLOC_N(d->vhead, VALUE, cap); d->vtail = d->vhead + pos; d->vend = d->vhead + cap; } *d->vtail = Qundef; // key place holder d->vtail++; *d->vtail = v; d->vtail++; } static void open_object(ojParser p) { Delegate d = (Delegate)p->ctx; assure_cstack(d); d->ctail->vi = d->vtail - d->vhead; d->ctail->ki = d->ktail - d->khead; d->ctail++; push(p, Qundef); } static void open_object_key(ojParser p) { Delegate d = (Delegate)p->ctx; push_key(p); assure_cstack(d); d->ctail->vi = d->vtail - d->vhead + 1; d->ctail->ki = d->ktail - d->khead; d->ctail++; push2(p, Qundef); } static void open_array(ojParser p) { Delegate d = (Delegate)p->ctx; assure_cstack(d); d->ctail->vi = d->vtail - d->vhead; d->ctail->ki = -1; d->ctail++; push(p, Qundef); } static void open_array_key(ojParser p) { Delegate d = (Delegate)p->ctx; push_key(p); assure_cstack(d); d->ctail->vi = d->vtail - d->vhead + 1; d->ctail->ki = -1; d->ctail++; push2(p, Qundef); } static void close_object(ojParser p) { VALUE * vp; Delegate d = (Delegate)p->ctx; d->ctail--; Col c = d->ctail; Key kp = d->khead + c->ki; VALUE * head = d->vhead + c->vi + 1; volatile VALUE obj = rb_hash_new(); #if HAVE_RB_HASH_BULK_INSERT for (vp = head; kp < d->ktail; kp++, vp += 2) { *vp = d->get_key(p, kp); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } rb_hash_bulk_insert(d->vtail - head, head, obj); #else for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } #endif d->ktail = d->khead + c->ki; d->vtail = head; head--; *head = obj; } static void close_object_class(ojParser p) { VALUE * vp; Delegate d = (Delegate)p->ctx; d->ctail--; Col c = d->ctail; Key kp = d->khead + c->ki; VALUE * head = d->vhead + c->vi + 1; volatile VALUE obj = rb_class_new_instance(0, NULL, d->hash_class); for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } d->ktail = d->khead + c->ki; d->vtail = head; head--; *head = obj; } static void close_object_create(ojParser p) { VALUE * vp; Delegate d = (Delegate)p->ctx; d->ctail--; Col c = d->ctail; Key kp = d->khead + c->ki; VALUE * head = d->vhead + c->vi; volatile VALUE obj; if (Qundef == *head) { head++; if (Qnil == d->hash_class) { obj = rb_hash_new(); #if HAVE_RB_HASH_BULK_INSERT for (vp = head; kp < d->ktail; kp++, vp += 2) { *vp = d->get_key(p, kp); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } rb_hash_bulk_insert(d->vtail - head, head, obj); #else for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } #endif } else { obj = rb_class_new_instance(0, NULL, d->hash_class); for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_funcall(obj, hset_id, 2, d->get_key(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } } } else { VALUE clas = *head; head++; if (!d->ignore_json_create && rb_respond_to(clas, oj_json_create_id)) { volatile VALUE arg = rb_hash_new(); #if HAVE_RB_HASH_BULK_INSERT for (vp = head; kp < d->ktail; kp++, vp += 2) { *vp = d->get_key(p, kp); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } rb_hash_bulk_insert(d->vtail - head, head, arg); #else for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_hash_aset(arg, d->get_key(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } #endif obj = rb_funcall(clas, oj_json_create_id, 1, arg); } else { obj = rb_class_new_instance(0, NULL, clas); for (vp = head; kp < d->ktail; kp++, vp += 2) { rb_ivar_set(obj, get_attr_id(p, kp), *(vp + 1)); if (sizeof(kp->buf) <= (size_t)kp->len) { xfree(kp->key); } } } } d->ktail = d->khead + c->ki; d->vtail = head; head--; *head = obj; } static void close_array(ojParser p) { Delegate d = (Delegate)p->ctx; d->ctail--; VALUE * head = d->vhead + d->ctail->vi + 1; volatile VALUE a = rb_ary_new_from_values(d->vtail - head, head); d->vtail = head; head--; *head = a; } static void close_array_class(ojParser p) { VALUE * vp; Delegate d = (Delegate)p->ctx; d->ctail--; VALUE * head = d->vhead + d->ctail->vi + 1; volatile VALUE a = rb_class_new_instance(0, NULL, d->array_class); for (vp = head; vp < d->vtail; vp++) { rb_funcall(a, ltlt_id, 1, *vp); } d->vtail = head; head--; *head = a; } static void noop(ojParser p) { } static void add_null(ojParser p) { push(p, Qnil); } static void add_null_key(ojParser p) { push_key(p); push2(p, Qnil); } static void add_true(ojParser p) { push(p, Qtrue); } static void add_true_key(ojParser p) { push_key(p); push2(p, Qtrue); } static void add_false(ojParser p) { push(p, Qfalse); } static void add_false_key(ojParser p) { push_key(p); push2(p, Qfalse); } static void add_int(ojParser p) { push(p, LONG2NUM(p->num.fixnum)); } static void add_int_key(ojParser p) { push_key(p); push2(p, LONG2NUM(p->num.fixnum)); } static void add_float(ojParser p) { push(p, rb_float_new(p->num.dub)); } static void add_float_key(ojParser p) { push_key(p); push2(p, rb_float_new(p->num.dub)); } static void add_float_as_big(ojParser p) { char buf[64]; // snprintf fails on ubuntu and macOS for long double // snprintf(buf, sizeof(buf), "%Lg", p->num.dub); sprintf(buf, "%Lg", p->num.dub); push(p, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new2(buf))); } static void add_float_as_big_key(ojParser p) { char buf[64]; // snprintf fails on ubuntu and macOS for long double // snprintf(buf, sizeof(buf), "%Lg", p->num.dub); sprintf(buf, "%Lg", p->num.dub); push_key(p); push2(p, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new2(buf))); } static void add_big(ojParser p) { push(p, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf)))); } static void add_big_key(ojParser p) { push_key(p); push2(p, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf)))); } static void add_big_as_float(ojParser p) { volatile VALUE big = rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))); push(p, rb_funcall(big, to_f_id, 0)); } static void add_big_as_float_key(ojParser p) { volatile VALUE big = rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))); push_key(p); push2(p, rb_funcall(big, to_f_id, 0)); } static void add_big_as_ruby(ojParser p) { push(p, rb_funcall(rb_str_new(buf_str(&p->buf), buf_len(&p->buf)), to_f_id, 0)); } static void add_big_as_ruby_key(ojParser p) { push_key(p); push2(p, rb_funcall(rb_str_new(buf_str(&p->buf), buf_len(&p->buf)), to_f_id, 0)); } static void add_str(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE rstr; const char * str = buf_str(&p->buf); size_t len = buf_len(&p->buf); if (len < d->cache_str) { rstr = cache_intern(d->str_cache, str, len); } else { rstr = rb_utf8_str_new(str, len); } push(p, rstr); } static void add_str_key(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE rstr; const char * str = buf_str(&p->buf); size_t len = buf_len(&p->buf); if (len < d->cache_str) { rstr = cache_intern(d->str_cache, str, len); } else { rstr = rb_utf8_str_new(str, len); } push_key(p); push2(p, rstr); } static void add_str_key_create(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE rstr; const char * str = buf_str(&p->buf); size_t len = buf_len(&p->buf); const char * key = buf_str(&p->key); size_t klen = buf_len(&p->key); if (klen == (size_t)d->create_id_len && 0 == strncmp(d->create_id, key, klen)) { Col c = d->ctail - 1; VALUE clas; if (NULL != d->class_cache) { clas = cache_intern(d->class_cache, str, len); } else { clas = resolve_classpath(str, len, MISS_AUTO == d->miss_class); } if (Qundef != clas) { *(d->vhead + c->vi) = clas; return; } if (MISS_RAISE == d->miss_class) { rb_raise(rb_eLoadError, "%s is not define", str); } } if (len < d->cache_str) { rstr = cache_intern(d->str_cache, str, len); } else { rstr = rb_utf8_str_new(str, len); } push_key(p); push2(p, rstr); } static VALUE result(ojParser p) { Delegate d = (Delegate)p->ctx; if (d->vhead < d->vtail) { return *d->vhead; } return Qnil; } static void start(ojParser p) { Delegate d = (Delegate)p->ctx; d->vtail = d->vhead; d->ctail = d->chead; d->ktail = d->khead; } static void dfree(ojParser p) { Delegate d = (Delegate)p->ctx; cache_free(d->str_cache); cache_free(d->attr_cache); if (NULL != d->sym_cache) { cache_free(d->sym_cache); } if (NULL != d->class_cache) { cache_free(d->class_cache); } xfree(d->vhead); xfree(d->chead); xfree(d->khead); xfree(d->create_id); xfree(p->ctx); p->ctx = NULL; } static void mark(ojParser p) { if (NULL == p->ctx) { return; } Delegate d = (Delegate)p->ctx; VALUE * vp; if (NULL == d) { return; } cache_mark(d->str_cache); if (NULL != d->sym_cache) { cache_mark(d->sym_cache); } if (NULL != d->class_cache) { cache_mark(d->class_cache); } for (vp = d->vhead; vp < d->vtail; vp++) { if (Qundef != *vp) { rb_gc_mark(*vp); } } } ///// options ///////////////////////////////////////////////////////////////// // Each option is handled by a separate function and then added to an assoc // list (struct opt}. The list is then iterated over until there is a name // match. This is done primarily to keep each option separate and easier to // understand instead of placing all in one large function. struct opt { const char *name; VALUE (*func)(ojParser p, VALUE value); }; static VALUE opt_array_class(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return d->array_class; } static VALUE opt_array_class_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qnil == value) { p->funcs[TOP_FUN].close_array = close_array; p->funcs[ARRAY_FUN].close_array = close_array; p->funcs[OBJECT_FUN].close_array = close_array; } else { rb_check_type(value, T_CLASS); if (!rb_method_boundp(value, ltlt_id, 1)) { rb_raise(rb_eArgError, "An array class must implement the << method."); } p->funcs[TOP_FUN].close_array = close_array_class; p->funcs[ARRAY_FUN].close_array = close_array_class; p->funcs[OBJECT_FUN].close_array = close_array_class; } d->array_class = value; return d->array_class; } static VALUE opt_cache_keys(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return d->cache_keys ? Qtrue : Qfalse; } static VALUE opt_cache_keys_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qtrue == value) { d->cache_keys = true; d->get_key = cache_key; if (NULL == d->sym_cache) { d->key_cache = d->str_cache; } else { d->key_cache = d->sym_cache; } } else { d->cache_keys = false; if (NULL == d->sym_cache) { d->get_key = str_key; } else { d->get_key = sym_key; } } return d->cache_keys ? Qtrue : Qfalse; } static VALUE opt_cache_strings(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return INT2NUM((int)d->cache_str); } static VALUE opt_cache_strings_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; int limit = NUM2INT(value); if (CACHE_MAX_KEY < limit) { limit = CACHE_MAX_KEY; } else if (limit < 0) { limit = 0; } d->cache_str = limit; return INT2NUM((int)d->cache_str); } static VALUE opt_cache_expunge(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return INT2NUM((int)d->cache_xrate); } static VALUE opt_cache_expunge_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; int rate = NUM2INT(value); if (rate < 0) { rate = 0; } else if (3 < rate) { rate = 3; } d->cache_xrate = (uint8_t)rate; cache_set_expunge_rate(d->str_cache, rate); cache_set_expunge_rate(d->attr_cache, rate); if (NULL != d->sym_cache) { cache_set_expunge_rate(d->sym_cache, rate); } return INT2NUM((int)rate); } static VALUE opt_capacity(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return ULONG2NUM(d->vend - d->vhead); } static VALUE opt_capacity_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; long cap = NUM2LONG(value); if (d->vend - d->vhead < cap) { long pos = d->vtail - d->vhead; REALLOC_N(d->vhead, VALUE, cap); d->vtail = d->vhead + pos; d->vend = d->vhead + cap; } if (d->kend - d->khead < cap) { long pos = d->ktail - d->khead; REALLOC_N(d->khead, union _key, cap); d->ktail = d->khead + pos; d->kend = d->khead + cap; } return ULONG2NUM(d->vend - d->vhead); } static VALUE opt_class_cache(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return (NULL != d->class_cache) ? Qtrue : Qfalse; } static VALUE opt_class_cache_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qtrue == value) { if (NULL == d->class_cache) { d->class_cache = cache_create(0, form_class_auto, MISS_AUTO == d->miss_class, false); } } else if (NULL != d->class_cache) { cache_free(d->class_cache); d->class_cache = NULL; } return (NULL != d->class_cache) ? Qtrue : Qfalse; } static VALUE opt_create_id(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (NULL == d->create_id) { return Qnil; } return rb_utf8_str_new(d->create_id, d->create_id_len); } static VALUE opt_create_id_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qnil == value) { d->create_id = NULL; d->create_id_len = 0; p->funcs[OBJECT_FUN].add_str = add_str_key; if (Qnil == d->hash_class) { p->funcs[TOP_FUN].close_object = close_object; p->funcs[ARRAY_FUN].close_object = close_object; p->funcs[OBJECT_FUN].close_object = close_object; } else { p->funcs[TOP_FUN].close_object = close_object_class; p->funcs[ARRAY_FUN].close_object = close_object_class; p->funcs[OBJECT_FUN].close_object = close_object_class; } } else { rb_check_type(value, T_STRING); size_t len = RSTRING_LEN(value); if (1 << sizeof(d->create_id_len) <= len) { rb_raise(rb_eArgError, "The create_id values is limited to %d bytes.", 1 << sizeof(d->create_id_len)); } d->create_id_len = (uint8_t)len; d->create_id = str_dup(RSTRING_PTR(value), len); p->funcs[OBJECT_FUN].add_str = add_str_key_create; p->funcs[TOP_FUN].close_object = close_object_create; p->funcs[ARRAY_FUN].close_object = close_object_create; p->funcs[OBJECT_FUN].close_object = close_object_create; } return opt_create_id(p, value); } static VALUE opt_decimal(ojParser p, VALUE value) { if (add_float_as_big == p->funcs[TOP_FUN].add_float) { return ID2SYM(rb_intern("bigdecimal")); } if (add_big == p->funcs[TOP_FUN].add_big) { return ID2SYM(rb_intern("auto")); } if (add_big_as_float == p->funcs[TOP_FUN].add_big) { return ID2SYM(rb_intern("float")); } if (add_big_as_ruby == p->funcs[TOP_FUN].add_big) { return ID2SYM(rb_intern("ruby")); } return Qnil; } static VALUE opt_decimal_set(ojParser p, VALUE value) { const char * mode; volatile VALUE s; switch (rb_type(value)) { case T_STRING: mode = RSTRING_PTR(value); break; case T_SYMBOL: s = rb_sym2str(value); mode = RSTRING_PTR(s); break; default: rb_raise(rb_eTypeError, "the decimal options must be a Symbol or String, not %s.", rb_class2name(rb_obj_class(value))); break; } if (0 == strcmp("auto", mode)) { p->funcs[TOP_FUN].add_big = add_big; p->funcs[ARRAY_FUN].add_big = add_big; p->funcs[OBJECT_FUN].add_big = add_big_key; p->funcs[TOP_FUN].add_float = add_float; p->funcs[ARRAY_FUN].add_float = add_float; p->funcs[OBJECT_FUN].add_float = add_float_key; return opt_decimal(p, Qnil); } if (0 == strcmp("bigdecimal", mode)) { p->funcs[TOP_FUN].add_big = add_big; p->funcs[ARRAY_FUN].add_big = add_big; p->funcs[OBJECT_FUN].add_big = add_big_key; p->funcs[TOP_FUN].add_float = add_float_as_big; p->funcs[ARRAY_FUN].add_float = add_float_as_big; p->funcs[OBJECT_FUN].add_float = add_float_as_big_key; return opt_decimal(p, Qnil); } if (0 == strcmp("float", mode)) { p->funcs[TOP_FUN].add_big = add_big_as_float; p->funcs[ARRAY_FUN].add_big = add_big_as_float; p->funcs[OBJECT_FUN].add_big = add_big_as_float_key; p->funcs[TOP_FUN].add_float = add_float; p->funcs[ARRAY_FUN].add_float = add_float; p->funcs[OBJECT_FUN].add_float = add_float_key; return opt_decimal(p, Qnil); } if (0 == strcmp("ruby", mode)) { p->funcs[TOP_FUN].add_big = add_big_as_ruby; p->funcs[ARRAY_FUN].add_big = add_big_as_ruby; p->funcs[OBJECT_FUN].add_big = add_big_as_ruby_key; p->funcs[TOP_FUN].add_float = add_float; p->funcs[ARRAY_FUN].add_float = add_float; p->funcs[OBJECT_FUN].add_float = add_float_key; return opt_decimal(p, Qnil); } rb_raise(rb_eArgError, "%s is not a valid option for the decimal option.", mode); return Qnil; } static VALUE opt_hash_class(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return d->hash_class; } static VALUE opt_hash_class_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qnil != value) { rb_check_type(value, T_CLASS); if (!rb_method_boundp(value, hset_id, 1)) { rb_raise(rb_eArgError, "A hash class must implement the []= method."); } } d->hash_class = value; if (NULL == d->create_id) { if (Qnil == value) { p->funcs[TOP_FUN].close_object = close_object; p->funcs[ARRAY_FUN].close_object = close_object; p->funcs[OBJECT_FUN].close_object = close_object; } else { p->funcs[TOP_FUN].close_object = close_object_class; p->funcs[ARRAY_FUN].close_object = close_object_class; p->funcs[OBJECT_FUN].close_object = close_object_class; } } return d->hash_class; } static VALUE opt_ignore_json_create(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return d->ignore_json_create ? Qtrue : Qfalse; } static VALUE opt_ignore_json_create_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; d->ignore_json_create = (Qtrue == value); return d->ignore_json_create ? Qtrue : Qfalse; } static VALUE opt_missing_class(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; switch (d->miss_class) { case MISS_AUTO: return ID2SYM(rb_intern("auto")); case MISS_RAISE: return ID2SYM(rb_intern("raise")); case MISS_IGNORE: default: return ID2SYM(rb_intern("ignore")); } } static VALUE opt_missing_class_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; const char * mode; volatile VALUE s; switch (rb_type(value)) { case T_STRING: mode = RSTRING_PTR(value); break; case T_SYMBOL: s = rb_sym2str(value); mode = RSTRING_PTR(s); break; default: rb_raise(rb_eTypeError, "the missing_class options must be a Symbol or String, not %s.", rb_class2name(rb_obj_class(value))); break; } if (0 == strcmp("auto", mode)) { d->miss_class = MISS_AUTO; if (NULL != d->class_cache) { cache_set_form(d->class_cache, form_class_auto); } } else if (0 == strcmp("ignore", mode)) { d->miss_class = MISS_IGNORE; if (NULL != d->class_cache) { cache_set_form(d->class_cache, form_class); } } else if (0 == strcmp("raise", mode)) { d->miss_class = MISS_RAISE; if (NULL != d->class_cache) { cache_set_form(d->class_cache, form_class); } } else { rb_raise(rb_eArgError, "%s is not a valid value for the missing_class option.", mode); } return opt_missing_class(p, value); } static VALUE opt_omit_null(ojParser p, VALUE value) { return (noop == p->funcs[OBJECT_FUN].add_null) ? Qtrue : Qfalse; } static VALUE opt_omit_null_set(ojParser p, VALUE value) { if (Qtrue == value) { p->funcs[OBJECT_FUN].add_null = noop; } else { p->funcs[OBJECT_FUN].add_null = add_null_key; } return (noop == p->funcs[OBJECT_FUN].add_null) ? Qtrue : Qfalse; } static VALUE opt_symbol_keys(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; return (NULL != d->sym_cache) ? Qtrue : Qfalse; } static VALUE opt_symbol_keys_set(ojParser p, VALUE value) { Delegate d = (Delegate)p->ctx; if (Qtrue == value) { d->sym_cache = cache_create(0, form_sym, true, false); cache_set_expunge_rate(d->sym_cache, d->cache_xrate); d->key_cache = d->sym_cache; if (!d->cache_keys) { d->get_key = sym_key; } } else { if (NULL != d->sym_cache) { cache_free(d->sym_cache); d->sym_cache = NULL; } if (!d->cache_keys) { d->get_key = str_key; } } return (NULL != d->sym_cache) ? Qtrue : Qfalse; } static VALUE option(ojParser p, const char *key, VALUE value) { struct opt *op; struct opt opts[] = { {.name = "array_class", .func = opt_array_class}, {.name = "array_class=", .func = opt_array_class_set}, {.name = "cache_keys", .func = opt_cache_keys}, {.name = "cache_keys=", .func = opt_cache_keys_set}, {.name = "cache_strings", .func = opt_cache_strings}, {.name = "cache_strings=", .func = opt_cache_strings_set}, {.name = "cache_expunge", .func = opt_cache_expunge}, {.name = "cache_expunge=", .func = opt_cache_expunge_set}, {.name = "capacity", .func = opt_capacity}, {.name = "capacity=", .func = opt_capacity_set}, {.name = "class_cache", .func = opt_class_cache}, {.name = "class_cache=", .func = opt_class_cache_set}, {.name = "create_id", .func = opt_create_id}, {.name = "create_id=", .func = opt_create_id_set}, {.name = "decimal", .func = opt_decimal}, {.name = "decimal=", .func = opt_decimal_set}, {.name = "hash_class", .func = opt_hash_class}, {.name = "hash_class=", .func = opt_hash_class_set}, {.name = "ignore_json_create", .func = opt_ignore_json_create}, {.name = "ignore_json_create=", .func = opt_ignore_json_create_set}, {.name = "missing_class", .func = opt_missing_class}, {.name = "missing_class=", .func = opt_missing_class_set}, {.name = "omit_null", .func = opt_omit_null}, {.name = "omit_null=", .func = opt_omit_null_set}, {.name = "symbol_keys", .func = opt_symbol_keys}, {.name = "symbol_keys=", .func = opt_symbol_keys_set}, {.name = NULL}, }; for (op = opts; NULL != op->name; op++) { if (0 == strcmp(key, op->name)) { return op->func(p, value); } } rb_raise(rb_eArgError, "%s is not an option for the Usual delegate", key); return Qnil; // Never reached due to the raise but required by the compiler. } ///// the set up ////////////////////////////////////////////////////////////// void oj_set_parser_usual(ojParser p) { Delegate d = ALLOC(struct _delegate); int cap = 4096; d->vhead = ALLOC_N(VALUE, cap); d->vend = d->vhead + cap; d->vtail = d->vhead; d->khead = ALLOC_N(union _key, cap); d->kend = d->khead + cap; d->ktail = d->khead; cap = 256; d->chead = ALLOC_N(struct _col, cap); d->cend = d->chead + cap; d->ctail = d->chead; d->get_key = cache_key; d->cache_keys = true; d->ignore_json_create = false; d->cache_str = 6; d->array_class = Qnil; d->hash_class = Qnil; d->create_id = NULL; d->create_id_len = 0; d->miss_class = MISS_IGNORE; d->cache_xrate = 1; Funcs f = &p->funcs[TOP_FUN]; f->add_null = add_null; f->add_true = add_true; f->add_false = add_false; f->add_int = add_int; f->add_float = add_float; f->add_big = add_big; f->add_str = add_str; f->open_array = open_array; f->close_array = close_array; f->open_object = open_object; f->close_object = close_object; f = &p->funcs[ARRAY_FUN]; f->add_null = add_null; f->add_true = add_true; f->add_false = add_false; f->add_int = add_int; f->add_float = add_float; f->add_big = add_big; f->add_str = add_str; f->open_array = open_array; f->close_array = close_array; f->open_object = open_object; f->close_object = close_object; f = &p->funcs[OBJECT_FUN]; f->add_null = add_null_key; f->add_true = add_true_key; f->add_false = add_false_key; f->add_int = add_int_key; f->add_float = add_float_key; f->add_big = add_big_key; f->add_str = add_str_key; f->open_array = open_array_key; f->close_array = close_array; f->open_object = open_object_key; f->close_object = close_object; d->str_cache = cache_create(0, form_str, true, false); d->attr_cache = cache_create(0, form_attr, false, false); d->sym_cache = NULL; d->class_cache = NULL; d->key_cache = d->str_cache; p->ctx = (void *)d; p->option = option; p->result = result; p->free = dfree; p->mark = mark; p->start = start; if (0 == to_f_id) { to_f_id = rb_intern("to_f"); } if (0 == ltlt_id) { ltlt_id = rb_intern("<<"); } if (0 == hset_id) { hset_id = rb_intern("[]="); } } oj-3.13.9/ext/oj/dump_leaf.c0000644000004100000410000001411614136373754015577 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include "dump.h" #include "oj.h" static void dump_leaf(Leaf leaf, int depth, Out out); static void grow(Out out, size_t len) { size_t size = out->end - out->buf; long pos = out->cur - out->buf; char * buf; size *= 2; if (size <= len * 2 + pos) { size += len; } if (out->allocated) { buf = REALLOC_N(out->buf, char, (size + BUFFER_EXTRA)); } else { buf = ALLOC_N(char, (size + BUFFER_EXTRA)); out->allocated = true; memcpy(buf, out->buf, out->end - out->buf + BUFFER_EXTRA); } if (0 == buf) { rb_raise(rb_eNoMemError, "Failed to create string. [%d:%s]\n", ENOSPC, strerror(ENOSPC)); } out->buf = buf; out->end = buf + size; out->cur = out->buf + pos; } inline static void dump_chars(const char *s, size_t size, Out out) { if (out->end - out->cur <= (long)size) { grow(out, size); } memcpy(out->cur, s, size); out->cur += size; *out->cur = '\0'; } static void dump_leaf_str(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: oj_dump_cstr(leaf->str, strlen(leaf->str), 0, 0, out); break; case RUBY_VAL: oj_dump_cstr(rb_string_value_cstr(&leaf->value), (int)RSTRING_LEN(leaf->value), 0, 0, out); break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_fixnum(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: dump_chars(leaf->str, strlen(leaf->str), out); break; case RUBY_VAL: if (T_BIGNUM == rb_type(leaf->value)) { oj_dump_bignum(leaf->value, 0, out, false); } else { oj_dump_fixnum(leaf->value, 0, out, false); } break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_float(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: dump_chars(leaf->str, strlen(leaf->str), out); break; case RUBY_VAL: oj_dump_float(leaf->value, 0, out, false); break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_array(Leaf leaf, int depth, Out out) { size_t size; int d2 = depth + 1; size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '['; if (0 == leaf->elements) { *out->cur++ = ']'; } else { Leaf first = leaf->elements->next; Leaf e = first; size = d2 * out->indent + 2; do { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_leaf(e, d2, out); if (e->next != first) { *out->cur++ = ','; } e = e->next; } while (e != first); size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = ']'; } *out->cur = '\0'; } static void dump_leaf_hash(Leaf leaf, int depth, Out out) { size_t size; int d2 = depth + 1; size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '{'; if (0 == leaf->elements) { *out->cur++ = '}'; } else { Leaf first = leaf->elements->next; Leaf e = first; size = d2 * out->indent + 2; do { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); oj_dump_cstr(e->key, strlen(e->key), 0, 0, out); *out->cur++ = ':'; dump_leaf(e, d2, out); if (e->next != first) { *out->cur++ = ','; } e = e->next; } while (e != first); size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_leaf(Leaf leaf, int depth, Out out) { switch (leaf->rtype) { case T_NIL: oj_dump_nil(Qnil, 0, out, false); break; case T_TRUE: oj_dump_true(Qtrue, 0, out, false); break; case T_FALSE: oj_dump_false(Qfalse, 0, out, false); break; case T_STRING: dump_leaf_str(leaf, out); break; case T_FIXNUM: dump_leaf_fixnum(leaf, out); break; case T_FLOAT: dump_leaf_float(leaf, out); break; case T_ARRAY: dump_leaf_array(leaf, depth, out); break; case T_HASH: dump_leaf_hash(leaf, depth, out); break; default: rb_raise(rb_eTypeError, "Unexpected type %02x.\n", leaf->rtype); break; } } void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out) { if (0 == out->buf) { out->buf = ALLOC_N(char, 4096); out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors out->allocated = true; } out->cur = out->buf; out->circ_cnt = 0; out->opts = copts; out->hash_cnt = 0; out->indent = copts->indent; dump_leaf(leaf, 0, out); } void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts) { char buf[4096]; struct _out out; size_t size; FILE * f; out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = false; out.omit_nil = copts->dump_opts.omit_nil; oj_dump_leaf_to_json(leaf, copts, &out); size = out.cur - out.buf; if (0 == (f = fopen(path, "w"))) { rb_raise(rb_eIOError, "%s\n", strerror(errno)); } if (size != fwrite(out.buf, 1, size, f)) { int err = ferror(f); rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err)); } if (out.allocated) { xfree(out.buf); } fclose(f); } oj-3.13.9/ext/oj/resolve.h0000644000004100000410000000065014136373754015325 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_RESOLVE_H #define OJ_RESOLVE_H #include "ruby.h" extern VALUE oj_name2class(ParseInfo pi, const char *name, size_t len, int auto_define, VALUE error_class); extern VALUE oj_name2struct(ParseInfo pi, VALUE nameVal, VALUE error_class); #endif /* OJ_RESOLVE_H */ oj-3.13.9/ext/oj/dump_strict.c0000644000004100000410000003255014136373754016202 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #include #include #include "dump.h" #include "trace.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) typedef unsigned long ulong; static const char inf_val[] = INF_VAL; static const char ninf_val[] = NINF_VAL; static const char nan_val[] = NAN_VAL; static void raise_strict(VALUE obj) { rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(rb_obj_class(obj))); } // Removed dependencies on math due to problems with CentOS 5.4. static void dump_float(VALUE obj, int depth, Out out, bool as_ok) { char buf[64]; char * b; double d = rb_num2dbl(obj); int cnt = 0; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else { NanDump nd = out->opts->dump_opts.nan_dump; if (AutoNan == nd) { nd = RaiseNan; } if (OJ_INFINITY == d) { switch (nd) { case RaiseNan: case WordNan: raise_strict(obj); break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, inf_val); cnt = sizeof(inf_val) - 1; break; } } else if (-OJ_INFINITY == d) { switch (nd) { case RaiseNan: case WordNan: raise_strict(obj); break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, ninf_val); cnt = sizeof(ninf_val) - 1; break; } } else if (isnan(d)) { switch (nd) { case RaiseNan: case WordNan: raise_strict(obj); break; case NullNan: strcpy(buf, "null"); cnt = 4; break; case HugeNan: default: strcpy(buf, nan_val); cnt = sizeof(nan_val) - 1; break; } } else if (d == (double)(long long int)d) { cnt = snprintf(buf, sizeof(buf), "%.1f", d); } else if (0 == out->opts->float_prec) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); cnt = (int)RSTRING_LEN(rstr); if ((int)sizeof(buf) <= cnt) { cnt = sizeof(buf) - 1; } memcpy(buf, RSTRING_PTR(rstr), cnt); buf[cnt] = '\0'; } else { cnt = oj_dump_float_printf(buf, sizeof(buf), obj, d, out->opts->float_fmt); } } assure_size(out, cnt); for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; if (Yes == out->opts->circular) { if (0 > oj_check_circular(a, out)) { oj_dump_nil(Qnil, 0, out, false); return; } } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; size = 2; assure_size(out, size); if (0 == cnt) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } if (NullMode == out->opts->mode) { oj_dump_null_val(rb_ary_entry(a, i), d2, out); } else { oj_dump_strict_val(rb_ary_entry(a, i), d2, out); } if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { // printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, // out->opts->dump_opts->indent); if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; long size; int rtype = rb_type(key); if (rtype != T_STRING && rtype != T_SYMBOL) { rb_raise(rb_eTypeError, "In :strict and :null mode all Hash keys must be Strings or Symbols, not %s.\n", rb_class2name(rb_obj_class(key))); } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } if (!out->opts->dump_opts.use) { size = depth * out->indent + 1; assure_size(out, size); fill_indent(out, depth); if (rtype == T_STRING) { oj_dump_str(key, 0, out, false); } else { oj_dump_sym(key, 0, out, false); } *out->cur++ = ':'; } else { size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1; assure_size(out, size); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } if (rtype == T_STRING) { oj_dump_str(key, 0, out, false); } else { oj_dump_sym(key, 0, out, false); } size = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; assure_size(out, size); if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } } if (NullMode == out->opts->mode) { oj_dump_null_val(value, depth, out); } else { oj_dump_strict_val(value, depth, out); } out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; size_t size; if (Yes == out->opts->circular) { if (0 > oj_check_circular(obj, out)) { oj_dump_nil(Qnil, 0, out, false); return; } } cnt = (int)RHASH_SIZE(obj); size = depth * out->indent + 2; assure_size(out, 2); *out->cur++ = '{'; if (0 == cnt) { *out->cur++ = '}'; } else { out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, size); fill_indent(out, depth); } else { size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1; assure_size(out, size); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_data_strict(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_raw(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), out); } else { raise_strict(obj); } } static void dump_data_null(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_raw(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), out); } else { oj_dump_nil(Qnil, depth, out, false); } } static DumpFunc strict_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_data_strict, // RUBY_T_OBJECT = 0x01, NULL, // RUBY_T_CLASS = 0x02, NULL, // RUBY_T_MODULE = 0x03, dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, NULL, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, NULL, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_data_strict, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, NULL, // RUBY_T_COMPLEX = 0x0e, NULL, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_strict_val(VALUE obj, int depth, Out out) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = strict_funcs[type]; if (NULL != f) { f(obj, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } raise_strict(obj); } static DumpFunc null_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_data_null, // RUBY_T_OBJECT = 0x01, NULL, // RUBY_T_CLASS = 0x02, NULL, // RUBY_T_MODULE = 0x03, dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, NULL, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, NULL, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_data_null, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, NULL, // RUBY_T_COMPLEX = 0x0e, NULL, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_null_val(VALUE obj, int depth, Out out) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = null_funcs[type]; if (NULL != f) { f(obj, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } oj-3.13.9/ext/oj/scp.c0000644000004100000410000001355414136373754014435 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #include #include "encode.h" #include "intern.h" #include "oj.h" #include "parse.h" static VALUE noop_start(ParseInfo pi) { return Qnil; } static void noop_end(ParseInfo pi) { } static void noop_add_value(ParseInfo pi, VALUE val) { } static void noop_add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { } static void noop_add_num(ParseInfo pi, NumInfo ni) { } static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) { return Qundef; } static void noop_hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { } static void noop_hash_set_num(ParseInfo pi, Val kval, NumInfo ni) { } static void noop_hash_set_value(ParseInfo pi, Val kval, VALUE value) { } static void noop_array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { } static void noop_array_append_num(ParseInfo pi, NumInfo ni) { } static void noop_array_append_value(ParseInfo pi, VALUE value) { } static void add_value(ParseInfo pi, VALUE val) { rb_funcall(pi->handler, oj_add_value_id, 1, val); } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall(pi->handler, oj_add_value_id, 1, rstr); } static void add_num(ParseInfo pi, NumInfo ni) { rb_funcall(pi->handler, oj_add_value_id, 1, oj_num_as_value(ni)); } static VALUE start_hash(ParseInfo pi) { return rb_funcall(pi->handler, oj_hash_start_id, 0); } static void end_hash(ParseInfo pi) { rb_funcall(pi->handler, oj_hash_end_id, 0); } static VALUE start_array(ParseInfo pi) { return rb_funcall(pi->handler, oj_array_start_id, 0); } static void end_array(ParseInfo pi) { rb_funcall(pi->handler, oj_array_end_id, 0); } static VALUE hash_key(ParseInfo pi, const char *key, size_t klen) { return rb_funcall(pi->handler, oj_hash_key_id, 1, rb_str_new(key, klen)); } static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall(pi->handler, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, kval), rstr); } static void hash_set_num(ParseInfo pi, Val kval, NumInfo ni) { rb_funcall(pi->handler, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, kval), oj_num_as_value(ni)); } static void hash_set_value(ParseInfo pi, Val kval, VALUE value) { rb_funcall(pi->handler, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, kval), value); } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall(pi->handler, oj_array_append_id, 2, stack_peek(&pi->stack)->val, rstr); } static void array_append_num(ParseInfo pi, NumInfo ni) { rb_funcall(pi->handler, oj_array_append_id, 2, stack_peek(&pi->stack)->val, oj_num_as_value(ni)); } static void array_append_value(ParseInfo pi, VALUE value) { rb_funcall(pi->handler, oj_array_append_id, 2, stack_peek(&pi->stack)->val, value); } VALUE oj_sc_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; VALUE input = argv[1]; parse_info_init(&pi); pi.err_class = Qnil; pi.max_depth = 0; pi.options = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &pi.options); } if (rb_block_given_p()) { pi.proc = Qnil; } else { pi.proc = Qundef; } pi.handler = *argv; pi.start_hash = rb_respond_to(pi.handler, oj_hash_start_id) ? start_hash : noop_start; pi.end_hash = rb_respond_to(pi.handler, oj_hash_end_id) ? end_hash : noop_end; pi.hash_key = rb_respond_to(pi.handler, oj_hash_key_id) ? hash_key : noop_hash_key; pi.start_array = rb_respond_to(pi.handler, oj_array_start_id) ? start_array : noop_start; pi.end_array = rb_respond_to(pi.handler, oj_array_end_id) ? end_array : noop_end; if (rb_respond_to(pi.handler, oj_hash_set_id)) { pi.hash_set_value = hash_set_value; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.expect_value = 1; } else { pi.hash_set_value = noop_hash_set_value; pi.hash_set_cstr = noop_hash_set_cstr; pi.hash_set_num = noop_hash_set_num; pi.expect_value = 0; } if (rb_respond_to(pi.handler, oj_array_append_id)) { pi.array_append_value = array_append_value; pi.array_append_cstr = array_append_cstr; pi.array_append_num = array_append_num; pi.expect_value = 1; } else { pi.array_append_value = noop_array_append_value; pi.array_append_cstr = noop_array_append_cstr; pi.array_append_num = noop_array_append_num; pi.expect_value = 0; } if (rb_respond_to(pi.handler, oj_add_value_id)) { pi.add_cstr = add_cstr; pi.add_num = add_num; pi.add_value = add_value; pi.expect_value = 1; } else { pi.add_cstr = noop_add_cstr; pi.add_num = noop_add_num; pi.add_value = noop_add_value; pi.expect_value = 0; } pi.has_callbacks = true; if (T_STRING == rb_type(input)) { return oj_pi_parse(argc - 1, argv + 1, &pi, 0, 0, 1); } else { return oj_pi_sparse(argc - 1, argv + 1, &pi, 0); } } oj-3.13.9/ext/oj/cache8.h0000644000004100000410000000105014136373754014774 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_CACHE8_H #define OJ_CACHE8_H #include "ruby.h" #include "stdint.h" typedef struct _cache8 *Cache8; typedef uint64_t slot_t; typedef uint64_t sid_t; extern void oj_cache8_new(Cache8 *cache); extern void oj_cache8_delete(Cache8 cache); extern slot_t oj_cache8_get(Cache8 cache, sid_t key, slot_t **slot); extern void oj_cache8_print(Cache8 cache); #endif /* OJ_CACHE8_H */ oj-3.13.9/ext/oj/circarray.c0000644000004100000410000000300714136373754015617 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "circarray.h" CircArray oj_circ_array_new() { CircArray ca; if (0 == (ca = ALLOC(struct _circArray))) { rb_raise(rb_eNoMemError, "not enough memory\n"); } ca->objs = ca->obj_array; ca->size = sizeof(ca->obj_array) / sizeof(VALUE); ca->cnt = 0; return ca; } void oj_circ_array_free(CircArray ca) { if (ca->objs != ca->obj_array) { xfree(ca->objs); } xfree(ca); } void oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id) { if (0 < id && 0 != ca) { unsigned long i; if (ca->size < id) { unsigned long cnt = id + 512; if (ca->objs == ca->obj_array) { if (0 == (ca->objs = ALLOC_N(VALUE, cnt))) { rb_raise(rb_eNoMemError, "not enough memory\n"); } memcpy(ca->objs, ca->obj_array, sizeof(VALUE) * ca->cnt); } else { REALLOC_N(ca->objs, VALUE, cnt); } ca->size = cnt; } id--; for (i = ca->cnt; i < id; i++) { ca->objs[i] = Qnil; } ca->objs[id] = obj; if (ca->cnt <= id) { ca->cnt = id + 1; } } } VALUE oj_circ_array_get(CircArray ca, unsigned long id) { VALUE obj = Qnil; if (id <= ca->cnt && 0 != ca) { obj = ca->objs[id - 1]; } return obj; } oj-3.13.9/ext/oj/wab.c0000644000004100000410000004250114136373754014413 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #include "dump.h" #include "encode.h" #include "err.h" #include "intern.h" #include "oj.h" #include "parse.h" #include "trace.h" #include "util.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) static char hex_chars[256] = "\ ................................\ ................xxxxxxxxxx......\ .xxxxxx.........................\ .xxxxxx.........................\ ................................\ ................................\ ................................\ ................................"; static VALUE wab_uuid_clas = Qundef; static VALUE uri_clas = Qundef; static VALUE uri_http_clas = Qundef; ///// dump functions ///// static VALUE resolve_wab_uuid_class() { if (Qundef == wab_uuid_clas) { volatile VALUE wab_module; wab_uuid_clas = Qnil; if (rb_const_defined_at(rb_cObject, rb_intern("WAB"))) { wab_module = rb_const_get_at(rb_cObject, rb_intern("WAB")); if (rb_const_defined_at(wab_module, rb_intern("UUID"))) { wab_uuid_clas = rb_const_get(wab_module, rb_intern("UUID")); } } } return wab_uuid_clas; } static VALUE resolve_uri_class() { if (Qundef == uri_clas) { uri_clas = Qnil; if (rb_const_defined_at(rb_cObject, rb_intern("URI"))) { uri_clas = rb_const_get_at(rb_cObject, rb_intern("URI")); } } return uri_clas; } static VALUE resolve_uri_http_class() { if (Qundef == uri_http_clas) { volatile VALUE uri_module; uri_http_clas = Qnil; if (rb_const_defined_at(rb_cObject, rb_intern("URI"))) { uri_module = rb_const_get_at(rb_cObject, rb_intern("URI")); if (rb_const_defined_at(uri_module, rb_intern("HTTP"))) { uri_http_clas = rb_const_get(uri_module, rb_intern("HTTP")); } } } return uri_http_clas; } static void raise_wab(VALUE obj) { rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in wab mode.\n", rb_class2name(rb_obj_class(obj))); } // Removed dependencies on math due to problems with CentOS 5.4. static void dump_float(VALUE obj, int depth, Out out, bool as_ok) { char buf[64]; char * b; double d = rb_num2dbl(obj); int cnt = 0; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else { if (OJ_INFINITY == d || -OJ_INFINITY == d || isnan(d)) { raise_wab(obj); } else if (d == (double)(long long int)d) { cnt = snprintf(buf, sizeof(buf), "%.1f", d); } else { cnt = snprintf(buf, sizeof(buf), "%0.16g", d); } } assure_size(out, cnt); for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; size = 2; assure_size(out, size); if (0 == cnt) { *out->cur++ = ']'; } else { size = d2 * out->indent + 2; cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); fill_indent(out, d2); oj_dump_wab_val(rb_ary_entry(a, i), d2, out); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); fill_indent(out, depth); *out->cur++ = ']'; } *out->cur = '\0'; } static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; long size; int rtype = rb_type(key); if (rtype != T_SYMBOL) { rb_raise(rb_eTypeError, "In :wab mode all Hash keys must be Symbols, not %s.\n", rb_class2name(rb_obj_class(key))); } size = depth * out->indent + 1; assure_size(out, size); fill_indent(out, depth); oj_dump_sym(key, 0, out, false); *out->cur++ = ':'; oj_dump_wab_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; size_t size; cnt = (int)RHASH_SIZE(obj); size = depth * out->indent + 2; assure_size(out, 2); *out->cur++ = '{'; if (0 == cnt) { *out->cur++ = '}'; } else { out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } assure_size(out, size); fill_indent(out, depth); *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_time(VALUE obj, Out out) { char buf[64]; struct _timeInfo ti; int len; time_t sec; long long nsec; #ifdef HAVE_RB_TIME_TIMESPEC if (16 <= sizeof(struct timespec)) { struct timespec ts = rb_time_timespec(obj); sec = ts.tv_sec; nsec = ts.tv_nsec; } else { sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } #else sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #endif assure_size(out, 36); // 2012-01-05T23:58:07.123456000Z sec_as_time(sec, &ti); len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ", ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, (long)nsec); oj_dump_cstr(buf, len, 0, 0, out); } static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { volatile VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { dump_time(obj, out); } else if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_raw(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), out); } else if (resolve_wab_uuid_class() == clas) { oj_dump_str(rb_funcall(obj, oj_to_s_id, 0), depth, out, false); } else if (resolve_uri_http_class() == clas) { oj_dump_str(rb_funcall(obj, oj_to_s_id, 0), depth, out, false); } else { raise_wab(obj); } } static DumpFunc wab_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, NULL, // RUBY_T_CLASS = 0x02, NULL, // RUBY_T_MODULE = 0x03, dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, NULL, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, NULL, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_obj, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, NULL, // RUBY_T_COMPLEX = 0x0e, NULL, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_wab_val(VALUE obj, int depth, Out out) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = wab_funcs[type]; if (NULL != f) { f(obj, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } raise_wab(obj); } ///// load functions ///// static VALUE calc_hash_key(ParseInfo pi, Val parent) { volatile VALUE rkey = parent->key_val; if (Qundef != rkey) { rkey = oj_encode(rkey); rkey = rb_str_intern(rkey); return rkey; } if (Yes == pi->options.cache_keys) { rkey = oj_sym_intern(parent->key, parent->klen); } else { #if HAVE_RB_ENC_INTERNED_STR rkey = rb_enc_interned_str(parent->key, parent->klen, oj_utf8_encoding); #else rkey = rb_utf8_str_new(parent->key, parent->klen); rkey = rb_str_intern(rkey); OBJ_FREEZE(rkey); #endif } return rkey; } static void hash_end(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static void array_end(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_array_end(pi, __FILE__, __LINE__); } } static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) { return Qundef; } static void add_value(ParseInfo pi, VALUE val) { if (Yes == pi->options.trace) { oj_trace_parse_call("add_value", pi, __FILE__, __LINE__, val); } pi->stack.head->val = val; } // 123e4567-e89b-12d3-a456-426655440000 static bool uuid_check(const char *str, int len) { int i; for (i = 0; i < 8; i++, str++) { if ('x' != hex_chars[*(uint8_t *)str]) { return false; } } str++; for (i = 0; i < 4; i++, str++) { if ('x' != hex_chars[*(uint8_t *)str]) { return false; } } str++; for (i = 0; i < 4; i++, str++) { if ('x' != hex_chars[*(uint8_t *)str]) { return false; } } str++; for (i = 0; i < 4; i++, str++) { if ('x' != hex_chars[*(uint8_t *)str]) { return false; } } str++; for (i = 0; i < 12; i++, str++) { if ('x' != hex_chars[*(uint8_t *)str]) { return false; } } return true; } static const char *read_num(const char *s, int len, int *vp) { uint32_t v = 0; for (; 0 < len; len--, s++) { if ('0' <= *s && *s <= '9') { v = v * 10 + *s - '0'; } else { return NULL; } } *vp = (int)v; return s; } static VALUE time_parse(const char *s, int len) { struct tm tm; bool neg = false; long nsecs = 0; int i; time_t secs; memset(&tm, 0, sizeof(tm)); if ('-' == *s) { s++; neg = true; } if (NULL == (s = read_num(s, 4, &tm.tm_year))) { return Qnil; } if (neg) { tm.tm_year = -tm.tm_year; neg = false; } tm.tm_year -= 1900; s++; if (NULL == (s = read_num(s, 2, &tm.tm_mon))) { return Qnil; } tm.tm_mon--; s++; if (NULL == (s = read_num(s, 2, &tm.tm_mday))) { return Qnil; } s++; if (NULL == (s = read_num(s, 2, &tm.tm_hour))) { return Qnil; } s++; if (NULL == (s = read_num(s, 2, &tm.tm_min))) { return Qnil; } s++; if (NULL == (s = read_num(s, 2, &tm.tm_sec))) { return Qnil; } s++; for (i = 9; 0 < i; i--, s++) { if ('0' <= *s && *s <= '9') { nsecs = nsecs * 10 + *s - '0'; } else { return Qnil; } } #if IS_WINDOWS secs = (time_t)mktime(&tm); memset(&tm, 0, sizeof(tm)); tm.tm_year = 70; tm.tm_mday = 1; secs -= (time_t)mktime(&tm); #else secs = (time_t)timegm(&tm); #endif return rb_funcall(rb_time_nano_new(secs, nsecs), oj_utc_id, 0); } static VALUE protect_uri(VALUE rstr) { return rb_funcall(resolve_uri_class(), oj_parse_id, 1, rstr); } static VALUE cstr_to_rstr(ParseInfo pi, const char *str, size_t len) { volatile VALUE v = Qnil; if (30 == len && '-' == str[4] && '-' == str[7] && 'T' == str[10] && ':' == str[13] && ':' == str[16] && '.' == str[19] && 'Z' == str[29]) { if (Qnil != (v = time_parse(str, (int)len))) { return v; } } if (36 == len && '-' == str[8] && '-' == str[13] && '-' == str[18] && '-' == str[23] && uuid_check(str, (int)len) && Qnil != resolve_wab_uuid_class()) { return rb_funcall(wab_uuid_clas, oj_new_id, 1, rb_str_new(str, len)); } if (7 < len && 0 == strncasecmp("http://", str, 7)) { int err = 0; v = rb_str_new(str, len); volatile VALUE uri = rb_protect(protect_uri, v, &err); if (0 == err) { return uri; } } return oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { pi->stack.head->val = cstr_to_rstr(pi, str, len); if (Yes == pi->options.trace) { oj_trace_parse_call("add_string", pi, __FILE__, __LINE__, pi->stack.head->val); } } static void add_num(ParseInfo pi, NumInfo ni) { if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } pi->stack.head->val = oj_num_as_value(ni); if (Yes == pi->options.trace) { oj_trace_parse_call("add_number", pi, __FILE__, __LINE__, pi->stack.head->val); } } static VALUE start_hash(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_in("start_hash", pi, __FILE__, __LINE__); } if (Qnil != pi->options.hash_class) { return rb_class_new_instance(0, NULL, pi->options.hash_class); } return rb_hash_new(); } static void hash_set_cstr(ParseInfo pi, Val parent, const char *str, size_t len, const char *orig) { volatile VALUE rval = cstr_to_rstr(pi, str, len); rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), rval); if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rval); } } static void hash_set_num(ParseInfo pi, Val parent, NumInfo ni) { volatile VALUE rval = Qnil; if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } rval = oj_num_as_value(ni); rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), rval); if (Yes == pi->options.trace) { oj_trace_parse_call("set_number", pi, __FILE__, __LINE__, rval); } } static void hash_set_value(ParseInfo pi, Val parent, VALUE value) { rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), value); if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value); } } static VALUE start_array(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_in("start_array", pi, __FILE__, __LINE__); } return rb_ary_new(); } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rval = cstr_to_rstr(pi, str, len); rb_ary_push(stack_peek(&pi->stack)->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, rval); } } static void array_append_num(ParseInfo pi, NumInfo ni) { volatile VALUE rval = Qnil; if (ni->infinity || ni->nan) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); } rval = oj_num_as_value(ni); rb_ary_push(stack_peek(&pi->stack)->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval); } } static void array_append_value(ParseInfo pi, VALUE value) { rb_ary_push(stack_peek(&pi->stack)->val, value); if (Yes == pi->options.trace) { oj_trace_parse_call("append_value", pi, __FILE__, __LINE__, value); } } void oj_set_wab_callbacks(ParseInfo pi) { pi->start_hash = start_hash; pi->end_hash = hash_end; pi->hash_key = noop_hash_key; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->start_array = start_array; pi->end_array = array_end; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; pi->array_append_value = array_append_value; pi->add_cstr = add_cstr; pi->add_num = add_num; pi->add_value = add_value; pi->expect_value = 1; } VALUE oj_wab_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_wab_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, true); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_wab_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_wab_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, json, len, true); } oj-3.13.9/ext/oj/encode.h0000644000004100000410000000060414136373754015102 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_ENCODE_H #define OJ_ENCODE_H #include "oj.h" #include "ruby.h" #include "ruby/encoding.h" static inline VALUE oj_encode(VALUE rstr) { rb_enc_associate(rstr, oj_utf8_encoding); return rstr; } #endif /* OJ_ENCODE_H */ oj-3.13.9/ext/oj/stream_writer.c0000644000004100000410000002553514136373754016541 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include "encode.h" extern VALUE Oj; static void stream_writer_free(void *ptr) { StreamWriter sw; if (0 == ptr) { return; } sw = (StreamWriter)ptr; xfree(sw->sw.out.buf); xfree(sw->sw.types); xfree(ptr); } static void stream_writer_reset_buf(StreamWriter sw) { sw->sw.out.cur = sw->sw.out.buf; *sw->sw.out.cur = '\0'; } static void stream_writer_write(StreamWriter sw) { ssize_t size = sw->sw.out.cur - sw->sw.out.buf; switch (sw->type) { case STRING_IO: case STREAM_IO: { volatile VALUE rs = rb_str_new(sw->sw.out.buf, size); // Oddly enough, when pushing ASCII characters with UTF-8 encoding or // even ASCII-8BIT does not change the output encoding. Pushing any // non-ASCII no matter what the encoding changes the output encoding // to ASCII-8BIT if it the string is not forced to UTF-8 here. rs = oj_encode(rs); rb_funcall(sw->stream, oj_write_id, 1, rs); break; } case FILE_IO: if (size != write(sw->fd, sw->sw.out.buf, size)) { rb_raise(rb_eIOError, "Write failed. [_%d_:%s]\n", errno, strerror(errno)); } break; default: rb_raise(rb_eArgError, "expected an IO Object."); } stream_writer_reset_buf(sw); } static VALUE buffer_size_sym = Qundef; /* Document-method: new * call-seq: new(io, options) * * Creates a new StreamWriter. Options are supported according the specified * mode or the mode in the default options. Note that if mimic_JSON or * Oj.optimize_rails has not been called then the behavior of the modes may * not be the same as if they were. * * In addition to the regular dump options for the various modes a * _:buffer_size_ option is available. It should be set to a positive * integer. It is considered a hint of how large the initial internal buffer * should be and also a hint on when to flush. * * - *io* [_IO_] stream to write to * - *options* [_Hash_] formatting options */ static VALUE stream_writer_new(int argc, VALUE *argv, VALUE self) { StreamWriterType type = STREAM_IO; int fd = 0; VALUE stream = argv[0]; VALUE clas = rb_obj_class(stream); StreamWriter sw; #if !IS_WINDOWS VALUE s; #endif if (oj_stringio_class == clas) { type = STRING_IO; #if !IS_WINDOWS } else if (rb_respond_to(stream, oj_fileno_id) && Qnil != (s = rb_funcall(stream, oj_fileno_id, 0)) && 0 != (fd = FIX2INT(s))) { type = FILE_IO; #endif } else if (rb_respond_to(stream, oj_write_id)) { type = STREAM_IO; } else { rb_raise(rb_eArgError, "expected an IO Object."); } sw = ALLOC(struct _streamWriter); if (2 == argc && T_HASH == rb_type(argv[1])) { volatile VALUE v; int buf_size = 0; if (Qundef == buffer_size_sym) { buffer_size_sym = ID2SYM(rb_intern("buffer_size")); rb_gc_register_address(&buffer_size_sym); } if (Qnil != (v = rb_hash_lookup(argv[1], buffer_size_sym))) { #ifdef RUBY_INTEGER_UNIFICATION if (rb_cInteger != rb_obj_class(v)) { rb_raise(rb_eArgError, ":buffer size must be a Integer."); } #else if (T_FIXNUM != rb_type(v)) { rb_raise(rb_eArgError, ":buffer size must be a Integer."); } #endif buf_size = FIX2INT(v); } oj_str_writer_init(&sw->sw, buf_size); oj_parse_options(argv[1], &sw->sw.opts); sw->flush_limit = buf_size; } else { oj_str_writer_init(&sw->sw, 4096); sw->flush_limit = 0; } sw->sw.out.indent = sw->sw.opts.indent; sw->stream = stream; sw->type = type; sw->fd = fd; return Data_Wrap_Struct(oj_stream_writer_class, 0, stream_writer_free, sw); } /* Document-method: push_key * call-seq: push_key(key) * * Pushes a key onto the JSON document. The key will be used for the next push * if currently in a JSON object and ignored otherwise. If a key is provided on * the next push then that new key will be ignored. * * - *key* [_String_] the key pending for the next push */ static VALUE stream_writer_push_key(VALUE self, VALUE key) { StreamWriter sw = (StreamWriter)DATA_PTR(self); rb_check_type(key, T_STRING); oj_str_writer_push_key(&sw->sw, StringValuePtr(key)); if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: push_object * call-seq: push_object(key=nil) * * Pushes an object onto the JSON document. Future pushes will be to this object * until a pop() is called. * * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE stream_writer_push_object(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_object(&sw->sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_object(&sw->sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_object(&sw->sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: push_array * call-seq: push_array(key=nil) * * Pushes an array onto the JSON document. Future pushes will be to this object * until a pop() is called. * * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE stream_writer_push_array(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_array(&sw->sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_array(&sw->sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_array(&sw->sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: push_value * call-seq: push_value(value, key=nil) * * Pushes a value onto the JSON document. * - *value* [_Object_] value to add to the JSON document * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE stream_writer_push_value(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); switch (argc) { case 1: oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'."); break; } if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: push_json * call-seq: push_json(value, key=nil) * * Pushes a string onto the JSON document. The String must be a valid JSON * encoded string. No additional checking is done to verify the validity of the * string. * - *value* [_Object_] value to add to the JSON document * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE stream_writer_push_json(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); rb_check_type(argv[0], T_STRING); switch (argc) { case 1: oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'."); break; } if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: pop * call-seq: pop() * * Pops up a level in the JSON document closing the array or object that is * currently open. */ static VALUE stream_writer_pop(VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); oj_str_writer_pop(&sw->sw); if (sw->flush_limit < sw->sw.out.cur - sw->sw.out.buf) { stream_writer_write(sw); } return Qnil; } /* Document-method: pop_all * call-seq: pop_all() * * Pops all level in the JSON document closing all the array or object that is * currently open. */ static VALUE stream_writer_pop_all(VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); oj_str_writer_pop_all(&sw->sw); stream_writer_write(sw); return Qnil; } /* Document-method: flush * call-seq: flush() * * Flush any remaining characters in the buffer. */ static VALUE stream_writer_flush(VALUE self) { stream_writer_write((StreamWriter)DATA_PTR(self)); return Qnil; } /* Document-class: Oj::StreamWriter * * Supports building a JSON document one element at a time. Build the IO stream * document by pushing values into the document. Pushing an array or an object * will create that element in the JSON document and subsequent pushes will add * the elements to that array or object until a pop() is called. */ void oj_stream_writer_init() { oj_stream_writer_class = rb_define_class_under(Oj, "StreamWriter", rb_cObject); rb_define_module_function(oj_stream_writer_class, "new", stream_writer_new, -1); rb_define_method(oj_stream_writer_class, "push_key", stream_writer_push_key, 1); rb_define_method(oj_stream_writer_class, "push_object", stream_writer_push_object, -1); rb_define_method(oj_stream_writer_class, "push_array", stream_writer_push_array, -1); rb_define_method(oj_stream_writer_class, "push_value", stream_writer_push_value, -1); rb_define_method(oj_stream_writer_class, "push_json", stream_writer_push_json, -1); rb_define_method(oj_stream_writer_class, "pop", stream_writer_pop, 0); rb_define_method(oj_stream_writer_class, "pop_all", stream_writer_pop_all, 0); rb_define_method(oj_stream_writer_class, "flush", stream_writer_flush, 0); } oj-3.13.9/ext/oj/trace.h0000644000004100000410000000166214136373754014750 0ustar www-datawww-data// Copyright (c) 2018 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_TRACE_H #define OJ_TRACE_H #include #include typedef enum { TraceIn = '}', TraceOut = '{', TraceCall = '-', TraceRubyIn = '>', TraceRubyOut = '<', } TraceWhere; struct _parseInfo; extern void oj_trace(const char *func, VALUE obj, const char *file, int line, int depth, TraceWhere where); extern void oj_trace_parse_in(const char *func, struct _parseInfo *pi, const char *file, int line); extern void oj_trace_parse_call(const char *func, struct _parseInfo *pi, const char *file, int line, VALUE obj); extern void oj_trace_parse_hash_end(struct _parseInfo *pi, const char *file, int line); extern void oj_trace_parse_array_end(struct _parseInfo *pi, const char *file, int line); #endif /* OJ_TRACE_H */ oj-3.13.9/ext/oj/oj.h0000644000004100000410000002601014136373754014254 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_H #define OJ_H #if defined(cplusplus) extern "C" { #if 0 } /* satisfy cc-mode */ #endif #endif #define RSTRING_NOT_MODIFIED #include #include #include "ruby.h" #include "ruby/encoding.h" #ifdef HAVE_PTHREAD_MUTEX_INIT #include #endif #include "cache8.h" #ifdef RUBINIUS_RUBY #undef T_RATIONAL #undef T_COMPLEX enum st_retval { ST_CONTINUE = 0, ST_STOP = 1, ST_DELETE = 2, ST_CHECK }; #else #include "ruby/st.h" #endif #include "err.h" #include "rxclass.h" #define INF_VAL "3.0e14159265358979323846" #define NINF_VAL "-3.0e14159265358979323846" #define NAN_VAL "3.3e14159265358979323846" typedef enum { Yes = 'y', No = 'n', NotSet = 0 } YesNo; typedef enum { StrictMode = 's', ObjectMode = 'o', NullMode = 'n', CompatMode = 'c', RailsMode = 'r', CustomMode = 'C', WabMode = 'w', } Mode; typedef enum { UnixTime = 'u', UnixZTime = 'z', XmlTime = 'x', RubyTime = 'r' } TimeFormat; typedef enum { NLEsc = 'n', JSONEsc = 'j', XSSEsc = 'x', ASCIIEsc = 'a', JXEsc = 'g', // json gem RailsXEsc = 'r', // rails xss mode RailsEsc = 'R', // rails non escape } Encoding; typedef enum { BigDec = 'b', FloatDec = 'f', AutoDec = 'a', FastDec = 'F', RubyDec = 'r', } BigLoad; typedef enum { ArrayNew = 'A', ArrayType = 'a', ObjectNew = 'O', ObjectType = 'o', } DumpType; typedef enum { AutoNan = 'a', NullNan = 'n', HugeNan = 'h', WordNan = 'w', RaiseNan = 'r', } NanDump; typedef enum { STRING_IO = 'c', STREAM_IO = 's', FILE_IO = 'f', } StreamWriterType; typedef enum { CALLER_DUMP = 'd', CALLER_TO_JSON = 't', CALLER_GENERATE = 'g', // Add the fast versions if necessary. Maybe unparse as well if needed. } DumpCaller; typedef struct _dumpOpts { bool use; char indent_str[16]; char before_sep[16]; char after_sep[16]; char hash_nl[16]; char array_nl[16]; uint8_t indent_size; uint8_t before_size; uint8_t after_size; uint8_t hash_size; uint8_t array_size; char nan_dump; // NanDump bool omit_nil; int max_depth; } * DumpOpts; typedef struct _options { int indent; // indention for dump, default 2 char circular; // YesNo char auto_define; // YesNo char sym_key; // YesNo char escape_mode; // Escape_Mode char mode; // Mode char class_cache; // YesNo char time_format; // TimeFormat char bigdec_as_num; // YesNo char bigdec_load; // BigLoad char compat_bigdec; // boolean (0 or 1) char to_hash; // YesNo char to_json; // YesNo char as_json; // YesNo char raw_json; // YesNo char nilnil; // YesNo char empty_string; // YesNo char allow_gc; // allow GC during parse char quirks_mode; // allow single JSON values instead of documents char allow_invalid; // YesNo - allow invalid unicode char create_ok; // YesNo allow create_id char allow_nan; // YEsyNo for parsing only char trace; // YesNo char safe; // YesNo char sec_prec_set; // boolean (0 or 1) char ignore_under; // YesNo - ignore attrs starting with _ if true in object and custom modes char cache_keys; // YesNo char cache_str; // string short than or equal to this are cache int64_t int_range_min; // dump numbers below as string int64_t int_range_max; // dump numbers above as string const char * create_id; // 0 or string size_t create_id_len; // length of create_id int sec_prec; // second precision when dumping time char float_prec; // float precision, linked to float_fmt char float_fmt[7]; // float format for dumping, if empty use Ruby VALUE hash_class; // class to use in place of Hash on load VALUE array_class; // class to use in place of Array on load struct _dumpOpts dump_opts; struct _rxClass str_rx; VALUE * ignore; // Qnil terminated array of classes or NULL } * Options; struct _out; typedef void (*DumpFunc)(VALUE obj, int depth, struct _out *out, bool as_ok); // rails optimize typedef struct _rOpt { VALUE clas; bool on; DumpFunc dump; } * ROpt; typedef struct _rOptTable { int len; int alen; ROpt table; } * ROptTable; typedef struct _out { char * buf; char * end; char * cur; Cache8 circ_cache; slot_t circ_cnt; int indent; int depth; // used by dump_hash Options opts; uint32_t hash_cnt; bool allocated; bool omit_nil; int argc; VALUE * argv; DumpCaller caller; // used for the mimic json only ROptTable ropts; } * Out; typedef struct _strWriter { struct _out out; struct _options opts; int depth; char * types; // DumpType char * types_end; int keyWritten; } * StrWriter; typedef struct _streamWriter { struct _strWriter sw; StreamWriterType type; VALUE stream; int fd; int flush_limit; // indicator of when to flush } * StreamWriter; enum { NO_VAL = 0x00, STR_VAL = 0x01, COL_VAL = 0x02, RUBY_VAL = 0x03 }; typedef struct _leaf { struct _leaf *next; union { const char *key; // hash key size_t index; // array index, 0 is not set }; union { char * str; // pointer to location in json string or allocated struct _leaf *elements; // array and hash elements VALUE value; }; uint8_t rtype; uint8_t parent_type; uint8_t value_type; } * Leaf; extern VALUE oj_saj_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_sc_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_strict_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_strict_sparse(int argc, VALUE *argv, VALUE self); extern VALUE oj_compat_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_compat_load(int argc, VALUE *argv, VALUE self); extern VALUE oj_object_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_custom_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_wab_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_strict_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern bool oj_hash_has_key(VALUE hash, VALUE key); extern void oj_parse_options(VALUE ropts, Options copts); extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out); extern void oj_dump_obj_to_json_using_params(VALUE obj, Options copts, Out out, int argc, VALUE *argv); extern void oj_write_obj_to_file(VALUE obj, const char *path, Options copts); extern void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts); extern void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out); extern void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts); extern void oj_str_writer_push_key(StrWriter sw, const char *key); extern void oj_str_writer_push_object(StrWriter sw, const char *key); extern void oj_str_writer_push_array(StrWriter sw, const char *key); extern void oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key); extern void oj_str_writer_push_json(StrWriter sw, const char *json, const char *key); extern void oj_str_writer_pop(StrWriter sw); extern void oj_str_writer_pop_all(StrWriter sw); extern void oj_init_doc(void); extern void oj_string_writer_init(); extern void oj_stream_writer_init(); extern void oj_str_writer_init(StrWriter sw, int buf_size); extern VALUE oj_define_mimic_json(int argc, VALUE *argv, VALUE self); extern VALUE oj_mimic_generate(int argc, VALUE *argv, VALUE self); extern VALUE oj_mimic_pretty_generate(int argc, VALUE *argv, VALUE self); extern void oj_parse_mimic_dump_options(VALUE ropts, Options copts); extern VALUE oj_mimic_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_get_json_err_class(const char *err_classname); extern void oj_parse_opt_match_string(RxClass rc, VALUE ropts); extern VALUE oj_rails_encode(int argc, VALUE *argv, VALUE self); extern VALUE Oj; extern struct _options oj_default_options; extern rb_encoding * oj_utf8_encoding; extern VALUE oj_bag_class; extern VALUE oj_bigdecimal_class; extern VALUE oj_cstack_class; extern VALUE oj_date_class; extern VALUE oj_datetime_class; extern VALUE oj_doc_class; extern VALUE oj_enumerable_class; extern VALUE oj_json_generator_error_class; extern VALUE oj_json_parser_error_class; extern VALUE oj_stream_writer_class; extern VALUE oj_string_writer_class; extern VALUE oj_stringio_class; extern VALUE oj_struct_class; extern VALUE oj_allow_nan_sym; extern VALUE oj_array_class_sym; extern VALUE oj_array_nl_sym; extern VALUE oj_ascii_only_sym; extern VALUE oj_create_additions_sym; extern VALUE oj_decimal_class_sym; extern VALUE oj_hash_class_sym; extern VALUE oj_indent_sym; extern VALUE oj_max_nesting_sym; extern VALUE oj_object_class_sym; extern VALUE oj_object_nl_sym; extern VALUE oj_quirks_mode_sym; extern VALUE oj_space_before_sym; extern VALUE oj_space_sym; extern VALUE oj_symbolize_names_sym; extern VALUE oj_trace_sym; extern VALUE oj_slash_string; extern ID oj_add_value_id; extern ID oj_array_append_id; extern ID oj_array_end_id; extern ID oj_array_start_id; extern ID oj_as_json_id; extern ID oj_begin_id; extern ID oj_bigdecimal_id; extern ID oj_end_id; extern ID oj_error_id; extern ID oj_exclude_end_id; extern ID oj_file_id; extern ID oj_fileno_id; extern ID oj_ftype_id; extern ID oj_hash_end_id; extern ID oj_hash_key_id; extern ID oj_hash_set_id; extern ID oj_hash_start_id; extern ID oj_iconv_id; extern ID oj_instance_variables_id; extern ID oj_json_create_id; extern ID oj_length_id; extern ID oj_new_id; extern ID oj_parse_id; extern ID oj_pos_id; extern ID oj_read_id; extern ID oj_readpartial_id; extern ID oj_replace_id; extern ID oj_stat_id; extern ID oj_string_id; extern ID oj_raw_json_id; extern ID oj_to_h_id; extern ID oj_to_hash_id; extern ID oj_to_json_id; extern ID oj_to_s_id; extern ID oj_to_sym_id; extern ID oj_to_time_id; extern ID oj_tv_nsec_id; extern ID oj_tv_sec_id; extern ID oj_tv_usec_id; extern ID oj_utc_id; extern ID oj_utc_offset_id; extern ID oj_utcq_id; extern ID oj_write_id; extern bool oj_use_hash_alt; extern bool oj_use_array_alt; extern bool string_writer_optimized; #ifdef HAVE_PTHREAD_MUTEX_INIT extern pthread_mutex_t oj_cache_mutex; #else extern VALUE oj_cache_mutex; #endif #if defined(cplusplus) #if 0 { /* satisfy cc-mode */ #endif } /* extern "C" { */ #endif #endif /* OJ_H */ oj-3.13.9/ext/oj/saj2.c0000644000004100000410000002336114136373754014504 0ustar www-datawww-data// Copyright (c) 2021, Peter Ohler, All rights reserved. #include "cache.h" #include "oj.h" #include "parser.h" typedef struct _delegate { VALUE handler; VALUE * keys; VALUE * tail; size_t klen; struct _cache *str_cache; uint8_t cache_str; bool cache_keys; bool thread_safe; } * Delegate; static VALUE get_key(ojParser p) { Delegate d = (Delegate)p->ctx; const char * key = buf_str(&p->key); size_t len = buf_len(&p->key); volatile VALUE rkey; if (d->cache_keys) { rkey = cache_intern(d->str_cache, key, len); } else { rkey = rb_utf8_str_new(key, len); } return rkey; } static void push_key(Delegate d, VALUE key) { if (d->klen <= (size_t)(d->tail - d->keys)) { size_t off = d->tail - d->keys; d->klen += d->klen / 2; REALLOC_N(d->keys, VALUE, d->klen); d->tail = d->keys + off; } *d->tail = key; d->tail++; } static void noop(ojParser p) { } static void open_object(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_hash_start_id, 1, Qnil); } static void open_object_key(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE key = get_key(p); push_key(d, key); rb_funcall(d->handler, oj_hash_start_id, 1, key); } static void open_array(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_array_start_id, 1, Qnil); } static void open_array_key(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE key = get_key(p); push_key(d, key); rb_funcall(d->handler, oj_array_start_id, 1, key); } static void close_object(ojParser p) { Delegate d = (Delegate)p->ctx; VALUE key = Qnil; if (OBJECT_FUN == p->stack[p->depth]) { d->tail--; if (d->tail < d->keys) { rb_raise(rb_eIndexError, "accessing key stack"); } key = *d->tail; } rb_funcall(d->handler, oj_hash_end_id, 1, key); } static void close_array(ojParser p) { Delegate d = (Delegate)p->ctx; VALUE key = Qnil; if (OBJECT_FUN == p->stack[p->depth]) { d->tail--; if (d->tail < d->keys) { rb_raise(rb_eIndexError, "accessing key stack"); } key = *d->tail; } rb_funcall(d->handler, oj_array_end_id, 1, key); } static void add_null(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qnil, Qnil); } static void add_null_key(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qnil, get_key(p)); } static void add_true(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qtrue, Qnil); } static void add_true_key(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qtrue, get_key(p)); } static void add_false(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qfalse, Qnil); } static void add_false_key(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, Qfalse, get_key(p)); } static void add_int(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, LONG2NUM(p->num.fixnum), Qnil); } static void add_int_key(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, LONG2NUM(p->num.fixnum), get_key(p)); } static void add_float(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, rb_float_new(p->num.dub), Qnil); } static void add_float_key(ojParser p) { rb_funcall(((Delegate)p->ctx)->handler, oj_add_value_id, 2, rb_float_new(p->num.dub), get_key(p)); } static void add_big(ojParser p) { rb_funcall((VALUE)p->ctx, oj_add_value_id, 2, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))), Qnil); } static void add_big_key(ojParser p) { rb_funcall((VALUE)p->ctx, oj_add_value_id, 2, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))), get_key(p)); } static void add_str(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE rstr; const char * str = buf_str(&p->buf); size_t len = buf_len(&p->buf); if (d->cache_str < len) { rstr = cache_intern(d->str_cache, str, len); } else { rstr = rb_utf8_str_new(str, len); } rb_funcall(d->handler, oj_add_value_id, 2, rstr, Qnil); } static void add_str_key(ojParser p) { Delegate d = (Delegate)p->ctx; volatile VALUE rstr; const char * str = buf_str(&p->buf); size_t len = buf_len(&p->buf); if (d->cache_str < len) { rstr = cache_intern(d->str_cache, str, len); } else { rstr = rb_utf8_str_new(str, len); } rb_funcall(d->handler, oj_add_value_id, 2, rstr, get_key(p)); } static void reset(ojParser p) { Funcs end = p->funcs + 3; Funcs f; for (f = p->funcs; f < end; f++) { f->add_null = noop; f->add_true = noop; f->add_false = noop; f->add_int = noop; f->add_float = noop; f->add_big = noop; f->add_str = noop; f->open_array = noop; f->close_array = noop; f->open_object = noop; f->close_object = noop; } } static VALUE option(ojParser p, const char *key, VALUE value) { Delegate d = (Delegate)p->ctx; if (0 == strcmp(key, "handler")) { return d->handler; } if (0 == strcmp(key, "handler=")) { d->tail = d->keys; d->handler = value; reset(p); if (rb_respond_to(value, oj_hash_start_id)) { p->funcs[TOP_FUN].open_object = open_object; p->funcs[ARRAY_FUN].open_object = open_object; p->funcs[OBJECT_FUN].open_object = open_object_key; } if (rb_respond_to(value, oj_array_start_id)) { p->funcs[TOP_FUN].open_array = open_array; p->funcs[ARRAY_FUN].open_array = open_array; p->funcs[OBJECT_FUN].open_array = open_array_key; } if (rb_respond_to(value, oj_hash_end_id)) { p->funcs[TOP_FUN].close_object = close_object; p->funcs[ARRAY_FUN].close_object = close_object; p->funcs[OBJECT_FUN].close_object = close_object; } if (rb_respond_to(value, oj_array_end_id)) { p->funcs[TOP_FUN].close_array = close_array; p->funcs[ARRAY_FUN].close_array = close_array; p->funcs[OBJECT_FUN].close_array = close_array; } if (rb_respond_to(value, oj_add_value_id)) { p->funcs[TOP_FUN].add_null = add_null; p->funcs[ARRAY_FUN].add_null = add_null; p->funcs[OBJECT_FUN].add_null = add_null_key; p->funcs[TOP_FUN].add_true = add_true; p->funcs[ARRAY_FUN].add_true = add_true; p->funcs[OBJECT_FUN].add_true = add_true_key; p->funcs[TOP_FUN].add_false = add_false; p->funcs[ARRAY_FUN].add_false = add_false; p->funcs[OBJECT_FUN].add_false = add_false_key; p->funcs[TOP_FUN].add_int = add_int; p->funcs[ARRAY_FUN].add_int = add_int; p->funcs[OBJECT_FUN].add_int = add_int_key; p->funcs[TOP_FUN].add_float = add_float; p->funcs[ARRAY_FUN].add_float = add_float; p->funcs[OBJECT_FUN].add_float = add_float_key; p->funcs[TOP_FUN].add_big = add_big; p->funcs[ARRAY_FUN].add_big = add_big; p->funcs[OBJECT_FUN].add_big = add_big_key; p->funcs[TOP_FUN].add_str = add_str; p->funcs[ARRAY_FUN].add_str = add_str; p->funcs[OBJECT_FUN].add_str = add_str_key; } return Qnil; } if (0 == strcmp(key, "cache_keys")) { return d->cache_keys ? Qtrue : Qfalse; } if (0 == strcmp(key, "cache_keys=")) { d->cache_keys = (Qtrue == value); return d->cache_keys ? Qtrue : Qfalse; } if (0 == strcmp(key, "cache_strings")) { return INT2NUM((int)d->cache_str); } if (0 == strcmp(key, "cache_strings=")) { int limit = NUM2INT(value); if (CACHE_MAX_KEY < limit) { limit = CACHE_MAX_KEY; } else if (limit < 0) { limit = 0; } d->cache_str = limit; return INT2NUM((int)d->cache_str); } rb_raise(rb_eArgError, "%s is not an option for the SAJ (Simple API for JSON) delegate", key); return Qnil; // Never reached due to the raise but required by the compiler. } static VALUE result(ojParser p) { return Qnil; } static void start(ojParser p) { Delegate d = (Delegate)p->ctx; d->tail = d->keys; } static void dfree(ojParser p) { Delegate d = (Delegate)p->ctx; if (NULL != d->keys) { xfree(d->keys); } cache_free(d->str_cache); xfree(p->ctx); } static void mark(ojParser p) { if (NULL == p->ctx) { return; } Delegate d = (Delegate)p->ctx; VALUE *kp; cache_mark(d->str_cache); if (Qnil != d->handler) { rb_gc_mark(d->handler); } if (!d->cache_keys) { for (kp = d->keys; kp < d->tail; kp++) { rb_gc_mark(*kp); } } } static VALUE form_str(const char *str, size_t len) { return rb_str_freeze(rb_utf8_str_new(str, len)); } void oj_set_parser_saj(ojParser p) { Delegate d = ALLOC(struct _delegate); d->klen = 256; d->keys = ALLOC_N(VALUE, d->klen); d->tail = d->keys; d->str_cache = cache_create(0, form_str, true, false); p->ctx = (void *)d; reset(p); p->option = option; p->result = result; p->free = dfree; p->mark = mark; p->start = start; } oj-3.13.9/ext/oj/util.h0000644000004100000410000000062614136373754014626 0ustar www-datawww-data// Copyright (c) 2019 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_UTIL_H #define OJ_UTIL_H #include typedef struct _timeInfo { int sec; int min; int hour; int day; int mon; int year; } * TimeInfo; extern void sec_as_time(int64_t secs, TimeInfo ti); #endif /* OJ_UTIL_H */ oj-3.13.9/ext/oj/err.h0000644000004100000410000000353214136373754014440 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_ERR_H #define OJ_ERR_H #include #include "ruby.h" // Needed to silence 2.4.0 warnings. #ifndef NORETURN #define NORETURN(x) x #endif #define OJ_ERR_START 300 typedef enum { OJ_OK = 0, OJ_ERR_MEMORY = ENOMEM, OJ_ERR_PARSE = OJ_ERR_START, OJ_ERR_READ, OJ_ERR_WRITE, OJ_ERR_OVERFLOW, OJ_ERR_ARG, OJ_ERR_TOO_MANY, OJ_ERR_TYPE, OJ_ERR_KEY, OJ_ABORT, OJ_ERR_LAST, } ojStatus; #define set_error(err, eclas, msg, json, current) \ _oj_err_set_with_location(err, eclas, msg, json, current, FILE, LINE) typedef struct _err { VALUE clas; char msg[128]; } * Err; extern VALUE oj_parse_error_class; extern void oj_err_set(Err e, VALUE clas, const char *format, ...); extern void _oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char *file, int line); NORETURN(extern void oj_err_raise(Err e)); #define raise_error(msg, json, current) _oj_raise_error(msg, json, current, __FILE__, __LINE__) NORETURN(extern void _oj_raise_error(const char *msg, const char *json, const char *current, const char *file, int line)); inline static void err_init(Err e) { e->clas = Qnil; *e->msg = '\0'; } inline static int err_has(Err e) { return (Qnil != e->clas); } #endif /* OJ_ERR_H */ oj-3.13.9/ext/oj/cache.h0000644000004100000410000000137614136373754014717 0ustar www-datawww-data// Copyright (c) 2021 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef CACHE_H #define CACHE_H #include #include #define CACHE_MAX_KEY 35 struct _cache; extern struct _cache *cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking); extern void cache_free(struct _cache *c); extern void cache_mark(struct _cache *c); extern void cache_set_form(struct _cache *c, VALUE (*form)(const char *str, size_t len)); extern VALUE cache_intern(struct _cache *c, const char *key, size_t len); extern void cache_set_expunge_rate(struct _cache *c, int rate); #endif /* CACHE_H */ oj-3.13.9/ext/oj/err.c0000644000004100000410000000343714136373754014437 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "err.h" #include void oj_err_set(Err e, VALUE clas, const char *format, ...) { va_list ap; va_start(ap, format); e->clas = clas; vsnprintf(e->msg, sizeof(e->msg) - 1, format, ap); va_end(ap); } void oj_err_raise(Err e) { rb_raise(e->clas, "%s", e->msg); } void _oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char *file, int line) { int n = 1; int col = 1; for (; json < current && '\n' != *current; current--) { col++; } for (; json < current; current--) { if ('\n' == *current) { n++; } } oj_err_set(err, eclas, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line); } void _oj_raise_error(const char *msg, const char *json, const char *current, const char *file, int line) { struct _err err; int n = 1; int col = 1; for (; json < current && '\n' != *current; current--) { col++; } for (; json < current; current--) { if ('\n' == *current) { n++; } } oj_err_set(&err, oj_parse_error_class, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line); rb_raise(err.clas, "%s", err.msg); } oj-3.13.9/ext/oj/rxclass.c0000644000004100000410000000661614136373754015330 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include #include #if !IS_WINDOWS #include #endif #include "rxclass.h" typedef struct _rxC { struct _rxC *next; VALUE rrx; #if !IS_WINDOWS regex_t rx; #endif VALUE clas; char src[256]; } * RxC; void oj_rxclass_init(RxClass rc) { *rc->err = '\0'; rc->head = NULL; rc->tail = NULL; } void oj_rxclass_cleanup(RxClass rc) { RxC rxc; while (NULL != (rxc = rc->head)) { rc->head = rc->head->next; #if !IS_WINDOWS if (Qnil == rxc->rrx) { regfree(&rxc->rx); } xfree(rxc); #endif } } void oj_rxclass_rappend(RxClass rc, VALUE rx, VALUE clas) { RxC rxc = ALLOC_N(struct _rxC, 1); memset(rxc, 0, sizeof(struct _rxC)); rxc->rrx = rx; rxc->clas = clas; if (NULL == rc->tail) { rc->head = rxc; } else { rc->tail->next = rxc; } rc->tail = rxc; } // Attempt to compile the expression. If it fails populate the error code.. int oj_rxclass_append(RxClass rc, const char *expr, VALUE clas) { RxC rxc; #if !IS_WINDOWS int err; int flags = 0; #endif if (sizeof(rxc->src) <= strlen(expr)) { snprintf(rc->err, sizeof(rc->err), "expressions must be less than %lu characters", (unsigned long)sizeof(rxc->src)); return EINVAL; } rxc = ALLOC_N(struct _rxC, 1); rxc->next = 0; rxc->clas = clas; #if IS_WINDOWS rxc->rrx = rb_funcall(rb_cRegexp, rb_intern("new"), 1, rb_str_new2(expr)); #else rxc->rrx = Qnil; if (0 != (err = regcomp(&rxc->rx, expr, flags))) { regerror(err, &rxc->rx, rc->err, sizeof(rc->err)); free(rxc); return err; } #endif if (NULL == rc->tail) { rc->head = rxc; } else { rc->tail->next = rxc; } rc->tail = rxc; return 0; } VALUE oj_rxclass_match(RxClass rc, const char *str, int len) { RxC rxc; char buf[4096]; for (rxc = rc->head; NULL != rxc; rxc = rxc->next) { if (Qnil != rxc->rrx) { // Must use a valiabel for this to work. volatile VALUE rstr = rb_str_new(str, len); // if (Qtrue == rb_funcall(rxc->rrx, rb_intern("match?"), 1, rstr)) { if (Qnil != rb_funcall(rxc->rrx, rb_intern("match"), 1, rstr)) { return rxc->clas; } } else if (len < (int)sizeof(buf)) { #if !IS_WINDOWS // string is not \0 terminated so copy and attempt a match memcpy(buf, str, len); buf[len] = '\0'; if (0 == regexec(&rxc->rx, buf, 0, NULL, 0)) { // match return rxc->clas; } #endif } else { // TBD allocate a larger buffer and attempt } } return Qnil; } void oj_rxclass_copy(RxClass src, RxClass dest) { dest->head = NULL; dest->tail = NULL; if (NULL != src->head) { RxC rxc; for (rxc = src->head; NULL != rxc; rxc = rxc->next) { if (Qnil != rxc->rrx) { oj_rxclass_rappend(dest, rxc->rrx, rxc->clas); } else { #if !IS_WINDOWS oj_rxclass_append(dest, rxc->src, rxc->clas); #endif } } } } oj-3.13.9/ext/oj/intern.c0000644000004100000410000001746314136373754015152 0ustar www-datawww-data// Copyright (c) 2011, 2021 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "intern.h" #include #if HAVE_PTHREAD_MUTEX_INIT #include #endif #include "cache.h" #include "parse.h" // Only used for the class cache so 256 should be sufficient. #define HASH_SLOT_CNT ((uint64_t)256) #define HASH_MASK (HASH_SLOT_CNT - 1) // almost the Murmur hash algorithm #define M 0x5bd1e995 typedef struct _keyVal { struct _keyVal *next; const char * key; size_t len; VALUE val; } * KeyVal; typedef struct _hash { struct _keyVal slots[HASH_SLOT_CNT]; #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_t mutex; #else VALUE mutex; #endif } * Hash; struct _hash class_hash; struct _hash attr_hash; static struct _cache *str_cache = NULL; static VALUE str_cache_obj; static struct _cache *sym_cache = NULL; static VALUE sym_cache_obj; static struct _cache *attr_cache = NULL; static VALUE attr_cache_obj; static VALUE form_str(const char *str, size_t len) { return rb_str_freeze(rb_utf8_str_new(str, len)); } static VALUE form_sym(const char *str, size_t len) { return rb_to_symbol(rb_str_intern(rb_utf8_str_new(str, len))); } static VALUE form_attr(const char *str, size_t len) { char buf[256]; if (sizeof(buf) - 2 <= len) { char *b = ALLOC_N(char, len + 2); ID id; if ('~' == *str) { memcpy(b, str + 1, len - 1); b[len - 1] = '\0'; len -= 2; } else { *b = '@'; memcpy(b + 1, str, len); b[len + 1] = '\0'; } id = rb_intern3(buf, len + 1, oj_utf8_encoding); xfree(b); return id; } if ('~' == *str) { memcpy(buf, str + 1, len - 1); buf[len - 1] = '\0'; len -= 2; } else { *buf = '@'; memcpy(buf + 1, str, len); buf[len + 1] = '\0'; } return (VALUE)rb_intern3(buf, len + 1, oj_utf8_encoding); } void oj_hash_init() { VALUE cache_class = rb_define_class_under(Oj, "Cache", rb_cObject); str_cache = cache_create(0, form_str, true, true); str_cache_obj = Data_Wrap_Struct(cache_class, cache_mark, cache_free, str_cache); rb_gc_register_address(&str_cache_obj); sym_cache = cache_create(0, form_sym, true, true); sym_cache_obj = Data_Wrap_Struct(cache_class, cache_mark, cache_free, sym_cache); rb_gc_register_address(&sym_cache_obj); attr_cache = cache_create(0, form_attr, false, true); attr_cache_obj = Data_Wrap_Struct(cache_class, cache_mark, cache_free, attr_cache); rb_gc_register_address(&attr_cache_obj); memset(class_hash.slots, 0, sizeof(class_hash.slots)); #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_init(&class_hash.mutex, NULL); #else class_hash.mutex = rb_mutex_new(); rb_gc_register_address(&class_hash.mutex); #endif } VALUE oj_str_intern(const char *key, size_t len) { // For huge cache sizes over half a million the rb_enc_interned_str // performs slightly better but at more "normal" size of a several // thousands the cache intern performs about 20% better. #if HAVE_RB_ENC_INTERNED_STR && 0 return rb_enc_interned_str(key, len, rb_utf8_encoding()); #else return cache_intern(str_cache, key, len); #endif } VALUE oj_sym_intern(const char *key, size_t len) { return cache_intern(sym_cache, key, len); } ID oj_attr_intern(const char *key, size_t len) { return cache_intern(attr_cache, key, len); } static uint64_t hash_calc(const uint8_t *key, size_t len) { const uint8_t *end = key + len; const uint8_t *endless = key + (len & 0xFFFFFFFC); uint64_t h = (uint64_t)len; uint64_t k; while (key < endless) { k = (uint64_t)*key++; k |= (uint64_t)*key++ << 8; k |= (uint64_t)*key++ << 16; k |= (uint64_t)*key++ << 24; k *= M; k ^= k >> 24; h *= M; h ^= k * M; } if (1 < end - key) { uint16_t k16 = (uint16_t)*key++; k16 |= (uint16_t)*key++ << 8; h ^= k16 << 8; } if (key < end) { h ^= *key; } h *= M; h ^= h >> 13; h *= M; h ^= h >> 15; return h; } static VALUE resolve_classname(VALUE mod, const char *classname, int auto_define) { VALUE clas; ID ci = rb_intern(classname); if (rb_const_defined_at(mod, ci)) { clas = rb_const_get_at(mod, ci); } else if (auto_define) { clas = rb_define_class_under(mod, classname, oj_bag_class); } else { clas = Qundef; } return clas; } static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int auto_define, VALUE error_class) { char class_name[1024]; VALUE clas; char * end = class_name + sizeof(class_name) - 1; char * s; const char *n = name; size_t nlen = len; clas = rb_cObject; for (s = class_name; 0 < len; n++, len--) { if (':' == *n) { *s = '\0'; n++; len--; if (':' != *n) { return Qundef; } if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { return Qundef; } s = class_name; } else if (end <= s) { return Qundef; } else { *s++ = *n; } } *s = '\0'; if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { if (sizeof(class_name) <= nlen) { nlen = sizeof(class_name) - 1; } strncpy(class_name, name, nlen); class_name[nlen] = '\0'; oj_set_error_at(pi, error_class, __FILE__, __LINE__, "class '%s' is not defined", class_name); if (Qnil != error_class) { pi->err_class = error_class; } } return clas; } VALUE oj_class_intern(const char *key, size_t len, bool safe, ParseInfo pi, int auto_define, VALUE error_class) { uint64_t h = hash_calc((const uint8_t *)key, len) & HASH_MASK; KeyVal bucket = class_hash.slots + h; KeyVal b; if (safe) { #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_lock(&class_hash.mutex); #else rb_mutex_lock(class_hash.mutex); #endif if (NULL != bucket->key) { // not the top slot for (b = bucket; 0 != b; b = b->next) { if (len == b->len && 0 == strncmp(b->key, key, len)) { #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_unlock(&class_hash.mutex); #else rb_mutex_unlock(class_hash.mutex); #endif return b->val; } bucket = b; } b = ALLOC(struct _keyVal); b->next = NULL; bucket->next = b; bucket = b; } bucket->key = oj_strndup(key, len); bucket->len = len; bucket->val = resolve_classpath(pi, key, len, auto_define, error_class); #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_unlock(&class_hash.mutex); #else rb_mutex_unlock(class_hash.mutex); #endif } else { if (NULL != bucket->key) { for (b = bucket; 0 != b; b = b->next) { if (len == b->len && 0 == strncmp(b->key, key, len)) { return (ID)b->val; } bucket = b; } b = ALLOC(struct _keyVal); b->next = NULL; bucket->next = b; bucket = b; } bucket->key = oj_strndup(key, len); bucket->len = len; bucket->val = resolve_classpath(pi, key, len, auto_define, error_class); } return bucket->val; } char *oj_strndup(const char *s, size_t len) { char *d = ALLOC_N(char, len + 1); memcpy(d, s, len); d[len] = '\0'; return d; } void intern_cleanup() { cache_free(str_cache); cache_free(sym_cache); cache_free(attr_cache); } oj-3.13.9/ext/oj/rxclass.h0000644000004100000410000000134614136373754015330 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_RXCLASS_H #define OJ_RXCLASS_H #include #include "ruby.h" struct _rxC; typedef struct _rxClass { struct _rxC *head; struct _rxC *tail; char err[128]; } * RxClass; extern void oj_rxclass_init(RxClass rc); extern void oj_rxclass_cleanup(RxClass rc); extern int oj_rxclass_append(RxClass rc, const char *expr, VALUE clas); extern VALUE oj_rxclass_match(RxClass rc, const char *str, int len); extern void oj_rxclass_copy(RxClass src, RxClass dest); extern void oj_rxclass_rappend(RxClass rc, VALUE rx, VALUE clas); #endif /* OJ_RXCLASS_H */ oj-3.13.9/ext/oj/extconf.rb0000644000004100000410000000355114136373754015473 0ustar www-datawww-datarequire 'mkmf' require 'rbconfig' extension_name = 'oj' dir_config(extension_name) parts = RUBY_DESCRIPTION.split(' ') type = parts[0] type = type[4..-1] if type.start_with?('tcs-') is_windows = RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ platform = RUBY_PLATFORM version = RUBY_VERSION.split('.') puts ">>>>> Creating Makefile for #{type} version #{RUBY_VERSION} on #{platform} <<<<<" dflags = { 'RUBY_TYPE' => type, (type.upcase + '_RUBY') => nil, 'RUBY_VERSION' => RUBY_VERSION, 'RUBY_VERSION_MAJOR' => version[0], 'RUBY_VERSION_MINOR' => version[1], 'RUBY_VERSION_MICRO' => version[2], 'IS_WINDOWS' => is_windows ? 1 : 0, 'RSTRUCT_LEN_RETURNS_INTEGER_OBJECT' => ('ruby' == type && '2' == version[0] && '4' == version[1] && '1' >= version[2]) ? 1 : 0, } have_func('rb_time_timespec') have_func('rb_ivar_count') have_func('rb_ivar_foreach') # Support for compaction. have_func('rb_gc_mark_movable') have_func('stpcpy') have_func('pthread_mutex_init') have_func('rb_enc_associate') have_func('rb_enc_interned_str') have_func('rb_ext_ractor_safe', 'ruby.h') # rb_hash_bulk_insert is deep down in a header not included in normal build and that seems to fool have_func. have_func('rb_hash_bulk_insert', 'ruby.h') unless '2' == version[0] && '6' == version[1] dflags['OJ_DEBUG'] = true unless ENV['OJ_DEBUG'].nil? dflags.each do |k,v| if v.nil? $CPPFLAGS += " -D#{k}" else $CPPFLAGS += " -D#{k}=#{v}" end end $CPPFLAGS += ' -Wall' #puts "*** $CPPFLAGS: #{$CPPFLAGS}" # Adding the __attribute__ flag only works with gcc compilers and even then it # does not work to check args with varargs so just remove the check. CONFIG['warnflags'].slice!(/ -Wsuggest-attribute=format/) CONFIG['warnflags'].slice!(/ -Wdeclaration-after-statement/) CONFIG['warnflags'].slice!(/ -Wmissing-noreturn/) create_makefile(File.join(extension_name, extension_name)) %x{make clean} oj-3.13.9/ext/oj/code.h0000644000004100000410000000211114136373754014552 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_CODE_H #define OJ_CODE_H #include #include "oj.h" typedef void (*EncodeFunc)(VALUE obj, int depth, Out out); typedef VALUE (*DecodeFunc)(VALUE clas, VALUE args); typedef struct _code { const char *name; VALUE clas; EncodeFunc encode; DecodeFunc decode; bool active; // For compat mode. } * Code; // Used by encode functions. typedef struct _attr { const char *name; int len; VALUE value; long num; VALUE time; } * Attr; extern bool oj_code_dump(Code codes, VALUE obj, int depth, Out out); extern VALUE oj_code_load(Code codes, VALUE clas, VALUE args); extern void oj_code_set_active(Code codes, VALUE clas, bool active); extern bool oj_code_has(Code codes, VALUE clas, bool encode); extern void oj_code_attrs(VALUE obj, Attr attrs, int depth, Out out, bool with_class); extern struct _code oj_compat_codes[]; #endif /* OJ_CODE_H */ oj-3.13.9/ext/oj/fast.c0000644000004100000410000014156114136373754014605 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for // license details. #if !IS_WINDOWS #include // for getrlimit() on linux #endif #include #include #include #include #include #include "encode.h" #include "oj.h" // maximum to allocate on the stack, arbitrary limit #define SMALL_JSON 65536 #define MAX_STACK 100 //#define BATCH_SIZE (4096 / sizeof(struct _leaf) - 1) #define BATCH_SIZE 100 // Support for compaction #ifdef HAVE_RB_GC_MARK_MOVABLE #define mark rb_gc_mark_movable #else #define mark rb_gc_mark #endif typedef struct _batch { struct _batch *next; int next_avail; struct _leaf leaves[BATCH_SIZE]; } * Batch; typedef struct _doc { Leaf data; Leaf * where; // points to current location Leaf where_path[MAX_STACK]; // points to head of path char * json; unsigned long size; // number of leaves/branches in the doc VALUE self; Batch batches; struct _batch batch0; } * Doc; typedef struct _parseInfo { char *str; // buffer being read from char *s; // current position in buffer Doc doc; void *stack_min; } * ParseInfo; static void leaf_init(Leaf leaf, int type); static Leaf leaf_new(Doc doc, int type); static void leaf_append_element(Leaf parent, Leaf element); static VALUE leaf_value(Doc doc, Leaf leaf); static void leaf_fixnum_value(Leaf leaf); static void leaf_float_value(Leaf leaf); static VALUE leaf_array_value(Doc doc, Leaf leaf); static VALUE leaf_hash_value(Doc doc, Leaf leaf); static Leaf read_next(ParseInfo pi); static Leaf read_obj(ParseInfo pi); static Leaf read_array(ParseInfo pi); static Leaf read_str(ParseInfo pi); static Leaf read_num(ParseInfo pi); static Leaf read_true(ParseInfo pi); static Leaf read_false(ParseInfo pi); static Leaf read_nil(ParseInfo pi); static void next_non_white(ParseInfo pi); static char *read_quoted_value(ParseInfo pi); static void skip_comment(ParseInfo pi); static VALUE protect_open_proc(VALUE x); static VALUE parse_json(VALUE clas, char *json, bool given, bool allocated); static void each_leaf(Doc doc, VALUE self); static int move_step(Doc doc, const char *path, int loc); static Leaf get_doc_leaf(Doc doc, const char *path); static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path); static void each_value(Doc doc, Leaf leaf); VALUE oj_doc_class = Qundef; // This is only for CentOS 5.4 with Ruby 1.9.3-p0. #ifndef HAVE_STPCPY char *stpcpy(char *dest, const char *src) { size_t cnt = strlen(src); strcpy(dest, src); return dest + cnt; } #endif inline static void next_non_white(ParseInfo pi) { for (; 1; pi->s++) { switch (*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': break; case '/': skip_comment(pi); break; default: return; } } } inline static char *ulong_fill(char *s, size_t num) { char buf[32]; char *b = buf + sizeof(buf) - 1; *b-- = '\0'; for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } b++; if ('\0' == *b) { b--; *b = '0'; } for (; '\0' != *b; b++, s++) { *s = *b; } return s; } inline static void leaf_init(Leaf leaf, int type) { leaf->next = 0; leaf->rtype = type; leaf->parent_type = T_NONE; switch (type) { case T_ARRAY: case T_HASH: leaf->elements = 0; leaf->value_type = COL_VAL; break; case T_NIL: leaf->value = Qnil; leaf->value_type = RUBY_VAL; break; case T_TRUE: leaf->value = Qtrue; leaf->value_type = RUBY_VAL; break; case T_FALSE: leaf->value = Qfalse; leaf->value_type = RUBY_VAL; break; case T_FIXNUM: case T_FLOAT: case T_STRING: default: leaf->value_type = STR_VAL; break; } } inline static Leaf leaf_new(Doc doc, int type) { Leaf leaf; if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) { Batch b = ALLOC(struct _batch); // Initializes all leaves with a NO_VAL value_type memset(b, 0, sizeof(struct _batch)); b->next = doc->batches; doc->batches = b; b->next_avail = 0; } leaf = &doc->batches->leaves[doc->batches->next_avail]; doc->batches->next_avail++; leaf_init(leaf, type); return leaf; } inline static void leaf_append_element(Leaf parent, Leaf element) { if (0 == parent->elements) { parent->elements = element; element->next = element; } else { element->next = parent->elements->next; parent->elements->next = element; parent->elements = element; } } static VALUE leaf_value(Doc doc, Leaf leaf) { if (RUBY_VAL != leaf->value_type) { switch (leaf->rtype) { case T_NIL: leaf->value = Qnil; break; case T_TRUE: leaf->value = Qtrue; break; case T_FALSE: leaf->value = Qfalse; break; case T_FIXNUM: leaf_fixnum_value(leaf); break; case T_FLOAT: leaf_float_value(leaf); break; case T_STRING: leaf->value = rb_str_new2(leaf->str); leaf->value = oj_encode(leaf->value); leaf->value_type = RUBY_VAL; break; case T_ARRAY: return leaf_array_value(doc, leaf); break; case T_HASH: return leaf_hash_value(doc, leaf); break; default: rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->rtype); break; } } return leaf->value; } inline static Doc self_doc(VALUE self) { Doc doc = DATA_PTR(self); if (0 == doc) { rb_raise(rb_eIOError, "Document already closed or not open."); } return doc; } static void skip_comment(ParseInfo pi) { pi->s++; // skip first / if ('*' == *pi->s) { pi->s++; for (; '\0' != *pi->s; pi->s++) { if ('*' == *pi->s && '/' == *(pi->s + 1)) { pi->s++; return; } else if ('\0' == *pi->s) { raise_error("comment not terminated", pi->str, pi->s); } } } else if ('/' == *pi->s) { for (; 1; pi->s++) { switch (*pi->s) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { raise_error("invalid comment", pi->str, pi->s); } } #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif static void leaf_fixnum_value(Leaf leaf) { char * s = leaf->str; int64_t n = 0; int neg = 0; int big = 0; if ('-' == *s) { s++; neg = 1; } else if ('+' == *s) { s++; } for (; '0' <= *s && *s <= '9'; s++) { n = n * 10 + (*s - '0'); if (NUM_MAX <= n) { big = 1; } } if (big) { char c = *s; *s = '\0'; leaf->value = rb_cstr_to_inum(leaf->str, 10, 0); *s = c; } else { if (neg) { n = -n; } leaf->value = rb_ll2inum(n); } leaf->value_type = RUBY_VAL; } static void leaf_float_value(Leaf leaf) { leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1)); leaf->value_type = RUBY_VAL; } static VALUE leaf_array_value(Doc doc, Leaf leaf) { volatile VALUE a = rb_ary_new(); if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { rb_ary_push(a, leaf_value(doc, e)); e = e->next; } while (e != first); } return a; } static VALUE leaf_hash_value(Doc doc, Leaf leaf) { volatile VALUE h = rb_hash_new(); if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; volatile VALUE key; do { key = rb_str_new2(e->key); key = oj_encode(key); rb_hash_aset(h, key, leaf_value(doc, e)); e = e->next; } while (e != first); } return h; } static Leaf read_next(ParseInfo pi) { Leaf leaf = 0; if ((void *)&leaf < pi->stack_min) { rb_raise(rb_eSysStackError, "JSON is too deeply nested"); } next_non_white(pi); // skip white space switch (*pi->s) { case '{': leaf = read_obj(pi); break; case '[': leaf = read_array(pi); break; case '"': leaf = read_str(pi); break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': leaf = read_num(pi); break; case 't': leaf = read_true(pi); break; case 'f': leaf = read_false(pi); break; case 'n': leaf = read_nil(pi); break; case '\0': default: break; // returns 0 } pi->doc->size++; return leaf; } static Leaf read_obj(ParseInfo pi) { Leaf h = leaf_new(pi->doc, T_HASH); char * end; const char *key = 0; Leaf val = 0; pi->s++; next_non_white(pi); if ('}' == *pi->s) { pi->s++; return h; } while (1) { next_non_white(pi); key = 0; val = 0; if ('"' != *pi->s || 0 == (key = read_quoted_value(pi))) { raise_error("unexpected character", pi->str, pi->s); } next_non_white(pi); if (':' == *pi->s) { pi->s++; } else { raise_error("invalid format, expected :", pi->str, pi->s); } if (0 == (val = read_next(pi))) { // printf("*** '%s'\n", pi->s); raise_error("unexpected character", pi->str, pi->s); } end = pi->s; val->key = key; val->parent_type = T_HASH; leaf_append_element(h, val); next_non_white(pi); if ('}' == *pi->s) { pi->s++; *end = '\0'; break; } else if (',' == *pi->s) { pi->s++; } else { // printf("*** '%s'\n", pi->s); raise_error("invalid format, expected , or } while in an object", pi->str, pi->s); } *end = '\0'; } return h; } static Leaf read_array(ParseInfo pi) { Leaf a = leaf_new(pi->doc, T_ARRAY); Leaf e; char *end; int cnt = 0; pi->s++; next_non_white(pi); if (']' == *pi->s) { pi->s++; return a; } while (1) { next_non_white(pi); if (0 == (e = read_next(pi))) { raise_error("unexpected character", pi->str, pi->s); } cnt++; e->index = cnt; e->parent_type = T_ARRAY; leaf_append_element(a, e); end = pi->s; next_non_white(pi); if (',' == *pi->s) { pi->s++; } else if (']' == *pi->s) { pi->s++; *end = '\0'; break; } else { raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s); } *end = '\0'; } return a; } static Leaf read_str(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_STRING); leaf->str = read_quoted_value(pi); return leaf; } static Leaf read_num(ParseInfo pi) { char *start = pi->s; int type = T_FIXNUM; Leaf leaf; if ('-' == *pi->s) { pi->s++; } // digits for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } if ('.' == *pi->s) { type = T_FLOAT; pi->s++; for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } } if ('e' == *pi->s || 'E' == *pi->s) { pi->s++; if ('-' == *pi->s || '+' == *pi->s) { pi->s++; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } } leaf = leaf_new(pi->doc, type); leaf->str = start; return leaf; } static Leaf read_true(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_TRUE); pi->s++; if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) { raise_error("invalid format, expected 'true'", pi->str, pi->s); } pi->s += 3; return leaf; } static Leaf read_false(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_FALSE); pi->s++; if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) { raise_error("invalid format, expected 'false'", pi->str, pi->s); } pi->s += 4; return leaf; } static Leaf read_nil(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_NIL); pi->s++; if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) { raise_error("invalid format, expected 'nil'", pi->str, pi->s); } pi->s += 3; return leaf; } static uint32_t read_4hex(ParseInfo pi, const char *h) { uint32_t b = 0; int i; for (i = 0; i < 4; i++, h++) { b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { raise_error("invalid hex character", pi->str, pi->s); } } return b; } static char *unicode_to_chars(ParseInfo pi, char *t, uint32_t code) { if (0x0000007F >= code) { *t++ = (char)code; } else if (0x000007FF >= code) { *t++ = 0xC0 | (code >> 6); *t++ = 0x80 | (0x3F & code); } else if (0x0000FFFF >= code) { *t++ = 0xE0 | (code >> 12); *t++ = 0x80 | ((code >> 6) & 0x3F); *t++ = 0x80 | (0x3F & code); } else if (0x001FFFFF >= code) { *t++ = 0xF0 | (code >> 18); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t++ = 0x80 | (0x3F & code); } else if (0x03FFFFFF >= code) { *t++ = 0xF8 | (code >> 24); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t++ = 0x80 | (0x3F & code); } else if (0x7FFFFFFF >= code) { *t++ = 0xFC | (code >> 30); *t++ = 0x80 | ((code >> 24) & 0x3F); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t++ = 0x80 | (0x3F & code); } else { raise_error("invalid Unicode character", pi->str, pi->s); } return t; } // Assume the value starts immediately and goes until the quote character is // reached again. Do not read the character after the terminating quote. static char *read_quoted_value(ParseInfo pi) { char *value = 0; char *h = pi->s; // head char *t = h; // tail h++; // skip quote character t++; value = h; for (; '"' != *h; h++, t++) { if ('\0' == *h) { pi->s = h; raise_error("quoted string not terminated", pi->str, pi->s); } else if ('\\' == *h) { h++; switch (*h) { case 'n': *t = '\n'; break; case 'r': *t = '\r'; break; case 't': *t = '\t'; break; case 'f': *t = '\f'; break; case 'b': *t = '\b'; break; case '"': *t = '"'; break; case '/': *t = '/'; break; case '\\': *t = '\\'; break; case 'u': { uint32_t code; h++; code = read_4hex(pi, h); h += 3; if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; h++; if ('\\' != *h || 'u' != *(h + 1)) { pi->s = h; raise_error("invalid escaped character", pi->str, pi->s); } h += 2; c2 = read_4hex(pi, h); h += 3; c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } t = unicode_to_chars(pi, t, code); t--; break; } default: pi->s = h; raise_error("invalid escaped character", pi->str, pi->s); break; } } else if (t != h) { *t = *h; } } *t = '\0'; // terminate value pi->s = h + 1; return value; } // doc support functions inline static void doc_init(Doc doc) { memset(doc, 0, sizeof(struct _doc)); doc->where = doc->where_path; doc->self = Qundef; doc->batches = &doc->batch0; } static void doc_free(Doc doc) { if (0 != doc) { Batch b; while (0 != (b = doc->batches)) { doc->batches = doc->batches->next; if (&doc->batch0 != b) { xfree(b); } } // xfree(f); } } static VALUE protect_open_proc(VALUE x) { ParseInfo pi = (ParseInfo)x; pi->doc->data = read_next(pi); // parse *pi->doc->where = pi->doc->data; pi->doc->where = pi->doc->where_path; if (rb_block_given_p()) { return rb_yield(pi->doc->self); // caller processing } return Qnil; } static void free_doc_cb(void *x) { Doc doc = (Doc)x; if (0 != doc) { xfree(doc->json); doc_free(doc); } } static void mark_leaf(Leaf leaf) { switch (leaf->value_type) { case COL_VAL: if (NULL != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { mark_leaf(e); e = e->next; } while (e != first); } break; case RUBY_VAL: mark(leaf->value); break; default: break; } } static void mark_doc(void *ptr) { if (NULL != ptr) { Doc doc = (Doc)ptr; mark(doc->self); mark_leaf(doc->data); } } #ifdef HAVE_RB_GC_MARK_MOVABLE static void compact_leaf(Leaf leaf) { switch (leaf->value_type) { case COL_VAL: if (NULL != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { compact_leaf(e); e = e->next; } while (e != first); } break; case RUBY_VAL: leaf->value = rb_gc_location(leaf->value); break; default: break; } } static void compact_doc(void *ptr) { Doc doc = (Doc)ptr; if (doc) { doc->self = rb_gc_location(doc->self); compact_leaf(doc->data); } } #endif static const rb_data_type_t oj_doc_type = { "Oj/doc", { mark_doc, free_doc_cb, NULL, #ifdef HAVE_RB_GC_MARK_MOVABLE compact_doc, #endif }, 0, 0, }; static VALUE parse_json(VALUE clas, char *json, bool given, bool allocated) { struct _parseInfo pi; volatile VALUE result = Qnil; Doc doc; int ex = 0; volatile VALUE self; // TBD are both needed? is stack allocation ever needed? if (given) { doc = ALLOCA_N(struct _doc, 1); } else { doc = ALLOC(struct _doc); } // skip UTF-8 BOM if present if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) { pi.str = json + 3; } else { pi.str = json; } pi.s = pi.str; doc_init(doc); pi.doc = doc; #if IS_WINDOWS // assume a 1M stack and give half to ruby pi.stack_min = (void*)((char*)&pi - (512 * 1024)); #else { struct rlimit lim; if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) { // let 3/4ths of the stack be used only pi.stack_min = (void *)((char *)&lim - (lim.rlim_cur / 4 * 3)); } else { pi.stack_min = 0; // indicates not to check stack limit } } #endif self = TypedData_Wrap_Struct(clas, &oj_doc_type, doc); doc->self = self; doc->json = json; DATA_PTR(doc->self) = doc; result = rb_protect(protect_open_proc, (VALUE)&pi, &ex); if (given || 0 != ex) { DATA_PTR(doc->self) = NULL; doc_free(pi.doc); if (allocated && 0 != ex) { // will jump so caller will not free xfree(json); } rb_gc_enable(); } else { result = doc->self; } if (0 != ex) { rb_jump_tag(ex); } return result; } static Leaf get_doc_leaf(Doc doc, const char *path) { Leaf leaf = *doc->where; if (0 != doc->data && 0 != path) { Leaf stack[MAX_STACK]; Leaf *lp; if ('/' == *path) { path++; *stack = doc->data; lp = stack; } else if (doc->where == doc->where_path) { *stack = doc->data; lp = stack; } else { size_t cnt = doc->where - doc->where_path; if (MAX_STACK <= cnt) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } memcpy(stack, doc->where_path, sizeof(Leaf) * (cnt + 1)); lp = stack + cnt; } return get_leaf(stack, lp, path); } return leaf; } static const char *next_slash(const char *s) { for (; '\0' != *s; s++) { if ('\\' == *s) { s++; if ('\0' == *s) { break; } } else if ('/' == *s) { return s; } } return NULL; } static bool key_match(const char *pat, const char *key, int plen) { for (; 0 < plen; plen--, pat++, key++) { if ('\\' == *pat) { plen--; pat++; } if (*pat != *key) { return false; } } return '\0' == *key; } static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path) { Leaf leaf = *lp; if (MAX_STACK <= lp - stack) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } if ('\0' != *path) { if ('.' == *path && '.' == *(path + 1)) { path += 2; if ('/' == *path) { path++; } if (stack < lp) { leaf = get_leaf(stack, lp - 1, path); } else { return 0; } } else if (NULL == leaf->elements) { leaf = NULL; } else if (STR_VAL == leaf->value_type || RUBY_VAL == leaf->value_type) { // We are trying to get a children of a leaf, which // doesn't exist. leaf = NULL; } else if (COL_VAL == leaf->value_type) { Leaf first = leaf->elements->next; Leaf e = first; int type = leaf->rtype; leaf = NULL; if (T_ARRAY == type) { int cnt = 0; for (; '0' <= *path && *path <= '9'; path++) { cnt = cnt * 10 + (*path - '0'); } if ('/' == *path) { path++; } do { if (1 >= cnt) { lp++; *lp = e; leaf = get_leaf(stack, lp, path); break; } cnt--; e = e->next; } while (e != first); } else if (T_HASH == type) { const char *key = path; const char *slash = next_slash(path); int klen; leaf = NULL; if (0 == slash) { klen = (int)strlen(key); path += klen; } else { klen = (int)(slash - key); path += klen + 1; } do { if (key_match(key, e->key, klen)) { lp++; *lp = e; leaf = get_leaf(stack, lp, path); break; } e = e->next; } while (e != first); } } } return leaf; } static void each_leaf(Doc doc, VALUE self) { if (COL_VAL == (*doc->where)->value_type) { if (0 != (*doc->where)->elements) { Leaf first = (*doc->where)->elements->next; Leaf e = first; doc->where++; if (MAX_STACK <= doc->where - doc->where_path) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } do { *doc->where = e; each_leaf(doc, self); e = e->next; } while (e != first); doc->where--; } } else { rb_yield(self); } } static int move_step(Doc doc, const char *path, int loc) { if (MAX_STACK <= doc->where - doc->where_path) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } if ('\0' == *path) { loc = 0; } else { Leaf leaf; if (0 == doc->where || 0 == (leaf = *doc->where)) { printf("*** Internal error at %s\n", path); return loc; } if ('.' == *path && '.' == *(path + 1)) { Leaf init = *doc->where; path += 2; if (doc->where == doc->where_path) { return loc; } if ('/' == *path) { path++; } *doc->where = 0; doc->where--; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = init; doc->where++; } } else if (COL_VAL == leaf->value_type && 0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; if (T_ARRAY == leaf->rtype) { int cnt = 0; for (; '0' <= *path && *path <= '9'; path++) { cnt = cnt * 10 + (*path - '0'); } if ('/' == *path) { path++; } else if ('\0' != *path) { return loc; } do { if (1 >= cnt) { doc->where++; *doc->where = e; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = 0; doc->where--; } break; } cnt--; e = e->next; } while (e != first); } else if (T_HASH == leaf->rtype) { const char *key = path; const char *slash = next_slash(path); int klen; if (0 == slash) { klen = (int)strlen(key); path += klen; } else { klen = (int)(slash - key); path += klen + 1; } do { if (key_match(key, e->key, klen)) { doc->where++; *doc->where = e; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = 0; doc->where--; } break; } e = e->next; } while (e != first); } } } return loc; } static void each_value(Doc doc, Leaf leaf) { if (COL_VAL == leaf->value_type) { if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { each_value(doc, e); e = e->next; } while (e != first); } } else { rb_yield(leaf_value(doc, leaf)); } } // doc functions /* @overload open(json) { |doc| ... } => Object * * Parses a JSON document String and then yields to the provided block if one * is given with an instance of the Oj::Doc as the single yield parameter. If * a block is not given then an Oj::Doc instance is returned and must be * closed with a call to the #close() method when no longer needed. * * @param [String] json JSON document string * @yieldparam [Oj::Doc] doc parsed JSON document * @yieldreturn [Object] returns the result of the yield as the result of the * method call * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4 * # or as an alternative * doc = Oj::Doc.open('[1,2,3]') * doc.size() #=> 4 * doc.close() */ static VALUE doc_open(VALUE clas, VALUE str) { char * json; size_t len; volatile VALUE obj; int given = rb_block_given_p(); int allocate; Check_Type(str, T_STRING); len = (int)RSTRING_LEN(str) + 1; allocate = (SMALL_JSON < len || !given); if (allocate) { json = ALLOC_N(char, len); } else { json = ALLOCA_N(char, len); } // It should not be necessaary to stop GC but if it is not stopped and a // large string is parsed that string is corrupted or freed during // parsing. I'm not sure what is going on exactly but disabling GC avoids // the issue. rb_gc_disable(); memcpy(json, StringValuePtr(str), len); obj = parse_json(clas, json, given, allocate); rb_gc_enable(); if (given && allocate) { xfree(json); } return obj; } /* @overload open_file(filename) { |doc| ... } => Object * * Parses a JSON document from a file and then yields to the provided block if * one is given with an instance of the Oj::Doc as the single yield * parameter. If a block is not given then an Oj::Doc instance is returned and * must be closed with a call to the #close() method when no longer needed. * * @param [String] filename name of file that contains a JSON document * @yieldparam [Oj::Doc] doc parsed JSON document * @yieldreturn [Object] returns the result of the yield as the result of the * method call * @example * File.open('array.json', 'w') { |f| f.write('[1,2,3]') } * Oj::Doc.open_file(filename) { |doc| doc.size() } #=> 4 * # or as an alternative * doc = Oj::Doc.open_file(filename) * doc.size() #=> 4 * doc.close() */ static VALUE doc_open_file(VALUE clas, VALUE filename) { char * path; char * json; FILE * f; size_t len; volatile VALUE obj; int given = rb_block_given_p(); int allocate; Check_Type(filename, T_STRING); path = StringValuePtr(filename); if (0 == (f = fopen(path, "r"))) { rb_raise(rb_eIOError, "%s", strerror(errno)); } fseek(f, 0, SEEK_END); len = ftell(f); allocate = (SMALL_JSON < len || !given); if (allocate) { json = ALLOC_N(char, len + 1); } else { json = ALLOCA_N(char, len + 1); } fseek(f, 0, SEEK_SET); if (len != fread(json, 1, len, f)) { fclose(f); rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")), "Failed to read %lu bytes from %s.", (unsigned long)len, path); } fclose(f); json[len] = '\0'; rb_gc_disable(); obj = parse_json(clas, json, given, allocate); rb_gc_enable(); if (given && allocate) { xfree(json); } return obj; } static int esc_strlen(const char *s) { int cnt = 0; for (; '\0' != *s; s++, cnt++) { if ('/' == *s) { cnt++; } } return cnt; } static char *append_key(char *p, const char *key) { for (; '\0' != *key; p++, key++) { if ('/' == *key) { *p++ = '\\'; } *p = *key; } return p; } /* Document-method: parse * @see Oj::Doc.open */ /* @overload where() => String * * Returns a String that describes the absolute path to the current location * in the JSON document. */ static VALUE doc_where(VALUE self) { Doc doc = self_doc(self); if (0 == *doc->where_path || doc->where == doc->where_path) { return oj_slash_string; } else { Leaf * lp; Leaf leaf; size_t size = 3; // leading / and terminating \0 char * path; char * p; for (lp = doc->where_path; lp <= doc->where; lp++) { leaf = *lp; if (T_HASH == leaf->parent_type) { size += esc_strlen((*lp)->key) + 1; } else if (T_ARRAY == leaf->parent_type) { size += ((*lp)->index < 100) ? 3 : 11; } } path = ALLOCA_N(char, size); p = path; for (lp = doc->where_path; lp <= doc->where; lp++) { leaf = *lp; if (T_HASH == leaf->parent_type) { p = append_key(p, (*lp)->key); } else if (T_ARRAY == leaf->parent_type) { p = ulong_fill(p, (*lp)->index); } *p++ = '/'; } *--p = '\0'; return rb_str_new(path, p - path); } } /* @overload where?() => String * @deprecated * Returns a String that describes the absolute path to the current location * in the JSON document. */ static VALUE doc_where_q(VALUE self) { return doc_where(self); } /* @overload path() => String * * Returns a String that describes the absolute path to the current location * in the JSON document. */ static VALUE doc_path(VALUE self) { return doc_where(self); } /* @overload local_key() => String, Fixnum, nil * * Returns the final key to the current location. * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.local_key() } #=> 2 * Oj::Doc.open('{"one":3}') { |doc| doc.move('/one'); doc.local_key() } #=> * "one" Oj::Doc.open('[1,2,3]') { |doc| doc.local_key() } * #=> nil */ static VALUE doc_local_key(VALUE self) { Doc doc = self_doc(self); Leaf leaf = *doc->where; volatile VALUE key = Qnil; if (T_HASH == leaf->parent_type) { key = rb_str_new2(leaf->key); key = oj_encode(key); } else if (T_ARRAY == leaf->parent_type) { key = LONG2NUM(leaf->index); } return key; } /* @overload home() => nil * * Moves the document marker or location to the hoot or home position. The * same operation can be performed with a Oj::Doc.move('/'). * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.home(); doc.where? } * #=> '/' */ static VALUE doc_home(VALUE self) { Doc doc = self_doc(self); *doc->where_path = doc->data; doc->where = doc->where_path; return oj_slash_string; } /* @overload type(path=nil) => Class * * Returns the Class of the data value at the location identified by the path * or the current location if the path is nil or not provided. This method * does not create the Ruby Object at the location specified so the overhead * is low. * @param [String] path path to the location to get the type of if provided * @example * Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array * Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum */ static VALUE doc_type(int argc, VALUE *argv, VALUE self) { Doc doc = self_doc(self); Leaf leaf; const char *path = 0; VALUE type = Qnil; if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (0 != (leaf = get_doc_leaf(doc, path))) { switch (leaf->rtype) { case T_NIL: type = rb_cNilClass; break; case T_TRUE: type = rb_cTrueClass; break; case T_FALSE: type = rb_cFalseClass; break; case T_STRING: type = rb_cString; break; #ifdef RUBY_INTEGER_UNIFICATION case T_FIXNUM: type = rb_cInteger; break; #else case T_FIXNUM: type = rb_cFixnum; break; #endif case T_FLOAT: type = rb_cFloat; break; case T_ARRAY: type = rb_cArray; break; case T_HASH: type = rb_cHash; break; default: break; } } return type; } /* @overload fetch(path=nil,default=nil) => nil, true, false, Fixnum, Float, String, Array, * Hash * * Returns the value at the location identified by the path or the current * location if the path is nil or not provided. This method will create and * return an Array or Hash if that is the type of Object at the location * specified. This is more expensive than navigating to the leaves of the JSON * document. If a default is provided that is used if no value if found. * @param [String] path path to the location to get the type of if provided * @example * Oj::Doc.open('[1,2]') { |doc| doc.fetch() } #=> [1, 2] * Oj::Doc.open('[1,2]') { |doc| doc.fetch('/1') } #=> 1 */ static VALUE doc_fetch(int argc, VALUE *argv, VALUE self) { Doc doc; Leaf leaf; volatile VALUE val = Qnil; const char * path = 0; doc = self_doc(self); if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if (2 == argc) { val = argv[1]; } } if (0 != (leaf = get_doc_leaf(doc, path))) { val = leaf_value(doc, leaf); } return val; } /* @overload exists?(path) => true, false * * Returns true if the value at the location identified by the path exists. * @param [String] path path to the location * @example * Oj::Doc.open('[1,2]') { |doc| doc.exists?('/1') } #=> true * Oj::Doc.open('[1,2]') { |doc| doc.exists?('/3') } #=> false */ static VALUE doc_exists(VALUE self, VALUE str) { Doc doc; Leaf leaf; doc = self_doc(self); Check_Type(str, T_STRING); if (0 != (leaf = get_doc_leaf(doc, StringValuePtr(str)))) { if (NULL != leaf) { return Qtrue; } } return Qfalse; } /* @overload each_leaf(path=nil) => nil * * Yields to the provided block for each leaf node with the identified * location of the JSON document as the root. The parameter passed to the * block on yield is the Doc instance after moving to the child location. * @param [String] path if provided it identified the top of the branch to * process the leaves of * @yieldparam [Doc] Doc at the child location * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = {} * doc.each_leaf() { |d| result[d.where?] = d.fetch() } * result * } * #=> ["/1" => 3, "/2/1" => 2, "/2/2" => 1] */ static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Leaf save_path[MAX_STACK]; Doc doc = self_doc(self); const char *path = 0; size_t wlen; wlen = doc->where - doc->where_path; if (0 < wlen) { memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1)); } if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != move_step(doc, path, 1)) { if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1)); } return Qnil; } } each_leaf(doc, self); if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1)); } } return Qnil; } /* @overload move(path) => nil * * Moves the document marker to the path specified. The path can an absolute * path or a relative path. * @param [String] path path to the location to move to * @example * Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=> * "/one/2" */ static VALUE doc_move(VALUE self, VALUE str) { Doc doc = self_doc(self); const char *path; int loc; Check_Type(str, T_STRING); path = StringValuePtr(str); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != (loc = move_step(doc, path, 1))) { rb_raise(rb_eArgError, "Failed to locate element %d of the path %s.", loc, path); } return Qnil; } /* @overload each_child(path=nil) { |doc| ... } => nil * * Yields to the provided block for each immediate child node with the * identified location of the JSON document as the root. The parameter passed * to the block on yield is the Doc instance after moving to the child * location. * @param [String] path if provided it identified the top of the branch to * process the children of * @yieldparam [Doc] Doc at the child location * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value('/2') { |doc| result << doc.where? } * result * } * #=> ["/2/1", "/2/2"] */ static VALUE doc_each_child(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Leaf save_path[MAX_STACK]; Doc doc = self_doc(self); const char *path = 0; size_t wlen; wlen = doc->where - doc->where_path; if (0 < wlen) { memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1)); } if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != move_step(doc, path, 1)) { if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1)); } return Qnil; } } if (COL_VAL == (*doc->where)->value_type && 0 != (*doc->where)->elements) { Leaf first = (*doc->where)->elements->next; Leaf e = first; doc->where++; do { *doc->where = e; rb_yield(self); e = e->next; } while (e != first); } if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1)); } } return Qnil; } /* @overload each_value(path=nil) { |val| ... } => nil * * Yields to the provided block for each leaf value in the identified location * of the JSON document. The parameter passed to the block on yield is the * value of the leaf. Only those leaves below the element specified by the * path parameter are processed. * @param [String] path if provided it identified the top of the branch to * process the leaf values of * @yieldparam [Object] val each leaf value * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value() { |v| result << v } * result * } * #=> [3, 2, 1] * * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value('/2') { |v| result << v } * result * } * #=> [2, 1] */ static VALUE doc_each_value(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Doc doc = self_doc(self); const char *path = 0; Leaf leaf; if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (0 != (leaf = get_doc_leaf(doc, path))) { each_value(doc, leaf); } } return Qnil; } /* @overload dump(path, filename) * * Dumps the document or nodes to a new JSON document. It uses the default * options for generating the JSON. * @param path [String] if provided it identified the top of the branch to * dump to JSON * @param filename [String] if provided it is the filename to write the output * to * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * doc.dump('/2') * } * #=> "[2,1]" */ static VALUE doc_dump(int argc, VALUE *argv, VALUE self) { Doc doc = self_doc(self); Leaf leaf; const char *path = 0; const char *filename = 0; if (1 <= argc) { if (Qnil != *argv) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (2 <= argc) { Check_Type(argv[1], T_STRING); filename = StringValuePtr(argv[1]); } } if (0 != (leaf = get_doc_leaf(doc, path))) { volatile VALUE rjson; if (0 == filename) { char buf[4096]; struct _out out; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.omit_nil = oj_default_options.dump_opts.omit_nil; oj_dump_leaf_to_json(leaf, &oj_default_options, &out); rjson = rb_str_new2(out.buf); if (out.allocated) { xfree(out.buf); } } else { oj_write_leaf_to_file(leaf, filename, &oj_default_options); rjson = Qnil; } return rjson; } return Qnil; } /* @overload size() => Fixnum * * Returns the number of nodes in the JSON document where a node is any one of * the basic JSON components. * @return Returns the size of the JSON document. * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4 */ static VALUE doc_size(VALUE self) { return ULONG2NUM(((Doc)DATA_PTR(self))->size); } /* @overload close() => nil * * Closes an open document. No further calls to the document will be valid * after closing. * @example * doc = Oj::Doc.open('[1,2,3]') * doc.size() #=> 4 * doc.close() */ static VALUE doc_close(VALUE self) { Doc doc = self_doc(self); rb_gc_unregister_address(&doc->self); DATA_PTR(doc->self) = 0; if (0 != doc) { xfree(doc->json); doc_free(doc); xfree(doc); } return Qnil; } #if 0 // hack to keep the doc generator happy Oj = rb_define_module("Oj"); #endif static VALUE doc_not_implemented(VALUE self) { rb_raise(rb_eNotImpError, "Not implemented."); return Qnil; } /* Document-class: Oj::Doc * * The Doc class is used to parse and navigate a JSON document. The model it * employs is that of a document that while open can be navigated and values * extracted. Once the document is closed the document can not longer be * accessed. This allows the parsing and data extraction to be extremely fast * compared to other JSON parses. * * An Oj::Doc class is not created directly but the _open()_ class method is * used to open a document and the yield parameter to the block of the #open() * call is the Doc instance. The Doc instance can be moved across, up, and * down the JSON document. At each element the data associated with the * element can be extracted. It is also possible to just provide a path to the * data to be extracted and retrieve the data in that manner. * * For many of the methods a path is used to describe the location of an * element. Paths follow a subset of the XPath syntax. The slash ('/') * character is the separator. Each step in the path identifies the next * branch to take through the document. A JSON object will expect a key string * while an array will expect a positive index. A .. step indicates a move up * the JSON document. * * @example * json = %{[ * { * "one" : 1, * "two" : 2 * }, * { * "three" : 3, * "four" : 4 * } * ]} * # move and get value * Oj::Doc.open(json) do |doc| * doc.move('/1/two') * # doc location is now at the 'two' element of the hash that is the first * element of the array. doc.fetch() end * #=> 2 * * # Now try again using a path to Oj::Doc.fetch() directly and not using a * block. doc = Oj::Doc.open(json) doc.fetch('/2/three') #=> 3 doc.close() */ void oj_init_doc() { oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject); rb_define_singleton_method(oj_doc_class, "open", doc_open, 1); rb_define_singleton_method(oj_doc_class, "open_file", doc_open_file, 1); rb_define_singleton_method(oj_doc_class, "parse", doc_open, 1); rb_define_method(oj_doc_class, "where?", doc_where_q, 0); rb_define_method(oj_doc_class, "where", doc_where, 0); rb_define_method(oj_doc_class, "path", doc_path, 0); rb_define_method(oj_doc_class, "local_key", doc_local_key, 0); rb_define_method(oj_doc_class, "home", doc_home, 0); rb_define_method(oj_doc_class, "type", doc_type, -1); rb_define_method(oj_doc_class, "fetch", doc_fetch, -1); rb_define_method(oj_doc_class, "exists?", doc_exists, 1); rb_define_method(oj_doc_class, "each_leaf", doc_each_leaf, -1); rb_define_method(oj_doc_class, "move", doc_move, 1); rb_define_method(oj_doc_class, "each_child", doc_each_child, -1); rb_define_method(oj_doc_class, "each_value", doc_each_value, -1); rb_define_method(oj_doc_class, "dump", doc_dump, -1); rb_define_method(oj_doc_class, "size", doc_size, 0); rb_define_method(oj_doc_class, "close", doc_close, 0); rb_define_method(oj_doc_class, "clone", doc_not_implemented, 0); rb_define_method(oj_doc_class, "dup", doc_not_implemented, 0); } oj-3.13.9/ext/oj/parser.c0000644000004100000410000014356214136373754015147 0ustar www-datawww-data// Copyright (c) 2020, 2021, Peter Ohler, All rights reserved. #include "parser.h" #include #include "oj.h" #define DEBUG 0 #define USE_THREAD_LIMIT 0 // #define USE_THREAD_LIMIT 100000 #define MAX_EXP 4932 // max in the pow_map #define MAX_POW 400 #define MIN_SLEEP (1000000000LL / (double)CLOCKS_PER_SEC) // 9,223,372,036,854,775,807 #define BIG_LIMIT LLONG_MAX / 10 #define FRAC_LIMIT 10000000000000000ULL // Give better performance with indented JSON but worse with unindented. //#define SPACE_JUMP enum { SKIP_CHAR = 'a', SKIP_NEWLINE = 'b', VAL_NULL = 'c', VAL_TRUE = 'd', VAL_FALSE = 'e', VAL_NEG = 'f', VAL0 = 'g', VAL_DIGIT = 'h', VAL_QUOTE = 'i', OPEN_ARRAY = 'k', OPEN_OBJECT = 'l', CLOSE_ARRAY = 'm', CLOSE_OBJECT = 'n', AFTER_COMMA = 'o', KEY_QUOTE = 'p', COLON_COLON = 'q', NUM_SPC = 'r', NUM_NEWLINE = 's', NUM_DOT = 't', NUM_COMMA = 'u', NUM_FRAC = 'v', FRAC_E = 'w', EXP_SIGN = 'x', EXP_DIGIT = 'y', STR_QUOTE = 'z', NEG_DIGIT = '-', STR_SLASH = 'A', ESC_OK = 'B', BIG_DIGIT = 'C', BIG_DOT = 'D', U_OK = 'E', TOKEN_OK = 'F', NUM_CLOSE_OBJECT = 'G', NUM_CLOSE_ARRAY = 'H', BIG_FRAC = 'I', BIG_E = 'J', BIG_EXP_SIGN = 'K', BIG_EXP = 'L', UTF1 = 'M', // expect 1 more follow byte NUM_DIGIT = 'N', NUM_ZERO = 'O', UTF2 = 'P', // expect 2 more follow byte UTF3 = 'Q', // expect 3 more follow byte STR_OK = 'R', UTFX = 'S', // following bytes ESC_U = 'U', CHAR_ERR = '.', DONE = 'X', }; /* 0123456789abcdef0123456789abcdef */ static const char value_map[257] = "\ X........ab..a..................\ a.i..........f..ghhhhhhhhh......\ ...........................k.m..\ ......e.......c.....d......l.n..\ ................................\ ................................\ ................................\ ................................v"; static const char null_map[257] = "\ ................................\ ............o...................\ ................................\ ............F........F..........\ ................................\ ................................\ ................................\ ................................N"; static const char true_map[257] = "\ ................................\ ............o...................\ ................................\ .....F............F..F..........\ ................................\ ................................\ ................................\ ................................T"; static const char false_map[257] = "\ ................................\ ............o...................\ ................................\ .F...F......F......F............\ ................................\ ................................\ ................................\ ................................F"; static const char comma_map[257] = "\ .........ab..a..................\ a.i..........f..ghhhhhhhhh......\ ...........................k....\ ......e.......c.....d......l....\ ................................\ ................................\ ................................\ ................................,"; static const char after_map[257] = "\ X........ab..a..................\ a...........o...................\ .............................m..\ .............................n..\ ................................\ ................................\ ................................\ ................................a"; static const char key1_map[257] = "\ .........ab..a..................\ a.p.............................\ ................................\ .............................n..\ ................................\ ................................\ ................................\ ................................K"; static const char key_map[257] = "\ .........ab..a..................\ a.p.............................\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................k"; static const char colon_map[257] = "\ .........ab..a..................\ a.........................q.....\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................:"; static const char neg_map[257] = "\ ................................\ ................O---------......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................-"; static const char zero_map[257] = "\ .........rs..r..................\ r...........u.t.................\ .............................H..\ .............................G..\ ................................\ ................................\ ................................\ ................................0"; static const char digit_map[257] = "\ .........rs..r..................\ r...........u.t.NNNNNNNNNN......\ .....w.......................H..\ .....w.......................G..\ ................................\ ................................\ ................................\ ................................d"; static const char dot_map[257] = "\ ................................\ ................vvvvvvvvvv......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................."; static const char frac_map[257] = "\ .........rs..r..................\ r...........u...vvvvvvvvvv......\ .....w.......................H..\ .....w.......................G..\ ................................\ ................................\ ................................\ ................................f"; static const char exp_sign_map[257] = "\ ................................\ ...........x.x..yyyyyyyyyy......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................x"; static const char exp_zero_map[257] = "\ ................................\ ................yyyyyyyyyy......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................z"; static const char exp_map[257] = "\ .........rs..r..................\ r...........u...yyyyyyyyyy......\ .............................H..\ .............................G..\ ................................\ ................................\ ................................\ ................................X"; static const char big_digit_map[257] = "\ .........rs..r..................\ r...........u.D.CCCCCCCCCC......\ .....J.......................H..\ .....J.......................G..\ ................................\ ................................\ ................................\ ................................D"; static const char big_dot_map[257] = "\ ................................\ ................IIIIIIIIII......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................o"; static const char big_frac_map[257] = "\ .........rs..r..................\ r...........u...IIIIIIIIII......\ .....J.......................H..\ .....J.......................G..\ ................................\ ................................\ ................................\ ................................g"; static const char big_exp_sign_map[257] = "\ ................................\ ...........K.K..LLLLLLLLLL......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................B"; static const char big_exp_zero_map[257] = "\ ................................\ ................LLLLLLLLLL......\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................Z"; static const char big_exp_map[257] = "\ .........rs..r..................\ r...........u...LLLLLLLLLL......\ .............................H..\ .............................G..\ ................................\ ................................\ ................................\ ................................Y"; static const char string_map[257] = "\ ................................\ RRzRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\ RRRRRRRRRRRRRRRRRRRRRRRRRRRRARRR\ RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\ ................................\ ................................\ MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\ PPPPPPPPPPPPPPPPQQQQQQQQ........s"; static const char esc_map[257] = "\ ................................\ ..B............B................\ ............................B...\ ..B...B.......B...B.BU..........\ ................................\ ................................\ ................................\ ................................~"; static const char esc_byte_map[257] = "\ ................................\ ..\"............/................\ ............................\\...\ ..\b...\f.......\n...\r.\t..........\ ................................\ ................................\ ................................\ ................................b"; static const char u_map[257] = "\ ................................\ ................EEEEEEEEEE......\ .EEEEEE.........................\ .EEEEEE.........................\ ................................\ ................................\ ................................\ ................................u"; static const char utf_map[257] = "\ ................................\ ................................\ ................................\ ................................\ SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS\ SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS\ ................................\ ................................8"; static const char space_map[257] = "\ .........ab..a..................\ a...............................\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................S"; static const char trail_map[257] = "\ .........ab..a..................\ a...............................\ ................................\ ................................\ ................................\ ................................\ ................................\ ................................R"; static const byte hex_map[256] = "\ ................................\ ................\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09......\ .\x0a\x0b\x0c\x0d\x0e\x0f.........................\ .\x0a\x0b\x0c\x0d\x0e\x0f.........................\ ................................\ ................................\ ................................\ ................................"; static long double pow_map[401] = { 1.0L, 1.0e1L, 1.0e2L, 1.0e3L, 1.0e4L, 1.0e5L, 1.0e6L, 1.0e7L, 1.0e8L, 1.0e9L, 1.0e10L, 1.0e11L, 1.0e12L, 1.0e13L, 1.0e14L, 1.0e15L, 1.0e16L, 1.0e17L, 1.0e18L, 1.0e19L, 1.0e20L, 1.0e21L, 1.0e22L, 1.0e23L, 1.0e24L, 1.0e25L, 1.0e26L, 1.0e27L, 1.0e28L, 1.0e29L, 1.0e30L, 1.0e31L, 1.0e32L, 1.0e33L, 1.0e34L, 1.0e35L, 1.0e36L, 1.0e37L, 1.0e38L, 1.0e39L, 1.0e40L, 1.0e41L, 1.0e42L, 1.0e43L, 1.0e44L, 1.0e45L, 1.0e46L, 1.0e47L, 1.0e48L, 1.0e49L, 1.0e50L, 1.0e51L, 1.0e52L, 1.0e53L, 1.0e54L, 1.0e55L, 1.0e56L, 1.0e57L, 1.0e58L, 1.0e59L, 1.0e60L, 1.0e61L, 1.0e62L, 1.0e63L, 1.0e64L, 1.0e65L, 1.0e66L, 1.0e67L, 1.0e68L, 1.0e69L, 1.0e70L, 1.0e71L, 1.0e72L, 1.0e73L, 1.0e74L, 1.0e75L, 1.0e76L, 1.0e77L, 1.0e78L, 1.0e79L, 1.0e80L, 1.0e81L, 1.0e82L, 1.0e83L, 1.0e84L, 1.0e85L, 1.0e86L, 1.0e87L, 1.0e88L, 1.0e89L, 1.0e90L, 1.0e91L, 1.0e92L, 1.0e93L, 1.0e94L, 1.0e95L, 1.0e96L, 1.0e97L, 1.0e98L, 1.0e99L, 1.0e100L, 1.0e101L, 1.0e102L, 1.0e103L, 1.0e104L, 1.0e105L, 1.0e106L, 1.0e107L, 1.0e108L, 1.0e109L, 1.0e110L, 1.0e111L, 1.0e112L, 1.0e113L, 1.0e114L, 1.0e115L, 1.0e116L, 1.0e117L, 1.0e118L, 1.0e119L, 1.0e120L, 1.0e121L, 1.0e122L, 1.0e123L, 1.0e124L, 1.0e125L, 1.0e126L, 1.0e127L, 1.0e128L, 1.0e129L, 1.0e130L, 1.0e131L, 1.0e132L, 1.0e133L, 1.0e134L, 1.0e135L, 1.0e136L, 1.0e137L, 1.0e138L, 1.0e139L, 1.0e140L, 1.0e141L, 1.0e142L, 1.0e143L, 1.0e144L, 1.0e145L, 1.0e146L, 1.0e147L, 1.0e148L, 1.0e149L, 1.0e150L, 1.0e151L, 1.0e152L, 1.0e153L, 1.0e154L, 1.0e155L, 1.0e156L, 1.0e157L, 1.0e158L, 1.0e159L, 1.0e160L, 1.0e161L, 1.0e162L, 1.0e163L, 1.0e164L, 1.0e165L, 1.0e166L, 1.0e167L, 1.0e168L, 1.0e169L, 1.0e170L, 1.0e171L, 1.0e172L, 1.0e173L, 1.0e174L, 1.0e175L, 1.0e176L, 1.0e177L, 1.0e178L, 1.0e179L, 1.0e180L, 1.0e181L, 1.0e182L, 1.0e183L, 1.0e184L, 1.0e185L, 1.0e186L, 1.0e187L, 1.0e188L, 1.0e189L, 1.0e190L, 1.0e191L, 1.0e192L, 1.0e193L, 1.0e194L, 1.0e195L, 1.0e196L, 1.0e197L, 1.0e198L, 1.0e199L, 1.0e200L, 1.0e201L, 1.0e202L, 1.0e203L, 1.0e204L, 1.0e205L, 1.0e206L, 1.0e207L, 1.0e208L, 1.0e209L, 1.0e210L, 1.0e211L, 1.0e212L, 1.0e213L, 1.0e214L, 1.0e215L, 1.0e216L, 1.0e217L, 1.0e218L, 1.0e219L, 1.0e220L, 1.0e221L, 1.0e222L, 1.0e223L, 1.0e224L, 1.0e225L, 1.0e226L, 1.0e227L, 1.0e228L, 1.0e229L, 1.0e230L, 1.0e231L, 1.0e232L, 1.0e233L, 1.0e234L, 1.0e235L, 1.0e236L, 1.0e237L, 1.0e238L, 1.0e239L, 1.0e240L, 1.0e241L, 1.0e242L, 1.0e243L, 1.0e244L, 1.0e245L, 1.0e246L, 1.0e247L, 1.0e248L, 1.0e249L, 1.0e250L, 1.0e251L, 1.0e252L, 1.0e253L, 1.0e254L, 1.0e255L, 1.0e256L, 1.0e257L, 1.0e258L, 1.0e259L, 1.0e260L, 1.0e261L, 1.0e262L, 1.0e263L, 1.0e264L, 1.0e265L, 1.0e266L, 1.0e267L, 1.0e268L, 1.0e269L, 1.0e270L, 1.0e271L, 1.0e272L, 1.0e273L, 1.0e274L, 1.0e275L, 1.0e276L, 1.0e277L, 1.0e278L, 1.0e279L, 1.0e280L, 1.0e281L, 1.0e282L, 1.0e283L, 1.0e284L, 1.0e285L, 1.0e286L, 1.0e287L, 1.0e288L, 1.0e289L, 1.0e290L, 1.0e291L, 1.0e292L, 1.0e293L, 1.0e294L, 1.0e295L, 1.0e296L, 1.0e297L, 1.0e298L, 1.0e299L, 1.0e300L, 1.0e301L, 1.0e302L, 1.0e303L, 1.0e304L, 1.0e305L, 1.0e306L, 1.0e307L, 1.0e308L, 1.0e309L, 1.0e310L, 1.0e311L, 1.0e312L, 1.0e313L, 1.0e314L, 1.0e315L, 1.0e316L, 1.0e317L, 1.0e318L, 1.0e319L, 1.0e320L, 1.0e321L, 1.0e322L, 1.0e323L, 1.0e324L, 1.0e325L, 1.0e326L, 1.0e327L, 1.0e328L, 1.0e329L, 1.0e330L, 1.0e331L, 1.0e332L, 1.0e333L, 1.0e334L, 1.0e335L, 1.0e336L, 1.0e337L, 1.0e338L, 1.0e339L, 1.0e340L, 1.0e341L, 1.0e342L, 1.0e343L, 1.0e344L, 1.0e345L, 1.0e346L, 1.0e347L, 1.0e348L, 1.0e349L, 1.0e350L, 1.0e351L, 1.0e352L, 1.0e353L, 1.0e354L, 1.0e355L, 1.0e356L, 1.0e357L, 1.0e358L, 1.0e359L, 1.0e360L, 1.0e361L, 1.0e362L, 1.0e363L, 1.0e364L, 1.0e365L, 1.0e366L, 1.0e367L, 1.0e368L, 1.0e369L, 1.0e370L, 1.0e371L, 1.0e372L, 1.0e373L, 1.0e374L, 1.0e375L, 1.0e376L, 1.0e377L, 1.0e378L, 1.0e379L, 1.0e380L, 1.0e381L, 1.0e382L, 1.0e383L, 1.0e384L, 1.0e385L, 1.0e386L, 1.0e387L, 1.0e388L, 1.0e389L, 1.0e390L, 1.0e391L, 1.0e392L, 1.0e393L, 1.0e394L, 1.0e395L, 1.0e396L, 1.0e397L, 1.0e398L, 1.0e399L, 1.0e400L}; static VALUE parser_class; // Works with extended unicode as well. \Uffffffff if support is desired in // the future. static size_t unicodeToUtf8(uint32_t code, byte *buf) { byte *start = buf; if (0x0000007F >= code) { *buf++ = (byte)code; } else if (0x000007FF >= code) { *buf++ = 0xC0 | (code >> 6); *buf++ = 0x80 | (0x3F & code); } else if (0x0000FFFF >= code) { *buf++ = 0xE0 | (code >> 12); *buf++ = 0x80 | ((code >> 6) & 0x3F); *buf++ = 0x80 | (0x3F & code); } else if (0x001FFFFF >= code) { *buf++ = 0xF0 | (code >> 18); *buf++ = 0x80 | ((code >> 12) & 0x3F); *buf++ = 0x80 | ((code >> 6) & 0x3F); *buf++ = 0x80 | (0x3F & code); } else if (0x03FFFFFF >= code) { *buf++ = 0xF8 | (code >> 24); *buf++ = 0x80 | ((code >> 18) & 0x3F); *buf++ = 0x80 | ((code >> 12) & 0x3F); *buf++ = 0x80 | ((code >> 6) & 0x3F); *buf++ = 0x80 | (0x3F & code); } else if (0x7FFFFFFF >= code) { *buf++ = 0xFC | (code >> 30); *buf++ = 0x80 | ((code >> 24) & 0x3F); *buf++ = 0x80 | ((code >> 18) & 0x3F); *buf++ = 0x80 | ((code >> 12) & 0x3F); *buf++ = 0x80 | ((code >> 6) & 0x3F); *buf++ = 0x80 | (0x3F & code); } return buf - start; } static void parser_reset(ojParser p) { p->reader = 0; memset(&p->num, 0, sizeof(p->num)); buf_reset(&p->key); buf_reset(&p->buf); p->map = value_map; p->next_map = NULL; p->depth = 0; } static void parse_error(ojParser p, const char *fmt, ...) { va_list ap; char buf[256]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); rb_raise(oj_json_parser_error_class, "%s at %ld:%ld", buf, p->line, p->col); } static void byte_error(ojParser p, byte b) { switch (p->map[256]) { case 'N': // null_map parse_error(p, "expected null"); break; case 'T': // true_map parse_error(p, "expected true"); break; case 'F': // false_map parse_error(p, "expected false"); break; case 's': // string_map parse_error(p, "invalid JSON character 0x%02x", b); break; default: parse_error(p, "unexpected character '%c' in '%c' mode", b, p->map[256]); break; } } static void calc_num(ojParser p) { switch (p->type) { case OJ_INT: if (p->num.neg) { p->num.fixnum = -p->num.fixnum; p->num.neg = false; } p->funcs[p->stack[p->depth]].add_int(p); break; case OJ_DECIMAL: { long double d = (long double)p->num.fixnum; if (p->num.neg) { d = -d; } if (0 < p->num.shift) { d /= pow_map[p->num.shift]; } if (0 < p->num.exp) { long double x; if (MAX_POW < p->num.exp) { x = powl(10.0L, (long double)p->num.exp); } else { x = pow_map[p->num.exp]; } if (p->num.exp_neg) { d /= x; } else { d *= x; } } p->num.dub = d; p->funcs[p->stack[p->depth]].add_float(p); break; } case OJ_BIG: p->funcs[p->stack[p->depth]].add_big(p); default: // nothing to do break; } } static void big_change(ojParser p) { char buf[32]; int64_t i = p->num.fixnum; int len = 0; buf[sizeof(buf) - 1] = '\0'; p->buf.tail = p->buf.head; switch (p->type) { case OJ_INT: // If an int then it will fit in the num.raw so no need to check length; for (len = sizeof(buf) - 1; 0 < i; len--, i /= 10) { buf[len] = '0' + (i % 10); } if (p->num.neg) { buf[len] = '-'; len--; } buf_append_string(&p->buf, buf + len + 1, sizeof(buf) - len - 1); p->type = OJ_BIG; break; case OJ_DECIMAL: { int shift = p->num.shift; for (len = sizeof(buf) - 1; 0 < i; len--, i /= 10, shift--) { if (0 == shift) { buf[len] = '.'; len--; } buf[len] = '0' + (i % 10); } if (p->num.neg) { buf[len] = '-'; len--; } buf_append_string(&p->buf, buf + len + 1, sizeof(buf) - len - 1); if (0 < p->num.exp) { int x = p->num.exp; int d, div; bool started = false; buf_append(&p->buf, 'e'); if (0 < p->num.exp_neg) { buf_append(&p->buf, '-'); } for (div = 1000; 0 < div; div /= 10) { d = x / div % 10; if (started || 0 < d) { buf_append(&p->buf, '0' + d); } } } p->type = OJ_BIG; break; } default: break; } } static void parse(ojParser p, const byte *json) { const byte *start; const byte *b = json; int i; #if DEBUG printf("*** parse - mode: %c %s\n", p->map[256], (const char *)json); #endif for (; '\0' != *b; b++) { #if DEBUG printf("*** parse - mode: %c %02x %s => %c\n", p->map[256], *b, b, p->map[*b]); #endif switch (p->map[*b]) { case SKIP_NEWLINE: p->line++; p->col = b - json; b++; #ifdef SPACE_JUMP // for (uint32_t *sj = (uint32_t*)b; 0x20202020 == *sj; sj++) { b += 4; } for (uint16_t *sj = (uint16_t *)b; 0x2020 == *sj; sj++) { b += 2; } #endif for (; SKIP_CHAR == space_map[*b]; b++) { } b--; break; case COLON_COLON: p->map = value_map; break; case SKIP_CHAR: break; case KEY_QUOTE: b++; p->key.tail = p->key.head; start = b; for (; STR_OK == string_map[*b]; b++) { } buf_append_string(&p->key, (const char *)start, b - start); if ('"' == *b) { p->map = colon_map; break; } b--; p->map = string_map; p->next_map = colon_map; break; case AFTER_COMMA: if (0 < p->depth && OBJECT_FUN == p->stack[p->depth]) { p->map = key_map; } else { p->map = comma_map; } break; case VAL_QUOTE: b++; start = b; p->buf.tail = p->buf.head; for (; STR_OK == string_map[*b]; b++) { } buf_append_string(&p->buf, (const char *)start, b - start); if ('"' == *b) { p->funcs[p->stack[p->depth]].add_str(p); p->map = (0 == p->depth) ? value_map : after_map; break; } b--; p->map = string_map; p->next_map = (0 == p->depth) ? value_map : after_map; break; case OPEN_OBJECT: p->funcs[p->stack[p->depth]].open_object(p); p->depth++; p->stack[p->depth] = OBJECT_FUN; p->map = key1_map; break; case NUM_CLOSE_OBJECT: calc_num(p); // flow through case CLOSE_OBJECT: p->map = (1 == p->depth) ? value_map : after_map; if (p->depth <= 0 || OBJECT_FUN != p->stack[p->depth]) { p->col = b - json - p->col + 1; parse_error(p, "unexpected object close"); return; } p->depth--; p->funcs[p->stack[p->depth]].close_object(p); break; case OPEN_ARRAY: p->funcs[p->stack[p->depth]].open_array(p); p->depth++; p->stack[p->depth] = ARRAY_FUN; p->map = value_map; break; case NUM_CLOSE_ARRAY: calc_num(p); // flow through case CLOSE_ARRAY: p->map = (1 == p->depth) ? value_map : after_map; if (p->depth <= 0 || ARRAY_FUN != p->stack[p->depth]) { p->col = b - json - p->col + 1; parse_error(p, "unexpected array close"); return; } p->depth--; p->funcs[p->stack[p->depth]].close_array(p); break; case NUM_COMMA: calc_num(p); if (0 < p->depth && OBJECT_FUN == p->stack[p->depth]) { p->map = key_map; } else { p->map = comma_map; } break; case VAL0: p->type = OJ_INT; p->num.fixnum = 0; p->num.neg = false; p->num.shift = 0; p->num.len = 0; p->num.exp = 0; p->num.exp_neg = false; p->map = zero_map; break; case VAL_NEG: p->type = OJ_INT; p->num.fixnum = 0; p->num.neg = true; p->num.shift = 0; p->num.len = 0; p->num.exp = 0; p->num.exp_neg = false; p->map = neg_map; break; ; case VAL_DIGIT: p->type = OJ_INT; p->num.fixnum = 0; p->num.neg = false; p->num.shift = 0; p->num.exp = 0; p->num.exp_neg = false; p->num.len = 0; p->map = digit_map; for (; NUM_DIGIT == digit_map[*b]; b++) { uint64_t x = (uint64_t)p->num.fixnum * 10 + (uint64_t)(*b - '0'); // Tried just checking for an int less than zero but that // fails when optimization is on for some reason with the // clang compiler so us a bit mask instead. if (x < BIG_LIMIT) { p->num.fixnum = (int64_t)x; } else { big_change(p); p->map = big_digit_map; break; } } b--; break; case NUM_DIGIT: for (; NUM_DIGIT == digit_map[*b]; b++) { uint64_t x = p->num.fixnum * 10 + (uint64_t)(*b - '0'); if (x < BIG_LIMIT) { p->num.fixnum = (int64_t)x; } else { big_change(p); p->map = big_digit_map; break; } } b--; break; case NUM_DOT: p->type = OJ_DECIMAL; p->map = dot_map; break; case NUM_FRAC: p->map = frac_map; for (; NUM_FRAC == frac_map[*b]; b++) { uint64_t x = p->num.fixnum * 10 + (uint64_t)(*b - '0'); if (x < FRAC_LIMIT) { p->num.fixnum = (int64_t)x; p->num.shift++; } else { big_change(p); p->map = big_frac_map; break; } } b--; break; case FRAC_E: p->type = OJ_DECIMAL; p->map = exp_sign_map; break; case NUM_ZERO: p->map = zero_map; break; case NEG_DIGIT: for (; NUM_DIGIT == digit_map[*b]; b++) { uint64_t x = p->num.fixnum * 10 + (uint64_t)(*b - '0'); if (x < BIG_LIMIT) { p->num.fixnum = (int64_t)x; } else { big_change(p); p->map = big_digit_map; break; } } b--; p->map = digit_map; break; case EXP_SIGN: p->num.exp_neg = ('-' == *b); p->map = exp_zero_map; break; case EXP_DIGIT: p->map = exp_map; for (; NUM_DIGIT == digit_map[*b]; b++) { int16_t x = p->num.exp * 10 + (int16_t)(*b - '0'); if (x <= MAX_EXP) { p->num.exp = x; } else { big_change(p); p->map = big_exp_map; break; } } b--; break; case BIG_DIGIT: start = b; for (; NUM_DIGIT == digit_map[*b]; b++) { } buf_append_string(&p->buf, (const char *)start, b - start); b--; break; case BIG_DOT: buf_append(&p->buf, '.'); p->map = big_dot_map; break; case BIG_FRAC: p->map = big_frac_map; start = b; for (; NUM_FRAC == frac_map[*b]; b++) { } buf_append_string(&p->buf, (const char *)start, b - start); b--; break; case BIG_E: buf_append(&p->buf, *b); p->map = big_exp_sign_map; break; case BIG_EXP_SIGN: buf_append(&p->buf, *b); p->map = big_exp_zero_map; break; case BIG_EXP: start = b; for (; NUM_DIGIT == digit_map[*b]; b++) { } buf_append_string(&p->buf, (const char *)start, b - start); b--; p->map = big_exp_map; break; case NUM_SPC: calc_num(p); break; case NUM_NEWLINE: calc_num(p); b++; #ifdef SPACE_JUMP // for (uint32_t *sj = (uint32_t*)b; 0x20202020 == *sj; sj++) { b += 4; } for (uint16_t *sj = (uint16_t *)b; 0x2020 == *sj; sj++) { b += 2; } #endif for (; SKIP_CHAR == space_map[*b]; b++) { } b--; break; case STR_OK: start = b; for (; STR_OK == string_map[*b]; b++) { } if (':' == p->next_map[256]) { buf_append_string(&p->key, (const char *)start, b - start); } else { buf_append_string(&p->buf, (const char *)start, b - start); } if ('"' == *b) { p->funcs[p->stack[p->depth]].add_str(p); p->map = p->next_map; break; } b--; break; case STR_SLASH: p->map = esc_map; break; case STR_QUOTE: p->funcs[p->stack[p->depth]].add_str(p); p->map = p->next_map; break; case ESC_U: p->map = u_map; p->ri = 0; p->ucode = 0; break; case U_OK: p->ri++; p->ucode = p->ucode << 4 | (uint32_t)hex_map[*b]; if (4 <= p->ri) { byte utf8[8]; size_t ulen = unicodeToUtf8(p->ucode, utf8); if (0 < ulen) { if (':' == p->next_map[256]) { buf_append_string(&p->key, (const char *)utf8, ulen); } else { buf_append_string(&p->buf, (const char *)utf8, ulen); } } else { parse_error(p, "invalid unicode"); return; } p->map = string_map; } break; case ESC_OK: if (':' == p->next_map[256]) { buf_append(&p->key, esc_byte_map[*b]); } else { buf_append(&p->buf, esc_byte_map[*b]); } p->map = string_map; break; case UTF1: p->ri = 1; p->map = utf_map; if (':' == p->next_map[256]) { buf_append(&p->key, *b); } else { buf_append(&p->buf, *b); } break; case UTF2: p->ri = 2; p->map = utf_map; if (':' == p->next_map[256]) { buf_append(&p->key, *b); } else { buf_append(&p->buf, *b); } break; case UTF3: p->ri = 3; p->map = utf_map; if (':' == p->next_map[256]) { buf_append(&p->key, *b); } else { buf_append(&p->buf, *b); } break; case UTFX: p->ri--; if (':' == p->next_map[256]) { buf_append(&p->key, *b); } else { buf_append(&p->buf, *b); } if (p->ri <= 0) { p->map = string_map; } break; case VAL_NULL: if ('u' == b[1] && 'l' == b[2] && 'l' == b[3]) { b += 3; p->funcs[p->stack[p->depth]].add_null(p); p->map = (0 == p->depth) ? value_map : after_map; break; } p->ri = 0; *p->token = *b++; for (i = 1; i < 4; i++) { if ('\0' == *b) { p->ri = i; break; } else { p->token[i] = *b++; } } if (0 < p->ri) { p->map = null_map; b--; break; } p->col = b - json - p->col; parse_error(p, "expected null"); return; case VAL_TRUE: if ('r' == b[1] && 'u' == b[2] && 'e' == b[3]) { b += 3; p->funcs[p->stack[p->depth]].add_true(p); p->map = (0 == p->depth) ? value_map : after_map; break; } p->ri = 0; *p->token = *b++; for (i = 1; i < 4; i++) { if ('\0' == *b) { p->ri = i; break; } else { p->token[i] = *b++; } } if (0 < p->ri) { p->map = true_map; b--; break; } p->col = b - json - p->col; parse_error(p, "expected true"); return; case VAL_FALSE: if ('a' == b[1] && 'l' == b[2] && 's' == b[3] && 'e' == b[4]) { b += 4; p->funcs[p->stack[p->depth]].add_false(p); p->map = (0 == p->depth) ? value_map : after_map; break; } p->ri = 0; *p->token = *b++; for (i = 1; i < 5; i++) { if ('\0' == *b) { p->ri = i; break; } else { p->token[i] = *b++; } } if (0 < p->ri) { p->map = false_map; b--; break; } p->col = b - json - p->col; parse_error(p, "expected false"); return; case TOKEN_OK: p->token[p->ri] = *b; p->ri++; switch (p->map[256]) { case 'N': if (4 == p->ri) { if (0 != strncmp("null", p->token, 4)) { p->col = b - json - p->col; parse_error(p, "expected null"); return; } p->funcs[p->stack[p->depth]].add_null(p); p->map = (0 == p->depth) ? value_map : after_map; } break; case 'F': if (5 == p->ri) { if (0 != strncmp("false", p->token, 5)) { p->col = b - json - p->col; parse_error(p, "expected false"); return; } p->funcs[p->stack[p->depth]].add_false(p); p->map = (0 == p->depth) ? value_map : after_map; } break; case 'T': if (4 == p->ri) { if (0 != strncmp("true", p->token, 4)) { p->col = b - json - p->col; parse_error(p, "expected true"); return; } p->funcs[p->stack[p->depth]].add_true(p); p->map = (0 == p->depth) ? value_map : after_map; } break; default: p->col = b - json - p->col; parse_error(p, "parse error"); return; } break; case CHAR_ERR: byte_error(p, *b); return; default: break; } if (0 == p->depth && 'v' == p->map[256] && p->just_one) { p->map = trail_map; } } if (0 == p->depth) { switch (p->map[256]) { case '0': case 'd': case 'f': case 'z': case 'X': case 'D': case 'g': case 'B': case 'Y': calc_num(p); break; } } return; } static void parser_free(void *ptr) { ojParser p; if (0 == ptr) { return; } p = (ojParser)ptr; buf_cleanup(&p->key); buf_cleanup(&p->buf); p->free(p); xfree(ptr); } static void parser_mark(void *ptr) { if (NULL != ptr) { ojParser p = (ojParser)ptr; if (0 != p->reader) { rb_gc_mark(p->reader); } p->mark(p); } } extern void oj_set_parser_validator(ojParser p); extern void oj_set_parser_saj(ojParser p); extern void oj_set_parser_usual(ojParser p); extern void oj_set_parser_debug(ojParser p); static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) { ojParser p = (ojParser)ptr; const char *key = NULL; char set_key[64]; long klen; switch (rb_type(rkey)) { case RUBY_T_SYMBOL: rkey = rb_sym2str(rkey); // fall through case RUBY_T_STRING: key = rb_string_value_ptr(&rkey); klen = RSTRING_LEN(rkey); break; default: rb_raise(rb_eArgError, "option keys must be a symbol or string"); } if ((long)sizeof(set_key) - 1 <= klen) { return ST_CONTINUE; } memcpy(set_key, key, klen); set_key[klen] = '='; set_key[klen + 1] = '\0'; p->option(p, set_key, value); return ST_CONTINUE; } /* Document-method: new * call-seq: new(mode=nil) * * Creates a new Parser with the specified mode. If no mode is provided * validation is assumed. Optional arguments can be provided that match the * mode. For example with the :usual mode the call might look like * Oj::Parser.new(:usual, cache_keys: true). */ static VALUE parser_new(int argc, VALUE *argv, VALUE self) { ojParser p = ALLOC(struct _ojParser); #if HAVE_RB_EXT_RACTOR_SAFE // This doesn't seem to do anything. rb_ext_ractor_safe(true); #endif memset(p, 0, sizeof(struct _ojParser)); buf_init(&p->key); buf_init(&p->buf); p->map = value_map; if (argc < 1) { oj_set_parser_validator(p); } else { VALUE mode = argv[0]; if (Qnil == mode) { oj_set_parser_validator(p); } else { const char *ms = NULL; switch (rb_type(mode)) { case RUBY_T_SYMBOL: mode = rb_sym2str(mode); // fall through case RUBY_T_STRING: ms = RSTRING_PTR(mode); break; default: rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object"); } if (0 == strcmp("usual", ms) || 0 == strcmp("standard", ms) || 0 == strcmp("strict", ms) || 0 == strcmp("compat", ms)) { oj_set_parser_usual(p); } else if (0 == strcmp("object", ms)) { // TBD } else if (0 == strcmp("saj", ms)) { oj_set_parser_saj(p); } else if (0 == strcmp("validate", ms)) { oj_set_parser_validator(p); } else if (0 == strcmp("debug", ms)) { oj_set_parser_debug(p); } else { rb_raise(rb_eArgError, "mode must be :validate, :usual, :saj, or :object"); } } if (1 < argc) { VALUE ropts = argv[1]; Check_Type(ropts, T_HASH); rb_hash_foreach(ropts, opt_cb, (VALUE)p); } } return Data_Wrap_Struct(parser_class, parser_mark, parser_free, p); } /* Document-method: method_missing(value) * call-seq: method_missing(value) * * Methods not handled by the parser are passed to the delegate. The methods * supported by delegate are: * * - *:validate* * - no options * * - *:saj* * - _cache_keys=_ sets the value of the _cache_keys_ flag. * - _cache_keys_ returns the value of the _cache_keys_ flag. * - _cache_strings=_ sets the value of the _cache_strings_ to an positive integer less than 35. Strings shorter than * that length are cached. * - _cache_strings_ returns the value of the _cache_strings_ integer value. * - _handler=_ sets the SAJ handler * - _handler_ returns the SAJ handler * * - *:usual* * - _cache_keys=_ sets the value of the _cache_keys_ flag. * - _cache_keys_ returns the value of the _cache_keys_ flag. * - _cache_strings=_ sets the value of the _cache_strings_ to a positive integer less than 35. Strings shorter than * that length are cached. * - _cache_strings_ returns the value of the _cache_strings_ integer value. * - _cache_expunge=_ sets the value of the _cache_expunge_ where 0 never expunges, 1 expunges slowly, 2 expunges * faster, and 3 or higher expunges agressively. * - _cache_expunge_ returns the value of the _cache_expunge_ integer value. * - _capacity=_ sets the capacity of the parser. The parser grows automatically but can be updated directly with this * call. * - _capacity_ returns the current capacity of the parser's internal stack. * - _create_id_ returns the value _create_id_ or _nil_ if there is no _create_id_. * - _create_id=_ sets the value _create_id_ or if _nil_ unsets it. Parsed JSON objects that include the specified * element use the element value as the name of the class to create an object from instead of a Hash. * - _decimal=_ sets the approach to how decimals are parser. If _:auto_ then the decimals with significant digits are * 16 or less are Floats and long ones are BigDecimal. _:ruby_ uses a call to Ruby to convert a string to a Float. * _:float_ always generates a Float. _:bigdecimal_ always results in a BigDecimal. * - _decimal_ returns the value of the decimal conversion option which can be :auto (default), :ruby, :float, or * :bigdecimal. * - _ignore_json_create_ returns the value of the _ignore_json_create_ flag. * - _ignore_json_create=_ sets the value of the _ignore_json_create_ flag. When set the class json_create method is * ignored on parsing in favor of creating an instance and populating directly. * - _missing_class_ return the value of the _missing_class_ indicator. * - _missing_class=_ sets the value of the _missing_class_ flag. Valid values are _:auto_ which creates any missing * classes on parse, :ignore which ignores and continues as a Hash (default), and :raise which raises an exception if * the class is not found. * - _omit_null=_ sets the _omit_null_ flag. If true then null values in a map or object are omitted from the * resulting Hash or Object. * - _omit_null_ returns the value of the _omit_null_ flag. * - _symbol_keys=_ sets the flag that indicates Hash keys should be parsed to Symbols versus Strings. * - _symbol_keys_ returns the value of the _symbol_keys_ flag. */ static VALUE parser_missing(int argc, VALUE *argv, VALUE self) { ojParser p = (ojParser)DATA_PTR(self); const char * key = NULL; volatile VALUE rkey = *argv; volatile VALUE rv = Qnil; #if HAVE_RB_EXT_RACTOR_SAFE // This doesn't seem to do anything. rb_ext_ractor_safe(true); #endif switch (rb_type(rkey)) { case RUBY_T_SYMBOL: rkey = rb_sym2str(rkey); // fall through case RUBY_T_STRING: key = rb_string_value_ptr(&rkey); break; default: rb_raise(rb_eArgError, "option method must be a symbol or string"); } if (1 < argc) { rv = argv[1]; } return p->option(p, key, rv); } /* Document-method: parse(json) * call-seq: parse(json) * * Parse a JSON string. * * Returns the result according to the delegate of the parser. */ static VALUE parser_parse(VALUE self, VALUE json) { ojParser p = (ojParser)DATA_PTR(self); Check_Type(json, T_STRING); parser_reset(p); p->start(p); parse(p, (const byte *)rb_string_value_ptr(&json)); return p->result(p); } static VALUE load_rescue(VALUE self, VALUE x) { // Normal EOF. No action needed other than to stop loading. return Qfalse; } static VALUE load(VALUE self) { ojParser p = (ojParser)DATA_PTR(self); volatile VALUE rbuf = rb_str_new2(""); p->start(p); while (true) { rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf); if (0 < RSTRING_LEN(rbuf)) { parse(p, (byte *)StringValuePtr(rbuf)); } } return Qtrue; } /* Document-method: load(reader) * call-seq: load(reader) * * Parse a JSON stream. * * Returns the result according to the delegate of the parser. */ static VALUE parser_load(VALUE self, VALUE reader) { ojParser p = (ojParser)DATA_PTR(self); parser_reset(p); p->reader = reader; rb_rescue2(load, self, load_rescue, Qnil, rb_eEOFError, 0); return p->result(p); } /* Document-method: file(filename) * call-seq: file(filename) * * Parse a JSON file. * * Returns the result according to the delegate of the parser. */ static VALUE parser_file(VALUE self, VALUE filename) { ojParser p = (ojParser)DATA_PTR(self); const char *path; int fd; Check_Type(filename, T_STRING); path = rb_string_value_ptr(&filename); parser_reset(p); p->start(p); if (0 > (fd = open(path, O_RDONLY))) { rb_raise(rb_eIOError, "error opening %s", path); } #if USE_THREAD_LIMIT struct stat info; // st_size will be 0 if not a file if (0 == fstat(fd, &info) && USE_THREAD_LIMIT < info.st_size) { // Use threaded version. // TBD only if has pthreads // TBD parse_large(p, fd); return p->result(p); } #endif byte buf[16385]; size_t size = sizeof(buf) - 1; size_t rsize; while (true) { if (0 < (rsize = read(fd, buf, size))) { buf[rsize] = '\0'; parse(p, buf); } if (rsize <= 0) { if (0 != rsize) { rb_raise(rb_eIOError, "error reading from %s", path); } break; } } return p->result(p); } /* Document-method: just_one * call-seq: just_one * * Returns the current state of the just_one [_Boolean_] option. */ static VALUE parser_just_one(VALUE self) { ojParser p = (ojParser)DATA_PTR(self); return p->just_one ? Qtrue : Qfalse; } /* Document-method: just_one= * call-seq: just_one=(value) * * Sets the *just_one* option which limits the parsing of a string or or * stream to a single JSON element. * * Returns the current state of the just_one [_Boolean_] option. */ static VALUE parser_just_one_set(VALUE self, VALUE v) { ojParser p = (ojParser)DATA_PTR(self); p->just_one = (Qtrue == v); return p->just_one ? Qtrue : Qfalse; } static VALUE usual_parser = Qundef; /* Document-method: usual * call-seq: usual * * Returns the default usual parser. Note the default usual parser can not be * used concurrently in more than one thread. */ static VALUE parser_usual(VALUE self) { if (Qundef == usual_parser) { ojParser p = ALLOC(struct _ojParser); memset(p, 0, sizeof(struct _ojParser)); buf_init(&p->key); buf_init(&p->buf); p->map = value_map; oj_set_parser_usual(p); usual_parser = Data_Wrap_Struct(parser_class, parser_mark, parser_free, p); rb_gc_register_address(&usual_parser); } return usual_parser; } static VALUE saj_parser = Qundef; /* Document-method: saj * call-seq: saj * * Returns the default saj parser. Note the default SAJ parser can not be used * concurrently in more than one thread. */ static VALUE parser_saj(VALUE self) { if (Qundef == saj_parser) { ojParser p = ALLOC(struct _ojParser); memset(p, 0, sizeof(struct _ojParser)); buf_init(&p->key); buf_init(&p->buf); p->map = value_map; oj_set_parser_saj(p); saj_parser = Data_Wrap_Struct(parser_class, parser_mark, parser_free, p); rb_gc_register_address(&saj_parser); } return saj_parser; } static VALUE validate_parser = Qundef; /* Document-method: validate * call-seq: validate * * Returns the default validate parser. */ static VALUE parser_validate(VALUE self) { if (Qundef == validate_parser) { ojParser p = ALLOC(struct _ojParser); memset(p, 0, sizeof(struct _ojParser)); buf_init(&p->key); buf_init(&p->buf); p->map = value_map; oj_set_parser_validator(p); validate_parser = Data_Wrap_Struct(parser_class, parser_mark, parser_free, p); rb_gc_register_address(&validate_parser); } return validate_parser; } /* Document-class: Oj::Parser * * A reusable parser that makes use of named delegates to determine the * handling of parsed data. Delegates are available for validation, a callback * parser (SAJ), and a usual delegate that builds Ruby objects as parsing * proceeds. * * This parser is considerably faster than the older Oj.parse call and * isolates options to just the parser so that other parts of the code are not * forced to use the same options. */ void oj_parser_init() { parser_class = rb_define_class_under(Oj, "Parser", rb_cObject); rb_define_module_function(parser_class, "new", parser_new, -1); rb_define_method(parser_class, "parse", parser_parse, 1); rb_define_method(parser_class, "load", parser_load, 1); rb_define_method(parser_class, "file", parser_file, 1); rb_define_method(parser_class, "just_one", parser_just_one, 0); rb_define_method(parser_class, "just_one=", parser_just_one_set, 1); rb_define_method(parser_class, "method_missing", parser_missing, -1); rb_define_module_function(parser_class, "usual", parser_usual, 0); rb_define_module_function(parser_class, "saj", parser_saj, 0); rb_define_module_function(parser_class, "validate", parser_validate, 0); } oj-3.13.9/ext/oj/dump.h0000644000004100000410000000602614136373754014616 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_DUMP_H #define OJ_DUMP_H #include #include "oj.h" #define MAX_DEPTH 1000 // Extra padding at end of buffer. #define BUFFER_EXTRA 64 extern void oj_dump_nil(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_true(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_false(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_bignum(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_str(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_sym(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_class(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_raw(const char *str, size_t cnt, Out out); extern void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out); extern void oj_dump_ruby_time(VALUE obj, Out out); extern void oj_dump_xml_time(VALUE obj, Out out); extern void oj_dump_time(VALUE obj, Out out, int withZone); extern void oj_dump_obj_to_s(VALUE obj, Out out); extern const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, int *lenp); extern void oj_grow_out(Out out, size_t len); extern long oj_check_circular(VALUE obj, Out out); extern void oj_dump_strict_val(VALUE obj, int depth, Out out); extern void oj_dump_null_val(VALUE obj, int depth, Out out); extern void oj_dump_obj_val(VALUE obj, int depth, Out out); extern void oj_dump_compat_val(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_rails_val(VALUE obj, int depth, Out out); extern void oj_dump_custom_val(VALUE obj, int depth, Out out, bool as_ok); extern void oj_dump_wab_val(VALUE obj, int depth, Out out); extern void oj_dump_raw_json(VALUE obj, int depth, Out out); extern VALUE oj_add_to_json(int argc, VALUE *argv, VALUE self); extern VALUE oj_remove_to_json(int argc, VALUE *argv, VALUE self); extern int oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format); extern bool oj_dump_ignore(Options opts, VALUE obj); extern time_t oj_sec_from_time_hard_way(VALUE obj); inline static void assure_size(Out out, size_t len) { if (out->end - out->cur <= (long)len) { oj_grow_out(out, len); } } inline static void fill_indent(Out out, int cnt) { if (0 < out->indent) { cnt *= out->indent; *out->cur++ = '\n'; for (; 0 < cnt; cnt--) { *out->cur++ = ' '; } } } inline static void dump_ulong(unsigned long num, Out out) { char buf[32]; char *b = buf + sizeof(buf) - 1; *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } b++; } else { *b = '0'; } for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } #endif /* OJ_DUMP_H */ oj-3.13.9/ext/oj/cache8.c0000644000004100000410000000500614136373754014774 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "cache8.h" #include #include #include #include #include #include "ruby.h" #define BITS 4 #define MASK 0x000000000000000FULL #define SLOT_CNT 16 #define DEPTH 16 typedef union { struct _cache8 *child; slot_t value; } Bucket; struct _cache8 { Bucket buckets[SLOT_CNT]; }; static void cache8_delete(Cache8 cache, int depth); static void slot_print(Cache8 cache, sid_t key, unsigned int depth); void oj_cache8_new(Cache8 *cache) { Bucket *b; int i; *cache = ALLOC(struct _cache8); for (i = SLOT_CNT, b = (*cache)->buckets; 0 < i; i--, b++) { b->value = 0; } } void oj_cache8_delete(Cache8 cache) { cache8_delete(cache, 0); } static void cache8_delete(Cache8 cache, int depth) { Bucket * b; unsigned int i; for (i = 0, b = cache->buckets; i < SLOT_CNT; i++, b++) { if (0 != b->child) { if (DEPTH - 1 != depth) { cache8_delete(b->child, depth + 1); } } } xfree(cache); } slot_t oj_cache8_get(Cache8 cache, sid_t key, slot_t **slot) { Bucket *b; int i; sid_t k8 = (sid_t)key; sid_t k; for (i = 64 - BITS; 0 < i; i -= BITS) { k = (k8 >> i) & MASK; b = cache->buckets + k; if (0 == b->child) { oj_cache8_new(&b->child); } cache = b->child; } *slot = &(cache->buckets + (k8 & MASK))->value; return **slot; } void oj_cache8_print(Cache8 cache) { /*printf("-------------------------------------------\n"); */ slot_print(cache, 0, 0); } static void slot_print(Cache8 c, sid_t key, unsigned int depth) { Bucket * b; unsigned int i; sid_t k8 = (sid_t)key; sid_t k; for (i = 0, b = c->buckets; i < SLOT_CNT; i++, b++) { if (0 != b->child) { k = (k8 << BITS) | i; /*printf("*** key: 0x%016llx depth: %u i: %u\n", k, depth, i); */ if (DEPTH - 1 == depth) { #if IS_WINDOWS printf("0x%016lx: %4lu\n", (long unsigned int)k, (long unsigned int)b->value); #else printf("0x%016llx: %4llu\n", (long long unsigned int)k, (long long unsigned int)b->value); #endif } else { slot_print(b->child, k, depth + 1); } } } } oj-3.13.9/ext/oj/dump_compat.c0000644000004100000410000006444114136373754016161 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "code.h" #include "dump.h" #include "rails.h" #include "trace.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) bool oj_use_hash_alt = false; bool oj_use_array_alt = false; static bool use_struct_alt = false; static bool use_exception_alt = false; static bool use_bignum_alt = false; static void raise_json_err(const char *msg, const char *err_classname) { rb_raise(oj_get_json_err_class(err_classname), "%s", msg); } static void dump_obj_classname(const char *classname, int depth, Out out) { int d2 = depth + 1; size_t len = strlen(classname); size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; size_t size = d2 * out->indent + 10 + len + out->opts->create_id_len + sep_len; assure_size(out, size); *out->cur++ = '{'; fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, len); out->cur += len; *out->cur++ = '"'; } static void dump_values_array(VALUE *values, int depth, Out out) { size_t size; int d2 = depth + 1; assure_size(out, d2 * out->indent + 3); *out->cur++ = '['; if (Qundef == *values) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 2; size += out->opts->dump_opts.array_size; size += out->opts->dump_opts.indent_size; } else { size = d2 * out->indent + 3; } for (; Qundef != *values; values++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_compat_val(*values, d2, out, true); if (Qundef != *(values + 1)) { *out->cur++ = ','; } } assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } } static void dump_to_json(VALUE obj, Out out) { volatile VALUE rs; const char *s; int len; if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, 0, TraceRubyIn); } if (0 == rb_obj_method_arity(obj, oj_to_json_id)) { rs = rb_funcall(obj, oj_to_json_id, 0); } else { rs = rb_funcall2(obj, oj_to_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, 0, TraceRubyOut); } s = RSTRING_PTR(rs); len = (int)RSTRING_LEN(rs); assure_size(out, len + 1); memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; long id = oj_check_circular(a, out); if (0 > id) { raise_json_err("Too deeply nested", "NestingError"); return; } if (as_ok && !oj_use_hash_alt && rb_obj_class(a) != rb_cArray && rb_respond_to(a, oj_to_json_id)) { dump_to_json(a, out); return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; assure_size(out, 2); if (0 == cnt) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_compat_val(rb_ary_entry(a, i), d2, out, true); if (i < cnt) { *out->cur++ = ','; } } if (out->opts->dump_opts.use) { size = out->opts->dump_opts.array_size + out->opts->dump_opts.indent_size * depth + 1; assure_size(out, size); if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { size = depth * out->indent + 1; assure_size(out, size); fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static ID _dump_id = 0; static void bigdecimal_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "b", 1, Qnil }, { NULL, 0, Qnil }, }; if (0 == _dump_id) { _dump_id = rb_intern("_dump"); } attrs[0].value = rb_funcall(obj, _dump_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static ID real_id = 0; static ID imag_id = 0; static void complex_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "r", 1, Qnil }, { "i", 1, Qnil }, { NULL, 0, Qnil }, }; if (0 == real_id) { real_id = rb_intern("real"); imag_id = rb_intern("imag"); } attrs[0].value = rb_funcall(obj, real_id, 0); attrs[1].value = rb_funcall(obj, imag_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static ID year_id = 0; static ID month_id = 0; static ID day_id = 0; static ID start_id = 0; static void date_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "y", 1, Qnil }, { "m", 1, Qnil }, { "d", 1, Qnil }, { "sg", 2, Qnil }, { NULL, 0, Qnil }, }; if (0 == year_id) { year_id = rb_intern("year"); month_id = rb_intern("month"); day_id = rb_intern("day"); start_id = rb_intern("start"); } attrs[0].value = rb_funcall(obj, year_id, 0); attrs[1].value = rb_funcall(obj, month_id, 0); attrs[2].value = rb_funcall(obj, day_id, 0); attrs[3].value = rb_funcall(obj, start_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static ID hour_id = 0; static ID min_id = 0; static ID sec_id = 0; static ID offset_id = 0; static void datetime_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "y", 1, Qnil }, { "m", 1, Qnil }, { "d", 1, Qnil }, { "H", 1, Qnil }, { "M", 1, Qnil }, { "S", 1, Qnil }, { "of", 2, Qnil }, { "sg", 2, Qnil }, { NULL, 0, Qnil }, }; if (0 == hour_id) { year_id = rb_intern("year"); month_id = rb_intern("month"); day_id = rb_intern("day"); hour_id = rb_intern("hour"); min_id = rb_intern("min"); sec_id = rb_intern("sec"); offset_id = rb_intern("offset"); start_id = rb_intern("start"); } attrs[0].value = rb_funcall(obj, year_id, 0); attrs[1].value = rb_funcall(obj, month_id, 0); attrs[2].value = rb_funcall(obj, day_id, 0); attrs[3].value = rb_funcall(obj, hour_id, 0); attrs[4].value = rb_funcall(obj, min_id, 0); attrs[5].value = rb_funcall(obj, sec_id, 0); attrs[6].value = rb_funcall(rb_funcall(obj, offset_id, 0), oj_to_s_id, 0); attrs[7].value = rb_funcall(obj, start_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static ID message_id = 0; static ID backtrace_id = 0; static void exception_alt(VALUE obj, int depth, Out out) { int d3 = depth + 2; size_t size = d3 * out->indent + 2; size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; if (0 == message_id) { message_id = rb_intern("message"); backtrace_id = rb_intern("backtrace"); } dump_obj_classname(rb_class2name(rb_obj_class(obj)), depth, out); assure_size(out, size + sep_len + 6); *out->cur++ = ','; fill_indent(out, d3); *out->cur++ = '"'; *out->cur++ = 'm'; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } oj_dump_str(rb_funcall(obj, message_id, 0), 0, out, false); assure_size(out, size + sep_len + 6); *out->cur++ = ','; fill_indent(out, d3); *out->cur++ = '"'; *out->cur++ = 'b'; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } dump_array(rb_funcall(obj, backtrace_id, 0), depth, out, false); fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static ID table_id = 0; static void openstruct_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "t", 1, Qnil }, { NULL, 0, Qnil }, }; if (0 == table_id) { table_id = rb_intern("table"); } attrs[0].value = rb_funcall(obj, table_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static void range_alt(VALUE obj, int depth, Out out) { int d3 = depth + 2; size_t size = d3 * out->indent + 2; size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; VALUE args[] = { Qundef, Qundef, Qundef, Qundef }; dump_obj_classname(rb_class2name(rb_obj_class(obj)), depth, out); assure_size(out, size + sep_len + 6); *out->cur++ = ','; fill_indent(out, d3); *out->cur++ = '"'; *out->cur++ = 'a'; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } args[0] = rb_funcall(obj, oj_begin_id, 0); args[1] = rb_funcall(obj, oj_end_id, 0); args[2] = rb_funcall(obj, oj_exclude_end_id, 0); dump_values_array(args, depth, out); fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static ID numerator_id = 0; static ID denominator_id = 0; static void rational_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "n", 1, Qnil }, { "d", 1, Qnil }, { NULL, 0, Qnil }, }; if (0 == numerator_id) { numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); } attrs[0].value = rb_funcall(obj, numerator_id, 0); attrs[1].value = rb_funcall(obj, denominator_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static ID options_id = 0; static ID source_id = 0; static void regexp_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "o", 1, Qnil }, { "s", 1, Qnil }, { NULL, 0, Qnil }, }; if (0 == options_id) { options_id = rb_intern("options"); source_id = rb_intern("source"); } attrs[0].value = rb_funcall(obj, options_id, 0); attrs[1].value = rb_funcall(obj, source_id, 0); oj_code_attrs(obj, attrs, depth, out, true); } static void time_alt(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "s", 1, Qundef, 0, Qundef }, { "n", 1, Qundef, 0, Qundef }, { NULL, 0, Qnil }, }; time_t sec; long long nsec; #ifdef HAVE_RB_TIME_TIMESPEC if (16 <= sizeof(struct timespec)) { struct timespec ts = rb_time_timespec(obj); sec = (long long)ts.tv_sec; nsec = ts.tv_nsec; } else { sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); } #else sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #endif attrs[0].num = sec; attrs[1].num = nsec; oj_code_attrs(obj, attrs, depth, out, true); } struct _code oj_compat_codes[] = { { "BigDecimal", Qnil, bigdecimal_alt, NULL, false }, { "Complex", Qnil, complex_alt, NULL, false }, { "Date", Qnil, date_alt, false }, { "DateTime", Qnil, datetime_alt, NULL, false }, { "OpenStruct", Qnil, openstruct_alt, NULL, false }, { "Range", Qnil, range_alt, NULL, false }, { "Rational", Qnil, rational_alt, NULL, false }, { "Regexp", Qnil, regexp_alt, NULL, false }, { "Time", Qnil, time_alt, NULL, false }, // TBD the rest of the library classes { NULL, Qundef, NULL, NULL, false }, }; VALUE oj_add_to_json(int argc, VALUE *argv, VALUE self) { Code a; if (0 == argc) { for (a = oj_compat_codes; NULL != a->name; a++) { if (Qnil == a->clas || Qundef == a->clas) { a->clas = rb_const_get_at(rb_cObject, rb_intern(a->name)); } a->active = true; } use_struct_alt = true; use_exception_alt = true; use_bignum_alt = true; oj_use_hash_alt = true; oj_use_array_alt = true; } else { for (; 0 < argc; argc--, argv++) { if (rb_cStruct == *argv) { use_struct_alt = true; continue; } if (rb_eException == *argv) { use_exception_alt = true; continue; } if (rb_cInteger == *argv) { use_bignum_alt = true; continue; } if (rb_cHash == *argv) { oj_use_hash_alt = true; continue; } if (rb_cArray == *argv) { oj_use_array_alt = true; continue; } for (a = oj_compat_codes; NULL != a->name; a++) { if (Qnil == a->clas || Qundef == a->clas) { a->clas = rb_const_get_at(rb_cObject, rb_intern(a->name)); } if (*argv == a->clas) { a->active = true; break; } } } } return Qnil; } VALUE oj_remove_to_json(int argc, VALUE *argv, VALUE self) { if (0 == argc) { oj_code_set_active(oj_compat_codes, Qnil, false); use_struct_alt = false; use_exception_alt = false; use_bignum_alt = false; oj_use_hash_alt = false; oj_use_array_alt = false; } else { for (; 0 < argc; argc--, argv++) { if (rb_cStruct == *argv) { use_struct_alt = false; continue; } if (rb_eException == *argv) { use_exception_alt = false; continue; } if (rb_cInteger == *argv) { use_bignum_alt = false; continue; } if (rb_cHash == *argv) { oj_use_hash_alt = false; continue; } if (rb_cArray == *argv) { oj_use_array_alt = false; continue; } oj_code_set_active(oj_compat_codes, *argv, false); } } return Qnil; } // The JSON gem is inconsistent with handling of infinity. Using // JSON.dump(0.1/0) returns the string Infinity but (0.1/0).to_json raise and // exception. Worse, for BigDecimals a quoted "Infinity" is returned. static void dump_float(VALUE obj, int depth, Out out, bool as_ok) { char buf[64]; char *b; double d = rb_num2dbl(obj); int cnt = 0; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else if (OJ_INFINITY == d) { if (WordNan == out->opts->dump_opts.nan_dump) { strcpy(buf, "Infinity"); } else { raise_json_err("Infinity not allowed in JSON.", "GeneratorError"); } } else if (-OJ_INFINITY == d) { if (WordNan == out->opts->dump_opts.nan_dump) { strcpy(buf, "-Infinity"); } else { raise_json_err("-Infinity not allowed in JSON.", "GeneratorError"); } } else if (isnan(d)) { if (WordNan == out->opts->dump_opts.nan_dump) { strcpy(buf, "NaN"); } else { raise_json_err("NaN not allowed in JSON.", "GeneratorError"); } } else if (d == (double)(long long int)d) { cnt = snprintf(buf, sizeof(buf), "%.1f", d); } else if (oj_rails_float_opt) { cnt = oj_dump_float_printf(buf, sizeof(buf), obj, d, "%0.16g"); } else { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); strcpy(buf, RSTRING_PTR(rstr)); cnt = (int)RSTRING_LEN(rstr); } assure_size(out, cnt); for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 1); fill_indent(out, depth); } else { assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } switch (rb_type(key)) { case T_STRING: oj_dump_str(key, 0, out, false); break; case T_SYMBOL: oj_dump_sym(key, 0, out, false); break; default: /*rb_raise(rb_eTypeError, "In :compat mode all Hash keys must be Strings or Symbols, not %s.\n", rb_class2name(rb_obj_class(key)));*/ oj_dump_str(rb_funcall(key, oj_to_s_id, 0), 0, out, false); break; } if (!out->opts->dump_opts.use) { *out->cur++ = ':'; } else { assure_size(out, out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2); if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } } oj_dump_compat_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; long id = oj_check_circular(obj, out); if (0 > id) { raise_json_err("Too deeply nested", "NestingError"); return; } if (as_ok && !oj_use_hash_alt && rb_obj_class(obj) != rb_cHash && rb_respond_to(obj, oj_to_json_id)) { dump_to_json(obj, out); return; } cnt = (int)RHASH_SIZE(obj); assure_size(out, 2); if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { *out->cur++ = '{'; out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 2); fill_indent(out, depth); } else { assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } // In compat mode only the first call check for to_json. After that to_s is // called. static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { if (oj_code_dump(oj_compat_codes, obj, depth, out)) { return; } if (use_exception_alt && rb_obj_is_kind_of(obj, rb_eException)) { exception_alt(obj, depth, out); return; } if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) { oj_dump_raw_json(obj, depth, out); return; } if (as_ok && rb_respond_to(obj, oj_to_json_id)) { dump_to_json(obj, out); return; } // Nothing else matched so encode as a JSON object with Ruby obj members // as JSON object members. oj_dump_obj_to_s(obj, out); } static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) { VALUE clas = rb_obj_class(obj); if (oj_code_dump(oj_compat_codes, obj, depth, out)) { return; } if (rb_cRange == clas) { *out->cur++ = '"'; oj_dump_compat_val(rb_funcall(obj, oj_begin_id, 0), 0, out, false); assure_size(out, 3); *out->cur++ = '.'; *out->cur++ = '.'; if (Qtrue == rb_funcall(obj, oj_exclude_end_id, 0)) { *out->cur++ = '.'; } oj_dump_compat_val(rb_funcall(obj, oj_end_id, 0), 0, out, false); *out->cur++ = '"'; return; } if (as_ok && rb_respond_to(obj, oj_to_json_id)) { dump_to_json(obj, out); return; } if (use_struct_alt) { int d3 = depth + 2; size_t size = d3 * out->indent + 2; size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; const char *classname = rb_class2name(rb_obj_class(obj)); VALUE args[100]; int cnt; int i; if (NULL == classname || '#' == *classname) { raise_json_err("Only named structs are supported.", "JSONError"); } #ifdef RSTRUCT_LEN #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)NUM2LONG(RSTRUCT_LEN(obj)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)RSTRUCT_LEN(obj); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT #else // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. cnt = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0)); #endif if (sizeof(args) / sizeof(*args) <= (size_t)cnt) { // TBD allocate and try again cnt = 99; } dump_obj_classname(rb_class2name(rb_obj_class(obj)), depth, out); assure_size(out, size + sep_len + 6); *out->cur++ = ','; fill_indent(out, d3); *out->cur++ = '"'; *out->cur++ = 'v'; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } for (i = 0; i < cnt; i++) { #ifdef RSTRUCT_LEN args[i] = RSTRUCT_GET(obj, i); #else args[i] = rb_struct_aref(obj, INT2FIX(i)); #endif } args[cnt] = Qundef; dump_values_array(args, depth, out); fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } else { oj_dump_obj_to_s(obj, out); } } static void dump_bignum(VALUE obj, int depth, Out out, bool as_ok) { // The json gem uses to_s explicitly. to_s can be overridden while // rb_big2str can not so unless overridden by using add_to_json(Integer) // this must use to_s to pass the json gem unit tests. volatile VALUE rs; int cnt; bool dump_as_string = false; if (use_bignum_alt) { rs = rb_big2str(obj, 10); } else { rs = rb_funcall(obj, oj_to_s_id, 0); } rb_check_type(rs, T_STRING); cnt = (int)RSTRING_LEN(rs); if (out->opts->int_range_min != 0 || out->opts->int_range_max != 0) { dump_as_string = true; // Bignum cannot be inside of Fixnum range assure_size(out, cnt + 2); *out->cur++ = '"'; } else { assure_size(out, cnt); } memcpy(out->cur, RSTRING_PTR(rs), cnt); out->cur += cnt; if (dump_as_string) { *out->cur++ = '"'; } *out->cur = '\0'; } static DumpFunc compat_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, oj_dump_class, // RUBY_T_CLASS = 0x02, oj_dump_class, // RUBY_T_MODULE = 0x03, dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, dump_obj, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, dump_struct, // RUBY_T_STRUCT = 0x09, dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_obj, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, dump_obj, // RUBY_T_COMPLEX = 0x0e, dump_obj, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; static void set_state_depth(VALUE state, int depth) { VALUE json_module = rb_const_get_at(rb_cObject, rb_intern("JSON")); VALUE ext = rb_const_get(json_module, rb_intern("Ext")); VALUE generator = rb_const_get(ext, rb_intern("Generator")); VALUE state_class = rb_const_get(generator, rb_intern("State")); if (state_class == rb_obj_class(state)) { rb_funcall(state, rb_intern("depth="), 1, INT2NUM(depth)); } } void oj_dump_compat_val(VALUE obj, int depth, Out out, bool as_ok) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (out->opts->dump_opts.max_depth <= depth) { // When JSON.dump is called then an ArgumentError is expected and the // limit is the depth inclusive. If JSON.generate is called then a // NestingError is expected and the limit is inclusive. Worse than // that there are unit tests for both. if (CALLER_DUMP == out->caller) { if (0 < out->argc) { set_state_depth(*out->argv, depth); } rb_raise(rb_eArgError, "Too deeply nested."); } else if (out->opts->dump_opts.max_depth < depth) { if (0 < out->argc) { set_state_depth(*out->argv, depth - 1); } raise_json_err("Too deeply nested", "NestingError"); } } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = compat_funcs[type]; if (NULL != f) { f(obj, depth, out, as_ok); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } oj-3.13.9/ext/oj/cache.c0000644000004100000410000002022414136373754014703 0ustar www-datawww-data// Copyright (c) 2011, 2021 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #if HAVE_PTHREAD_MUTEX_INIT #include #endif #include #include "cache.h" // The stdlib calloc, realloc, and free are used instead of the Ruby ALLOC, // ALLOC_N, REALLOC, and xfree since the later could trigger a GC which will // either corrupt memory or if the mark function locks will deadlock. #define REHASH_LIMIT 4 #define MIN_SHIFT 8 #define REUSE_MAX 8192 #if HAVE_PTHREAD_MUTEX_INIT #define CACHE_LOCK(c) pthread_mutex_lock(&((c)->mutex)) #define CACHE_UNLOCK(c) pthread_mutex_unlock(&((c)->mutex)) #else #define CACHE_LOCK(c) rb_mutex_lock((c)->mutex) #define CACHE_UNLOCK(c) rb_mutex_unlock((c)->mutex) #endif // almost the Murmur hash algorithm #define M 0x5bd1e995 typedef struct _slot { struct _slot * next; VALUE val; uint64_t hash; volatile uint32_t use_cnt; uint8_t klen; char key[CACHE_MAX_KEY]; } * Slot; typedef struct _cache { volatile Slot * slots; volatile size_t cnt; VALUE (*form)(const char *str, size_t len); uint64_t size; uint64_t mask; VALUE (*intern)(struct _cache *c, const char *key, size_t len); volatile Slot reuse; size_t rcnt; #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_t mutex; #else VALUE mutex; #endif uint8_t xrate; bool mark; } * Cache; void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) { c->form = form; } static uint64_t hash_calc(const uint8_t *key, size_t len) { const uint8_t *end = key + len; const uint8_t *endless = key + (len & 0xFFFFFFFC); uint64_t h = (uint64_t)len; uint64_t k; while (key < endless) { k = (uint64_t)*key++; k |= (uint64_t)*key++ << 8; k |= (uint64_t)*key++ << 16; k |= (uint64_t)*key++ << 24; k *= M; k ^= k >> 24; h *= M; h ^= k * M; } if (1 < end - key) { uint16_t k16 = (uint16_t)*key++; k16 |= (uint16_t)*key++ << 8; h ^= k16 << 8; } if (key < end) { h ^= *key; } h *= M; h ^= h >> 13; h *= M; h ^= h >> 15; return h; } static void rehash(Cache c) { uint64_t osize; Slot * end; Slot * sp; osize = c->size; c->size = osize * 4; c->mask = c->size - 1; c->slots = realloc((void *)c->slots, sizeof(Slot) * c->size); memset((Slot *)c->slots + osize, 0, sizeof(Slot) * osize * 3); end = (Slot *)c->slots + osize; for (sp = (Slot *)c->slots; sp < end; sp++) { Slot s = *sp; Slot next = NULL; *sp = NULL; for (; NULL != s; s = next) { uint64_t h = s->hash & c->mask; Slot * bucket = (Slot *)c->slots + h; next = s->next; s->next = *bucket; *bucket = s; } } } static VALUE lockless_intern(Cache c, const char *key, size_t len) { uint64_t h = hash_calc((const uint8_t *)key, len); Slot * bucket = (Slot *)c->slots + (h & c->mask); Slot b; volatile VALUE rkey; while (REUSE_MAX < c->rcnt) { if (NULL != (b = c->reuse)) { c->reuse = b->next; free(b); c->rcnt--; } else { // An accounting error occured somewhere so correct it. c->rcnt = 0; } } for (b = *bucket; NULL != b; b = b->next) { if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) { b->use_cnt += 16; return b->val; } } rkey = c->form(key, len); if (NULL == (b = c->reuse)) { b = calloc(1, sizeof(struct _slot)); } else { c->reuse = b->next; c->rcnt--; } b->hash = h; memcpy(b->key, key, len); b->klen = (uint8_t)len; b->key[len] = '\0'; b->val = rkey; b->use_cnt = 4; b->next = *bucket; *bucket = b; c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated. if (REHASH_LIMIT < c->cnt / c->size) { rehash(c); } return rkey; } static VALUE locking_intern(Cache c, const char *key, size_t len) { uint64_t h; Slot * bucket; Slot b; uint64_t old_size; volatile VALUE rkey; CACHE_LOCK(c); while (REUSE_MAX < c->rcnt) { if (NULL != (b = c->reuse)) { c->reuse = b->next; free(b); c->rcnt--; } else { // An accounting error occured somewhere so correct it. c->rcnt = 0; } } h = hash_calc((const uint8_t *)key, len); bucket = (Slot *)c->slots + (h & c->mask); for (b = *bucket; NULL != b; b = b->next) { if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) { b->use_cnt += 4; CACHE_UNLOCK(c); return b->val; } } old_size = c->size; // The creation of a new value may trigger a GC which be a problem if the // cache is locked so make sure it is unlocked for the key value creation. if (NULL != (b = c->reuse)) { c->reuse = b->next; c->rcnt--; } CACHE_UNLOCK(c); if (NULL == b) { b = calloc(1, sizeof(struct _slot)); } rkey = c->form(key, len); b->hash = h; memcpy(b->key, key, len); b->klen = (uint8_t)len; b->key[len] = '\0'; b->val = rkey; b->use_cnt = 16; // Lock again to add the new entry. CACHE_LOCK(c); if (old_size != c->size) { h = hash_calc((const uint8_t *)key, len); bucket = (Slot *)c->slots + (h & c->mask); } b->next = *bucket; *bucket = b; c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated. if (REHASH_LIMIT < c->cnt / c->size) { rehash(c); } CACHE_UNLOCK(c); return rkey; } Cache cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking) { Cache c = calloc(1, sizeof(struct _cache)); int shift = 0; for (; REHASH_LIMIT < size; size /= 2, shift++) { } if (shift < MIN_SHIFT) { shift = MIN_SHIFT; } #if HAVE_PTHREAD_MUTEX_INIT pthread_mutex_init(&c->mutex, NULL); #else c->mutex = rb_mutex_new(); #endif c->size = 1 << shift; c->mask = c->size - 1; c->slots = calloc(c->size, sizeof(Slot)); c->form = form; c->xrate = 1; // low c->mark = mark; if (locking) { c->intern = locking_intern; } else { c->intern = lockless_intern; } return c; } void cache_set_expunge_rate(Cache c, int rate) { c->xrate = (uint8_t)rate; } void cache_free(Cache c) { uint64_t i; for (i = 0; i < c->size; i++) { Slot next; Slot s; for (s = c->slots[i]; NULL != s; s = next) { next = s->next; free(s); } } free((void *)c->slots); free(c); } void cache_mark(Cache c) { uint64_t i; #if !HAVE_PTHREAD_MUTEX_INIT rb_gc_mark(c->mutex); #endif if (0 == c->cnt) { return; } for (i = 0; i < c->size; i++) { Slot s; Slot prev = NULL; Slot next; for (s = c->slots[i]; NULL != s; s = next) { next = s->next; if (0 == s->use_cnt) { if (NULL == prev) { c->slots[i] = next; } else { prev->next = next; } c->cnt--; s->next = c->reuse; c->reuse = s; c->rcnt++; continue; } switch (c->xrate) { case 0: break; case 2: s->use_cnt -= 2; break; case 3: s->use_cnt /= 2; break; default: s->use_cnt--; break; } if (c->mark) { rb_gc_mark(s->val); } prev = s; } } } VALUE cache_intern(Cache c, const char *key, size_t len) { if (CACHE_MAX_KEY <= len) { return c->form(key, len); } return c->intern(c, key, len); } oj-3.13.9/ext/oj/buf.h0000644000004100000410000000376614136373754014435 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_BUF_H #define OJ_BUF_H #include "ruby.h" typedef struct _buf { char *head; char *end; char *tail; char base[1024]; } * Buf; inline static void buf_init(Buf buf) { buf->head = buf->base; buf->end = buf->base + sizeof(buf->base) - 1; buf->tail = buf->head; } inline static void buf_reset(Buf buf) { buf->tail = buf->head; } inline static void buf_cleanup(Buf buf) { if (buf->base != buf->head) { xfree(buf->head); } } inline static size_t buf_len(Buf buf) { return buf->tail - buf->head; } inline static const char *buf_str(Buf buf) { *buf->tail = '\0'; return buf->head; } inline static void buf_append_string(Buf buf, const char *s, size_t slen) { if (buf->end <= buf->tail + slen) { size_t len = buf->end - buf->head; size_t toff = buf->tail - buf->head; size_t new_len = len + slen + len / 2; if (buf->base == buf->head) { buf->head = ALLOC_N(char, new_len); memcpy(buf->head, buf->base, len); } else { REALLOC_N(buf->head, char, new_len); } buf->tail = buf->head + toff; buf->end = buf->head + new_len - 1; } memcpy(buf->tail, s, slen); buf->tail += slen; } inline static void buf_append(Buf buf, char c) { if (buf->end <= buf->tail) { size_t len = buf->end - buf->head; size_t toff = buf->tail - buf->head; size_t new_len = len + len / 2; if (buf->base == buf->head) { buf->head = ALLOC_N(char, new_len); memcpy(buf->head, buf->base, len); } else { REALLOC_N(buf->head, char, new_len); } buf->tail = buf->head + toff; buf->end = buf->head + new_len - 1; } *buf->tail = c; buf->tail++; //*buf->tail = '\0'; // for debugging } #endif /* OJ_BUF_H */ oj-3.13.9/ext/oj/parse.c0000644000004100000410000011417514136373754014763 0ustar www-datawww-data// Copyright (c) 2013 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "parse.h" #include #include #include #include #include #include #include "buf.h" #include "encode.h" #include "oj.h" #include "rxclass.h" #include "val_stack.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) //#define EXP_MAX 1023 #define EXP_MAX 100000 #define DEC_MAX 15 static void next_non_white(ParseInfo pi) { for (; 1; pi->cur++) { switch (*pi->cur) { case ' ': case '\t': case '\f': case '\n': case '\r': break; default: return; } } } static void skip_comment(ParseInfo pi) { if ('*' == *pi->cur) { pi->cur++; for (; pi->cur < pi->end; pi->cur++) { if ('*' == *pi->cur && '/' == *(pi->cur + 1)) { pi->cur += 2; return; } else if (pi->end <= pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "comment not terminated"); return; } } } else if ('/' == *pi->cur) { for (; 1; pi->cur++) { switch (*pi->cur) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid comment format"); } } static void add_value(ParseInfo pi, VALUE rval) { Val parent = stack_peek(&pi->stack); if (0 == parent) { // simple add pi->add_value(pi, rval); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_value(pi, rval); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_value(pi, parent, rval); if (0 != parent->key && 0 < parent->klen && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char *)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void read_null(ParseInfo pi) { if ('u' == *pi->cur++ && 'l' == *pi->cur++ && 'l' == *pi->cur++) { add_value(pi, Qnil); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected null"); } } static void read_true(ParseInfo pi) { if ('r' == *pi->cur++ && 'u' == *pi->cur++ && 'e' == *pi->cur++) { add_value(pi, Qtrue); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected true"); } } static void read_false(ParseInfo pi) { if ('a' == *pi->cur++ && 'l' == *pi->cur++ && 's' == *pi->cur++ && 'e' == *pi->cur++) { add_value(pi, Qfalse); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected false"); } } static uint32_t read_hex(ParseInfo pi, const char *h) { uint32_t b = 0; int i; for (i = 0; i < 4; i++, h++) { b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hex character"); return 0; } } return b; } static void unicode_to_chars(ParseInfo pi, Buf buf, uint32_t code) { if (0x0000007F >= code) { buf_append(buf, (char)code); } else if (0x000007FF >= code) { buf_append(buf, 0xC0 | (code >> 6)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x0000FFFF >= code) { buf_append(buf, 0xE0 | (code >> 12)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x001FFFFF >= code) { buf_append(buf, 0xF0 | (code >> 18)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x03FFFFFF >= code) { buf_append(buf, 0xF8 | (code >> 24)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x7FFFFFFF >= code) { buf_append(buf, 0xFC | (code >> 30)); buf_append(buf, 0x80 | ((code >> 24) & 0x3F)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid Unicode character"); } } // entered at / static void read_escaped_str(ParseInfo pi, const char *start) { struct _buf buf; const char *s; int cnt = (int)(pi->cur - start); uint32_t code; Val parent = stack_peek(&pi->stack); buf_init(&buf); if (0 < cnt) { buf_append_string(&buf, start, cnt); } for (s = pi->cur; '"' != *s; s++) { if (s >= pi->end) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); buf_cleanup(&buf); return; } else if ('\\' == *s) { s++; switch (*s) { case 'n': buf_append(&buf, '\n'); break; case 'r': buf_append(&buf, '\r'); break; case 't': buf_append(&buf, '\t'); break; case 'f': buf_append(&buf, '\f'); break; case 'b': buf_append(&buf, '\b'); break; case '"': buf_append(&buf, '"'); break; case '/': buf_append(&buf, '/'); break; case '\\': buf_append(&buf, '\\'); break; case 'u': s++; if (0 == (code = read_hex(pi, s)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } s += 3; if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; s++; if ('\\' != *s || 'u' != *(s + 1)) { if (Yes == pi->options.allow_invalid) { s--; unicode_to_chars(pi, &buf, code); break; } pi->cur = s; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } s += 2; if (0 == (c2 = read_hex(pi, s)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } s += 3; c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } unicode_to_chars(pi, &buf, code); if (err_has(&pi->err)) { buf_cleanup(&buf); return; } break; default: // The json gem claims this is not an error despite the // ECMA-404 indicating it is not valid. if (CompatMode == pi->options.mode) { buf_append(&buf, *s); break; } pi->cur = s; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } } else { buf_append(&buf, *s); } } if (0 == parent) { pi->add_cstr(pi, buf.head, buf_len(&buf), start); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, buf.head, buf_len(&buf), start); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: if (Qundef == (parent->key_val = pi->hash_key(pi, buf.head, buf_len(&buf)))) { parent->klen = buf_len(&buf); parent->key = malloc(parent->klen + 1); memcpy((char *)parent->key, buf.head, parent->klen); *(char *)(parent->key + parent->klen) = '\0'; } else { parent->key = ""; parent->klen = 0; } parent->k1 = *start; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, buf.head, buf_len(&buf), start); if (0 != parent->key && 0 < parent->klen && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char *)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } pi->cur = s + 1; buf_cleanup(&buf); } static void read_str(ParseInfo pi) { const char *str = pi->cur; Val parent = stack_peek(&pi->stack); for (; '"' != *pi->cur; pi->cur++) { if (pi->end <= pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); return; } else if ('\0' == *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "NULL byte in string"); return; } else if ('\\' == *pi->cur) { read_escaped_str(pi, str); return; } } if (0 == parent) { // simple add pi->add_cstr(pi, str, pi->cur - str, str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, str, pi->cur - str, str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: if (Qundef == (parent->key_val = pi->hash_key(pi, str, pi->cur - str))) { parent->key = str; parent->klen = pi->cur - str; } else { parent->key = ""; parent->klen = 0; } parent->k1 = *str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent, str, pi->cur - str, str); if (0 != parent->key && 0 < parent->klen && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char *)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } pi->cur++; // move past " } static void read_num(ParseInfo pi) { struct _numInfo ni; Val parent = stack_peek(&pi->stack); ni.str = pi->cur; ni.i = 0; ni.num = 0; ni.div = 1; ni.di = 0; ni.len = 0; ni.exp = 0; ni.big = 0; ni.infinity = 0; ni.nan = 0; ni.neg = 0; ni.has_exp = 0; if (CompatMode == pi->options.mode) { ni.no_big = !pi->options.compat_bigdec; ni.bigdec_load = pi->options.compat_bigdec; } else { ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load || RubyDec == pi->options.bigdec_load); ni.bigdec_load = pi->options.bigdec_load; } if ('-' == *pi->cur) { pi->cur++; ni.neg = 1; } else if ('+' == *pi->cur) { if (StrictMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } pi->cur++; } if ('I' == *pi->cur) { if (No == pi->options.allow_nan || 0 != strncmp("Infinity", pi->cur, 8)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } pi->cur += 8; ni.infinity = 1; } else if ('N' == *pi->cur || 'n' == *pi->cur) { if ('a' != pi->cur[1] || ('N' != pi->cur[2] && 'n' != pi->cur[2])) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } pi->cur += 3; ni.nan = 1; } else { int dec_cnt = 0; bool zero1 = false; for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { if (0 == ni.i && '0' == *pi->cur) { zero1 = true; } if (0 < ni.i) { dec_cnt++; } if (!ni.big) { int d = (*pi->cur - '0'); if (0 < d) { if (zero1 && CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); return; } zero1 = false; } ni.i = ni.i * 10 + d; if (INT64_MAX <= ni.i || DEC_MAX < dec_cnt) { ni.big = 1; } } } if ('.' == *pi->cur) { pi->cur++; // A trailing . is not a valid decimal but if encountered allow it // except when mimicking the JSON gem or in strict mode. if (StrictMode == pi->options.mode || CompatMode == pi->options.mode) { int pos = (int)(pi->cur - ni.str); if (1 == pos || (2 == pos && ni.neg)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); return; } if (*pi->cur < '0' || '9' < *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number"); return; } } for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { int d = (*pi->cur - '0'); if (0 < ni.num || 0 < ni.i) { dec_cnt++; } if (INT64_MAX <= ni.div) { if (!ni.no_big) { ni.big = true; } } else { ni.num = ni.num * 10 + d; ni.div *= 10; ni.di++; if (INT64_MAX <= ni.div || DEC_MAX < dec_cnt) { if (!ni.no_big) { ni.big = true; } } } } } if ('e' == *pi->cur || 'E' == *pi->cur) { int eneg = 0; ni.has_exp = 1; pi->cur++; if ('-' == *pi->cur) { pi->cur++; eneg = 1; } else if ('+' == *pi->cur) { pi->cur++; } for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { ni.exp = ni.exp * 10 + (*pi->cur - '0'); if (EXP_MAX <= ni.exp) { ni.big = true; } } if (eneg) { ni.exp = -ni.exp; } } ni.len = pi->cur - ni.str; } // Check for special reserved values for Infinity and NaN. if (ni.big) { if (0 == strcasecmp(INF_VAL, ni.str)) { ni.infinity = 1; } else if (0 == strcasecmp(NINF_VAL, ni.str)) { ni.infinity = 1; ni.neg = 1; } else if (0 == strcasecmp(NAN_VAL, ni.str)) { ni.nan = 1; } } if (CompatMode == pi->options.mode) { if (pi->options.compat_bigdec) { ni.big = 1; } } else if (BigDec == pi->options.bigdec_load) { ni.big = 1; } if (0 == parent) { pi->add_num(pi, &ni); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_num(pi, &ni); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_num(pi, parent, &ni); if (0 != parent->key && 0 < parent->klen && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char *)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void array_start(ParseInfo pi) { volatile VALUE v = pi->start_array(pi); stack_push(&pi->stack, v, NEXT_ARRAY_NEW); } static void array_end(ParseInfo pi) { Val array = stack_pop(&pi->stack); if (0 == array) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected array close"); } else if (NEXT_ARRAY_COMMA != array->next && NEXT_ARRAY_NEW != array->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not an array close", oj_stack_next_string(array->next)); } else { pi->end_array(pi); add_value(pi, array->val); } } static void hash_start(ParseInfo pi) { volatile VALUE v = pi->start_hash(pi); stack_push(&pi->stack, v, NEXT_HASH_NEW); } static void hash_end(ParseInfo pi) { volatile Val hash = stack_peek(&pi->stack); // leave hash on stack until just before if (0 == hash) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected hash close"); } else if (NEXT_HASH_COMMA != hash->next && NEXT_HASH_NEW != hash->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a hash close", oj_stack_next_string(hash->next)); } else { pi->end_hash(pi); stack_pop(&pi->stack); add_value(pi, hash->val); } } static void comma(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 == parent) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } else if (NEXT_ARRAY_COMMA == parent->next) { parent->next = NEXT_ARRAY_ELEMENT; } else if (NEXT_HASH_COMMA == parent->next) { parent->next = NEXT_HASH_KEY; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } } static void colon(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 != parent && NEXT_HASH_COLON == parent->next) { parent->next = NEXT_HASH_VALUE; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected colon"); } } void oj_parse2(ParseInfo pi) { int first = 1; long start = 0; pi->cur = pi->json; err_init(&pi->err); while (1) { if (0 < pi->max_depth && pi->max_depth <= pi->stack.tail - pi->stack.head - 1) { VALUE err_clas = oj_get_json_err_class("NestingError"); oj_set_error_at(pi, err_clas, __FILE__, __LINE__, "Too deeply nested."); pi->err_class = err_clas; return; } next_non_white(pi); if (!first && '\0' != *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected characters after the JSON document"); } // If no tokens are consumed (i.e. empty string), throw a parse error // this is the behavior of JSON.parse in both Ruby and JS. if (No == pi->options.empty_string && 1 == first && '\0' == *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); } switch (*pi->cur++) { case '{': hash_start(pi); break; case '}': hash_end(pi); break; case ':': colon(pi); break; case '[': array_start(pi); break; case ']': array_end(pi); break; case ',': comma(pi); break; case '"': read_str(pi); break; // case '+': case '+': if (CompatMode == pi->options.mode) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } pi->cur--; read_num(pi); break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': pi->cur--; read_num(pi); break; case 'I': case 'N': if (Yes == pi->options.allow_nan) { pi->cur--; read_num(pi); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); } break; case 't': read_true(pi); break; case 'f': read_false(pi); break; case 'n': if ('u' == *pi->cur) { read_null(pi); } else { pi->cur--; read_num(pi); } break; case '/': skip_comment(pi); if (first) { continue; } break; case '\0': pi->cur--; return; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } if (err_has(&pi->err)) { return; } if (stack_empty(&pi->stack)) { if (Qundef != pi->proc) { VALUE args[3]; long len = (pi->cur - pi->json) - start; *args = stack_head_val(&pi->stack); args[1] = LONG2NUM(start); args[2] = LONG2NUM(len); if (Qnil == pi->proc) { rb_yield_values2(3, args); } else { rb_proc_call_with_block(pi->proc, 3, args, Qnil); } } else if (!pi->has_callbacks) { first = 0; } start = pi->cur - pi->json; } } } static VALUE rescue_big_decimal(VALUE str, VALUE ignore) { rb_raise(oj_parse_error_class, "Invalid value for BigDecimal()"); return Qnil; } static VALUE parse_big_decimal(VALUE str) { return rb_funcall(rb_cObject, oj_bigdecimal_id, 1, str); } static long double exp_plus[] = { 1.0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, 1.0e20, 1.0e21, 1.0e22, 1.0e23, 1.0e24, 1.0e25, 1.0e26, 1.0e27, 1.0e28, 1.0e29, 1.0e30, 1.0e31, 1.0e32, 1.0e33, 1.0e34, 1.0e35, 1.0e36, 1.0e37, 1.0e38, 1.0e39, 1.0e40, 1.0e41, 1.0e42, 1.0e43, 1.0e44, 1.0e45, 1.0e46, 1.0e47, 1.0e48, 1.0e49, }; VALUE oj_num_as_value(NumInfo ni) { volatile VALUE rnum = Qnil; if (ni->infinity) { if (ni->neg) { rnum = rb_float_new(-OJ_INFINITY); } else { rnum = rb_float_new(OJ_INFINITY); } } else if (ni->nan) { rnum = rb_float_new(0.0 / 0.0); } else if (1 == ni->div && 0 == ni->exp && !ni->has_exp) { // fixnum if (ni->big) { if (256 > ni->len) { char buf[256]; memcpy(buf, ni->str, ni->len); buf[ni->len] = '\0'; rnum = rb_cstr_to_inum(buf, 10, 0); } else { char *buf = ALLOC_N(char, ni->len + 1); memcpy(buf, ni->str, ni->len); buf[ni->len] = '\0'; rnum = rb_cstr_to_inum(buf, 10, 0); xfree(buf); } } else { if (ni->neg) { rnum = rb_ll2inum(-ni->i); } else { rnum = rb_ll2inum(ni->i); } } } else { // decimal if (ni->big) { volatile VALUE bd = rb_str_new(ni->str, ni->len); rnum = rb_rescue2(parse_big_decimal, bd, rescue_big_decimal, bd, rb_eException, 0); if (ni->no_big) { rnum = rb_funcall(rnum, rb_intern("to_f"), 0); } } else if (FastDec == ni->bigdec_load) { long double ld = (long double)ni->i * (long double)ni->div + (long double)ni->num; int x = (int)((int64_t)ni->exp - ni->di); if (0 < x) { if (x < (int)(sizeof(exp_plus) / sizeof(*exp_plus))) { ld *= exp_plus[x]; } else { ld *= powl(10.0, x); } } else if (x < 0) { if (-x < (int)(sizeof(exp_plus) / sizeof(*exp_plus))) { ld /= exp_plus[-x]; } else { ld /= powl(10.0, -x); } } if (ni->neg) { ld = -ld; } rnum = rb_float_new((double)ld); } else if (RubyDec == ni->bigdec_load) { volatile VALUE sv = rb_str_new(ni->str, ni->len); rnum = rb_funcall(sv, rb_intern("to_f"), 0); } else { char * end; double d = strtod(ni->str, &end); if ((long)ni->len != (long)(end - ni->str)) { rb_raise(oj_parse_error_class, "Invalid float"); } rnum = rb_float_new(d); } } return rnum; } void oj_set_error_at(ParseInfo pi, VALUE err_clas, const char *file, int line, const char *format, ...) { va_list ap; char msg[256]; char * p = msg; char * end = p + sizeof(msg) - 2; char * start; Val vp; int mlen; va_start(ap, format); mlen = vsnprintf(msg, sizeof(msg) - 1, format, ap); if (0 < mlen) { if (sizeof(msg) - 2 < (size_t)mlen) { p = end - 2; } else { p += mlen; } } va_end(ap); pi->err.clas = err_clas; if (p + 3 < end) { *p++ = ' '; *p++ = '('; *p++ = 'a'; *p++ = 'f'; *p++ = 't'; *p++ = 'e'; *p++ = 'r'; *p++ = ' '; start = p; for (vp = pi->stack.head; vp < pi->stack.tail; vp++) { if (end <= p + 1 + vp->klen) { break; } if (NULL != vp->key) { if (start < p) { *p++ = '.'; } memcpy(p, vp->key, vp->klen); p += vp->klen; } else { if (RUBY_T_ARRAY == rb_type(vp->val)) { if (end <= p + 12) { break; } p += snprintf(p, end - p, "[%ld]", RARRAY_LEN(vp->val)); } } } *p++ = ')'; } *p = '\0'; if (0 == pi->json) { oj_err_set(&pi->err, err_clas, "%s at line %d, column %d [%s:%d]", msg, pi->rd.line, pi->rd.col, file, line); } else { _oj_err_set_with_location(&pi->err, err_clas, msg, pi->json, pi->cur - 1, file, line); } } static VALUE protect_parse(VALUE pip) { oj_parse2((ParseInfo)pip); return Qnil; } extern int oj_utf8_index; static void oj_pi_set_input_str(ParseInfo pi, volatile VALUE *inputp) { rb_encoding *enc = rb_enc_get(*inputp); if (oj_utf8_encoding != enc) { *inputp = rb_str_conv_enc(*inputp, enc, oj_utf8_encoding); } pi->json = RSTRING_PTR(*inputp); pi->end = pi->json + RSTRING_LEN(*inputp); } VALUE oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len, int yieldOk) { char * buf = 0; volatile VALUE input; volatile VALUE wrapped_stack; volatile VALUE result = Qnil; int line = 0; int free_json = 0; if (argc < 1) { rb_raise(rb_eArgError, "Wrong number of arguments to parse."); } input = argv[0]; if (2 <= argc) { if (T_HASH == rb_type(argv[1])) { oj_parse_options(argv[1], &pi->options); } else if (3 <= argc && T_HASH == rb_type(argv[2])) { oj_parse_options(argv[2], &pi->options); } } if (yieldOk && rb_block_given_p()) { pi->proc = Qnil; } else { pi->proc = Qundef; } if (0 != json) { pi->json = json; pi->end = json + len; free_json = 1; } else if (T_STRING == rb_type(input)) { if (CompatMode == pi->options.mode) { if (No == pi->options.nilnil && 0 == RSTRING_LEN(input)) { rb_raise(oj_json_parser_error_class, "An empty string is not a valid JSON string."); } } oj_pi_set_input_str(pi, &input); } else if (Qnil == input) { if (Yes == pi->options.nilnil) { return Qnil; } else { rb_raise(rb_eTypeError, "Nil is not a valid JSON source."); } } else { VALUE clas = rb_obj_class(input); volatile VALUE s; if (oj_stringio_class == clas) { s = rb_funcall2(input, oj_string_id, 0, 0); oj_pi_set_input_str(pi, &s); #if !IS_WINDOWS } else if (rb_cFile == clas && 0 == FIX2INT(rb_funcall(input, oj_pos_id, 0))) { int fd = FIX2INT(rb_funcall(input, oj_fileno_id, 0)); ssize_t cnt; size_t len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); buf = ALLOC_N(char, len + 1); pi->json = buf; pi->end = buf + len; if (0 >= (cnt = read(fd, (char *)pi->json, len)) || cnt != (ssize_t)len) { if (0 != buf) { xfree(buf); } rb_raise(rb_eIOError, "failed to read from IO Object."); } ((char *)pi->json)[len] = '\0'; /* skip UTF-8 BOM if present */ if (0xEF == (uint8_t)*pi->json && 0xBB == (uint8_t)pi->json[1] && 0xBF == (uint8_t)pi->json[2]) { pi->cur += 3; } #endif } else if (rb_respond_to(input, oj_read_id)) { // use stream parser instead return oj_pi_sparse(argc, argv, pi, 0); } else { rb_raise(rb_eArgError, "parse() expected a String or IO Object."); } } if (Yes == pi->options.circular) { pi->circ_array = oj_circ_array_new(); } else { pi->circ_array = 0; } if (No == pi->options.allow_gc) { rb_gc_disable(); } // GC can run at any time. When it runs any Object created by C will be // freed. We protect against this by wrapping the value stack in a ruby // data object and poviding a mark function for ruby objects on the // value stack (while it is in scope). wrapped_stack = oj_stack_init(&pi->stack); rb_protect(protect_parse, (VALUE)pi, &line); if (Qundef == pi->stack.head->val && !empty_ok(&pi->options)) { if (No == pi->options.nilnil || (CompatMode == pi->options.mode && 0 < pi->cur - pi->json)) { oj_set_error_at(pi, oj_json_parser_error_class, __FILE__, __LINE__, "Empty input"); } } result = stack_head_val(&pi->stack); DATA_PTR(wrapped_stack) = 0; if (No == pi->options.allow_gc) { rb_gc_enable(); } if (!err_has(&pi->err)) { // If the stack is not empty then the JSON terminated early. Val v; VALUE err_class = oj_parse_error_class; if (0 != line) { VALUE ec = rb_obj_class(rb_errinfo()); if (rb_eArgError != ec && 0 != ec) { err_class = ec; } if (rb_eIOError != ec) { goto CLEANUP; } } if (NULL != (v = stack_peek(&pi->stack))) { switch (v->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: case NEXT_ARRAY_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Array not terminated"); break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COLON: case NEXT_HASH_VALUE: case NEXT_HASH_COMMA: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "Hash/Object not terminated"); break; default: oj_set_error_at(pi, err_class, __FILE__, __LINE__, "not terminated"); } } } CLEANUP: // proceed with cleanup if (0 != pi->circ_array) { oj_circ_array_free(pi->circ_array); } if (0 != buf) { xfree(buf); } else if (free_json) { xfree(json); } stack_cleanup(&pi->stack); if (pi->str_rx.head != oj_default_options.str_rx.head) { oj_rxclass_cleanup(&pi->str_rx); } if (err_has(&pi->err)) { rb_set_errinfo(Qnil); if (Qnil != pi->err_class) { pi->err.clas = pi->err_class; } if ((CompatMode == pi->options.mode || RailsMode == pi->options.mode) && Yes != pi->options.safe) { // The json gem requires the error message be UTF-8 encoded. In // additional the complete JSON source must be returned. There // does not seem to be a size limit. VALUE msg = oj_encode(rb_str_new2(pi->err.msg)); VALUE args[1]; if (NULL != pi->json) { msg = rb_str_append(msg, oj_encode(rb_str_new2(" in '"))); msg = rb_str_append(msg, oj_encode(rb_str_new2(pi->json))); } args[0] = msg; if (pi->err.clas == oj_parse_error_class) { // The error was an Oj::ParseError so change to a JSON::ParserError. pi->err.clas = oj_json_parser_error_class; } rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas)); } else { oj_err_raise(&pi->err); } } else if (0 != line) { rb_jump_tag(line); } if (pi->options.quirks_mode == No) { switch (rb_type(result)) { case T_NIL: case T_TRUE: case T_FALSE: case T_FIXNUM: case T_FLOAT: case T_CLASS: case T_STRING: case T_SYMBOL: { struct _err err; if (Qnil == pi->err_class) { err.clas = oj_parse_error_class; } else { err.clas = pi->err_class; } snprintf(err.msg, sizeof(err.msg), "unexpected non-document value"); oj_err_raise(&err); break; } default: // okay break; } } return result; } oj-3.13.9/ext/oj/compat.c0000644000004100000410000002263414136373754015132 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include "encode.h" #include "err.h" #include "intern.h" #include "oj.h" #include "parse.h" #include "resolve.h" #include "trace.h" static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { const char * key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); volatile VALUE rkey = kval->key_val; if (Qundef == rkey && Yes == pi->options.create_ok && NULL != pi->options.create_id && *pi->options.create_id == *key && (int)pi->options.create_id_len == klen && 0 == strncmp(pi->options.create_id, key, klen)) { parent->classname = oj_strndup(str, len); parent->clen = len; } else { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); if (Qundef == rkey) { if (Yes != pi->options.cache_keys) { if (Yes == pi->options.sym_key) { rkey = ID2SYM(rb_intern3(key, klen, oj_utf8_encoding)); } else { rkey = rb_utf8_str_new(key, klen); } } else if (Yes == pi->options.sym_key) { rkey = oj_sym_intern(key, klen); } else { rkey = oj_str_intern(key, klen); } } if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rstr = rb_funcall(clas, oj_json_create_id, 1, rstr); } } if (rb_cHash != rb_obj_class(parent->val)) { // The rb_hash_set would still work but the unit tests for the // json gem require the less efficient []= method be called to set // values. Even using the store method to set the values will fail // the unit tests. rb_funcall(parent->val, rb_intern("[]="), 2, rkey, rstr); } else { rb_hash_aset(parent->val, rkey, rstr); } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rstr); } } } static VALUE start_hash(ParseInfo pi) { volatile VALUE h; if (Qnil != pi->options.hash_class) { h = rb_class_new_instance(0, NULL, pi->options.hash_class); } else { h = rb_hash_new(); } if (Yes == pi->options.trace) { oj_trace_parse_in("start_hash", pi, __FILE__, __LINE__); } return h; } static void end_hash(struct _parseInfo *pi) { Val parent = stack_peek(&pi->stack); if (0 != parent->classname) { volatile VALUE clas; clas = oj_name2class(pi, parent->classname, parent->clen, 0, rb_eArgError); if (Qundef != clas) { // else an error ID creatable = rb_intern("json_creatable?"); if (!rb_respond_to(clas, creatable) || Qtrue == rb_funcall(clas, creatable, 0)) { parent->val = rb_funcall(clas, oj_json_create_id, 1, parent->val); } } if (0 != parent->classname) { xfree((char *)parent->classname); parent->classname = 0; } } if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { pi->stack.head->val = rb_funcall(clas, oj_json_create_id, 1, rstr); return; } } pi->stack.head->val = rstr; if (Yes == pi->options.trace) { oj_trace_parse_call("add_string", pi, __FILE__, __LINE__, rstr); } } static void add_num(ParseInfo pi, NumInfo ni) { pi->stack.head->val = oj_num_as_value(ni); if (Yes == pi->options.trace) { oj_trace_parse_call("add_number", pi, __FILE__, __LINE__, pi->stack.head->val); } } static void hash_set_num(struct _parseInfo *pi, Val parent, NumInfo ni) { volatile VALUE rval = oj_num_as_value(ni); if (!oj_use_hash_alt && rb_cHash != rb_obj_class(parent->val)) { // The rb_hash_set would still work but the unit tests for the // json gem require the less efficient []= method be called to set // values. Even using the store method to set the values will fail // the unit tests. rb_funcall(stack_peek(&pi->stack)->val, rb_intern("[]="), 2, oj_calc_hash_key(pi, parent), rval); } else { rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), rval); } if (Yes == pi->options.trace) { oj_trace_parse_call("set_number", pi, __FILE__, __LINE__, rval); } } static void hash_set_value(ParseInfo pi, Val parent, VALUE value) { if (rb_cHash != rb_obj_class(parent->val)) { // The rb_hash_set would still work but the unit tests for the // json gem require the less efficient []= method be called to set // values. Even using the store method to set the values will fail // the unit tests. rb_funcall(stack_peek(&pi->stack)->val, rb_intern("[]="), 2, oj_calc_hash_key(pi, parent), value); } else { rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), value); } if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value); } } static VALUE start_array(ParseInfo pi) { if (Qnil != pi->options.array_class) { return rb_class_new_instance(0, NULL, pi->options.array_class); } if (Yes == pi->options.trace) { oj_trace_parse_in("start_array", pi, __FILE__, __LINE__); } return rb_ary_new(); } static void array_append_num(ParseInfo pi, NumInfo ni) { Val parent = stack_peek(&pi->stack); volatile VALUE rval = oj_num_as_value(ni); if (!oj_use_array_alt && rb_cArray != rb_obj_class(parent->val)) { // The rb_ary_push would still work but the unit tests for the json // gem require the less efficient << method be called to push the // values. rb_funcall(parent->val, rb_intern("<<"), 1, rval); } else { rb_ary_push(parent->val, rval); } if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval); } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr)); return; } } rb_ary_push(stack_peek(&pi->stack)->val, rstr); if (Yes == pi->options.trace) { oj_trace_parse_call("append_string", pi, __FILE__, __LINE__, rstr); } } void oj_set_compat_callbacks(ParseInfo pi) { oj_set_strict_callbacks(pi); pi->start_hash = start_hash; pi->end_hash = end_hash; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->add_num = add_num; pi->add_cstr = add_cstr; pi->array_append_cstr = array_append_cstr; pi->start_array = start_array; pi->array_append_num = array_append_num; } VALUE oj_compat_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; pi.options.empty_string = No; oj_set_compat_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, false); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_compat_load(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; pi.options.empty_string = Yes; oj_set_compat_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, false); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; oj_set_compat_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, json, len, false); } oj-3.13.9/ext/oj/custom.c0000644000004100000410000011330214136373754015152 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include "code.h" #include "dump.h" #include "encode.h" #include "err.h" #include "intern.h" #include "odd.h" #include "oj.h" #include "parse.h" #include "resolve.h" #include "trace.h" #include "util.h" extern void oj_set_obj_ivar(Val parent, Val kval, VALUE value); extern VALUE oj_parse_xml_time(const char *str, int len); // from object.c static void dump_obj_str(VALUE obj, int depth, Out out) { struct _attr attrs[] = { {"s", 1, Qnil}, {NULL, 0, Qnil}, }; attrs->value = rb_funcall(obj, oj_to_s_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } static void dump_obj_as_str(VALUE obj, int depth, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char * str = RSTRING_PTR(rstr); oj_dump_cstr(str, RSTRING_LEN(rstr), 0, 0, out); } static void bigdecimal_dump(VALUE obj, int depth, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char * str = RSTRING_PTR(rstr); int len = (int)RSTRING_LEN(rstr); if (0 == strcasecmp("Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len); oj_dump_raw(str, len, out); } else if (0 == strcasecmp("-Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len); oj_dump_raw(str, len, out); } else if (No == out->opts->bigdec_as_num) { oj_dump_cstr(str, len, 0, 0, out); } else { oj_dump_raw(str, len, out); } } static ID real_id = 0; static ID imag_id = 0; static void complex_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { {"real", 4, Qnil}, {"imag", 4, Qnil}, {NULL, 0, Qnil}, }; if (0 == real_id) { real_id = rb_intern("real"); imag_id = rb_intern("imag"); } attrs[0].value = rb_funcall(obj, real_id, 0); attrs[1].value = rb_funcall(obj, imag_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE complex_load(VALUE clas, VALUE args) { if (0 == real_id) { real_id = rb_intern("real"); imag_id = rb_intern("imag"); } return rb_complex_new(rb_hash_aref(args, rb_id2str(real_id)), rb_hash_aref(args, rb_id2str(imag_id))); } static void time_dump(VALUE obj, int depth, Out out) { if (Yes == out->opts->create_ok) { struct _attr attrs[] = { {"time", 4, Qundef, 0, Qundef}, {NULL, 0, Qnil}, }; attrs->time = obj; oj_code_attrs(obj, attrs, depth, out, true); } else { switch (out->opts->time_format) { case RubyTime: oj_dump_ruby_time(obj, out); break; case XmlTime: oj_dump_xml_time(obj, out); break; case UnixZTime: oj_dump_time(obj, out, true); break; case UnixTime: default: oj_dump_time(obj, out, false); break; } } } static void date_dump(VALUE obj, int depth, Out out) { if (Yes == out->opts->create_ok) { struct _attr attrs[] = { {"s", 1, Qnil}, {NULL, 0, Qnil}, }; attrs->value = rb_funcall(obj, rb_intern("iso8601"), 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { volatile VALUE v; volatile VALUE ov; switch (out->opts->time_format) { case RubyTime: case XmlTime: v = rb_funcall(obj, rb_intern("iso8601"), 0); oj_dump_cstr(RSTRING_PTR(v), (int)RSTRING_LEN(v), 0, 0, out); break; case UnixZTime: v = rb_funcall(obj, rb_intern("to_time"), 0); if (oj_date_class == rb_obj_class(obj)) { ov = rb_funcall(v, rb_intern("utc_offset"), 0); v = rb_funcall(v, rb_intern("utc"), 0); v = rb_funcall(v, rb_intern("+"), 1, ov); oj_dump_time(v, out, false); } else { oj_dump_time(v, out, true); } break; case UnixTime: default: v = rb_funcall(obj, rb_intern("to_time"), 0); if (oj_date_class == rb_obj_class(obj)) { ov = rb_funcall(v, rb_intern("utc_offset"), 0); v = rb_funcall(v, rb_intern("utc"), 0); v = rb_funcall(v, rb_intern("+"), 1, ov); } oj_dump_time(v, out, false); break; } } } static VALUE date_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(oj_date_class, rb_intern("parse"), 1, v); } return Qnil; } static VALUE datetime_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(oj_datetime_class, rb_intern("parse"), 1, v); } return Qnil; } static ID table_id = 0; static void openstruct_dump(VALUE obj, int depth, Out out) { struct _attr attrs[] = { {"table", 5, Qnil}, {NULL, 0, Qnil}, }; if (0 == table_id) { table_id = rb_intern("table"); } attrs->value = rb_funcall(obj, table_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } static VALUE openstruct_load(VALUE clas, VALUE args) { if (0 == table_id) { table_id = rb_intern("table"); } return rb_funcall(clas, oj_new_id, 1, rb_hash_aref(args, rb_id2str(table_id))); } static void range_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { {"begin", 5, Qnil}, {"end", 3, Qnil}, {"exclude", 7, Qnil}, {NULL, 0, Qnil}, }; attrs[0].value = rb_funcall(obj, oj_begin_id, 0); attrs[1].value = rb_funcall(obj, oj_end_id, 0); attrs[2].value = rb_funcall(obj, oj_exclude_end_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE range_load(VALUE clas, VALUE args) { VALUE nargs[3]; nargs[0] = rb_hash_aref(args, rb_id2str(oj_begin_id)); nargs[1] = rb_hash_aref(args, rb_id2str(oj_end_id)); nargs[2] = rb_hash_aref(args, rb_id2str(oj_exclude_end_id)); return rb_class_new_instance(3, nargs, rb_cRange); } static ID numerator_id = 0; static ID denominator_id = 0; static void rational_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { {"numerator", 9, Qnil}, {"denominator", 11, Qnil}, {NULL, 0, Qnil}, }; if (0 == numerator_id) { numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); } attrs[0].value = rb_funcall(obj, numerator_id, 0); attrs[1].value = rb_funcall(obj, denominator_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE rational_load(VALUE clas, VALUE args) { if (0 == numerator_id) { numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); } return rb_rational_new(rb_hash_aref(args, rb_id2str(numerator_id)), rb_hash_aref(args, rb_id2str(denominator_id))); } static VALUE regexp_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(rb_cRegexp, oj_new_id, 1, v); } return Qnil; } static VALUE time_load(VALUE clas, VALUE args) { // Value should have already been replaced in one of the hash_set_xxx // functions. return args; } static struct _code codes[] = { {"BigDecimal", Qnil, bigdecimal_dump, NULL, true}, {"Complex", Qnil, complex_dump, complex_load, true}, {"Date", Qnil, date_dump, date_load, true}, {"DateTime", Qnil, date_dump, datetime_load, true}, {"OpenStruct", Qnil, openstruct_dump, openstruct_load, true}, {"Range", Qnil, range_dump, range_load, true}, {"Rational", Qnil, rational_dump, rational_load, true}, {"Regexp", Qnil, dump_obj_str, regexp_load, true}, {"Time", Qnil, time_dump, time_load, true}, {NULL, Qundef, NULL, NULL, false}, }; static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 1); fill_indent(out, depth); } else { assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } switch (rb_type(key)) { case T_STRING: oj_dump_str(key, 0, out, false); break; case T_SYMBOL: oj_dump_sym(key, 0, out, false); break; default: oj_dump_str(rb_funcall(key, oj_to_s_id, 0), 0, out, false); break; } if (!out->opts->dump_opts.use) { *out->cur++ = ':'; } else { assure_size(out, out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2); if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } } oj_dump_custom_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; long id = oj_check_circular(obj, out); if (0 > id) { oj_dump_nil(Qnil, depth, out, false); return; } cnt = (int)RHASH_SIZE(obj); assure_size(out, 2); if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { *out->cur++ = '{'; out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 2); fill_indent(out, depth); } else { assure_size( out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) { ID * idp; AttrGetFunc * fp; volatile VALUE v; const char * name; size_t size; int d2 = depth + 1; assure_size(out, 2); *out->cur++ = '{'; if (NULL != out->opts->create_id && Yes == out->opts->create_ok) { const char *classname = rb_class2name(clas); int clen = (int)strlen(classname); size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; size = d2 * out->indent + 10 + clen + out->opts->create_id_len + sep_len; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, clen); out->cur += clen; *out->cur++ = '"'; *out->cur++ = ','; } if (odd->raw) { v = rb_funcall(obj, *odd->attrs, 0); if (Qundef == v || T_STRING != rb_type(v)) { rb_raise(rb_eEncodingError, "Invalid type for raw JSON.\n"); } else { const char *s = RSTRING_PTR(v); int len = (int)RSTRING_LEN(v); const char *name = rb_id2name(*odd->attrs); size_t nlen = strlen(name); size = len + d2 * out->indent + nlen + 10; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, name, nlen); out->cur += nlen; *out->cur++ = '"'; *out->cur++ = ':'; memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } } else { size = d2 * out->indent + 1; for (idp = odd->attrs, fp = odd->attrFuncs; 0 != *idp; idp++, fp++) { size_t nlen; assure_size(out, size); name = rb_id2name(*idp); nlen = strlen(name); if (0 != *fp) { v = (*fp)(obj); } else if (0 == strchr(name, '.')) { v = rb_funcall(obj, *idp, 0); } else { char nbuf[256]; char *n2 = nbuf; char *n; char *end; ID i; if (sizeof(nbuf) <= nlen) { if (NULL == (n2 = strdup(name))) { rb_raise(rb_eNoMemError, "for attribute name."); } } else { strcpy(n2, name); } n = n2; v = obj; while (0 != (end = strchr(n, '.'))) { *end = '\0'; i = rb_intern(n); v = rb_funcall(v, i, 0); n = end + 1; } i = rb_intern(n); v = rb_funcall(v, i, 0); if (nbuf != n2) { free(n2); } } fill_indent(out, d2); oj_dump_cstr(name, nlen, 0, 0, out); *out->cur++ = ':'; oj_dump_custom_val(v, d2, out, true); assure_size(out, 2); *out->cur++ = ','; } out->cur--; } *out->cur++ = '}'; *out->cur = '\0'; } // Return class if still needs dumping. static VALUE dump_common(VALUE obj, int depth, Out out) { if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) { oj_dump_raw_json(obj, depth, out); } else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) { volatile VALUE rs; const char * s; int len; if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } if (0 == rb_obj_method_arity(obj, oj_to_json_id)) { rs = rb_funcall(obj, oj_to_json_id, 0); } else { rs = rb_funcall2(obj, oj_to_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } s = RSTRING_PTR(rs); len = (int)RSTRING_LEN(rs); assure_size(out, len + 1); memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } else if (Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) { volatile VALUE aj; if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } // Some classes elect to not take an options argument so check the arity // of as_json. if (0 == rb_obj_method_arity(obj, oj_as_json_id)) { aj = rb_funcall(obj, oj_as_json_id, 0); } else { aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } // Catch the obvious brain damaged recursive dumping. if (aj == obj) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), false, false, out); } else { oj_dump_custom_val(aj, depth, out, true); } } else if (Yes == out->opts->to_hash && rb_respond_to(obj, oj_to_hash_id)) { volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { // It seems that ActiveRecord implemented to_hash so that it returns // an Array and not a Hash. To get around that any value returned // will be dumped. // rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", // rb_class2name(rb_obj_class(obj))); oj_dump_custom_val(h, depth, out, false); } else { dump_hash(h, depth, out, true); } } else if (!oj_code_dump(codes, obj, depth, out)) { VALUE clas = rb_obj_class(obj); Odd odd = oj_get_odd(clas); if (NULL == odd) { return clas; } dump_odd(obj, odd, clas, depth + 1, out); } return Qnil; } static int dump_attr_cb(ID key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; size_t size; const char *attr; if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } size = depth * out->indent + 1; attr = rb_id2name(key); // Some exceptions such as NoMethodError have an invisible attribute where // the key name is NULL. Not an empty string but NULL. if (NULL == attr) { attr = ""; } else if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) { return ST_CONTINUE; } if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) { return ST_CONTINUE; } assure_size(out, size); fill_indent(out, depth); if ('@' == *attr) { attr++; oj_dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; oj_dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; oj_dump_custom_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) { size_t size = 0; int d2 = depth + 1; int cnt; bool class_written = false; assure_size(out, 2); *out->cur++ = '{'; if (Qundef != clas && NULL != out->opts->create_id && Yes == out->opts->create_ok) { size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; const char *classname = rb_obj_classname(obj); size_t len = strlen(classname); size = d2 * out->indent + 10 + len + out->opts->create_id_len + sep_len; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, len); out->cur += len; *out->cur++ = '"'; class_written = true; } cnt = (int)rb_ivar_count(obj); if (class_written) { *out->cur++ = ','; } if (0 == cnt && Qundef == clas) { // Might be something special like an Enumerable. if (Qtrue == rb_obj_is_kind_of(obj, oj_enumerable_class)) { out->cur--; oj_dump_custom_val(rb_funcall(obj, rb_intern("entries"), 0), depth, out, false); return; } } out->depth = depth + 1; rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (rb_obj_is_kind_of(obj, rb_eException)) { volatile VALUE rv; if (',' != *(out->cur - 1)) { *out->cur++ = ','; } // message assure_size(out, 2); fill_indent(out, d2); oj_dump_cstr("~mesg", 5, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("message"), 0, 0); oj_dump_custom_val(rv, d2, out, true); assure_size(out, size + 2); *out->cur++ = ','; // backtrace fill_indent(out, d2); oj_dump_cstr("~bt", 3, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0); oj_dump_custom_val(rv, d2, out, true); assure_size(out, 2); } out->depth = depth; fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { dump_obj_attrs(obj, clas, 0, depth, out); } *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; long id = oj_check_circular(a, out); if (0 > id) { oj_dump_nil(Qnil, depth, out, false); return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; assure_size(out, 2); if (0 == cnt) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_custom_val(rb_ary_entry(a, i), d2, out, true); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { VALUE ma = Qnil; VALUE v; char num_id[32]; int i; int d2 = depth + 1; int d3 = d2 + 1; size_t size = d2 * out->indent + d3 * out->indent + 3; const char *name; int cnt; size_t len; assure_size(out, size); if (clas == rb_cRange) { *out->cur++ = '"'; oj_dump_custom_val(rb_funcall(obj, oj_begin_id, 0), d3, out, false); assure_size(out, 3); *out->cur++ = '.'; *out->cur++ = '.'; if (Qtrue == rb_funcall(obj, oj_exclude_end_id, 0)) { *out->cur++ = '.'; } oj_dump_custom_val(rb_funcall(obj, oj_end_id, 0), d3, out, false); *out->cur++ = '"'; return; } *out->cur++ = '{'; fill_indent(out, d2); size = d3 * out->indent + 2; ma = rb_struct_s_members(clas); #ifdef RSTRUCT_LEN #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)NUM2LONG(RSTRUCT_LEN(obj)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)RSTRUCT_LEN(obj); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT #else // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. cnt = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0)); #endif for (i = 0; i < cnt; i++) { #ifdef RSTRUCT_LEN v = RSTRUCT_GET(obj, i); #else v = rb_struct_aref(obj, INT2FIX(i)); #endif if (ma != Qnil) { volatile VALUE s = rb_sym2str(rb_ary_entry(ma, i)); name = RSTRING_PTR(s); len = (int)RSTRING_LEN(s); } else { len = snprintf(num_id, sizeof(num_id), "%d", i); name = num_id; } assure_size(out, size + len + 3); fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, name, len); out->cur += len; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_custom_val(v, d3, out, true); *out->cur++ = ','; } out->cur--; *out->cur++ = '}'; *out->cur = '\0'; } } static void dump_data(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { dump_obj_attrs(obj, clas, id, depth, out); } } static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) { if (NULL != out->opts->create_id) { dump_obj_str(obj, depth, out); } else { dump_obj_as_str(obj, depth, out); } } static void dump_complex(VALUE obj, int depth, Out out, bool as_ok) { complex_dump(obj, depth, out); } static void dump_rational(VALUE obj, int depth, Out out, bool as_ok) { rational_dump(obj, depth, out); } static DumpFunc custom_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, oj_dump_class, // RUBY_T_CLASS = 0x02, oj_dump_class, // RUBY_T_MODULE = 0x03, oj_dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, dump_regexp, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, dump_struct, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_data, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, dump_complex, // RUBY_T_COMPLEX = 0x0e, dump_rational, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_custom_val(VALUE obj, int depth, Out out, bool as_ok) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = custom_funcs[type]; if (NULL != f) { f(obj, depth, out, true); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } ///// load functions ///// static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { const char * key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); volatile VALUE rkey = kval->key_val; if (Qundef == rkey && Yes == pi->options.create_ok && NULL != pi->options.create_id && *pi->options.create_id == *key && (int)pi->options.create_id_len == klen && 0 == strncmp(pi->options.create_id, key, klen)) { parent->clas = oj_name2class(pi, str, len, false, rb_eArgError); if (2 == klen && '^' == *key && 'o' == key[1]) { if (Qundef != parent->clas) { if (!oj_code_has(codes, parent->clas, false)) { parent->val = rb_obj_alloc(parent->clas); } } } } else { volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str); //volatile VALUE rstr = rb_utf8_str_new(str, len); if (Qundef == rkey) { if (Yes == pi->options.sym_key) { rkey = ID2SYM(rb_intern3(key, klen, oj_utf8_encoding)); } else { rkey = rb_utf8_str_new(key, klen); } } if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rstr = rb_funcall(clas, oj_json_create_id, 1, rstr); } } switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, rstr); break; case T_HASH: if (4 == parent->klen && NULL != parent->key && rb_cTime == parent->clas && 0 == strncmp("time", parent->key, 4)) { if (Qnil == (parent->val = oj_parse_xml_time(str, (int)len))) { parent->val = rb_funcall(rb_cTime, rb_intern("parse"), 1, rb_str_new(str, len)); } } else { rb_hash_aset(parent->val, rkey, rstr); } break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rstr); } } } static void end_hash(struct _parseInfo *pi) { Val parent = stack_peek(&pi->stack); if (Qundef != parent->clas && parent->clas != rb_obj_class(parent->val)) { volatile VALUE obj = oj_code_load(codes, parent->clas, parent->val); if (Qnil != obj) { parent->val = obj; } else { parent->val = rb_funcall(parent->clas, oj_json_create_id, 1, parent->val); } parent->clas = Qundef; } if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static void hash_set_num(struct _parseInfo *pi, Val kval, NumInfo ni) { Val parent = stack_peek(&pi->stack); volatile VALUE rval = oj_num_as_value(ni); switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, rval); break; case T_HASH: if (4 == parent->klen && NULL != parent->key && rb_cTime == parent->clas && 0 != ni->div && 0 == strncmp("time", parent->key, 4)) { int64_t nsec = ni->num * 1000000000LL / ni->div; if (ni->neg) { ni->i = -ni->i; if (0 < nsec) { ni->i--; nsec = 1000000000LL - nsec; } } if (86400 == ni->exp) { // UTC time parent->val = rb_time_nano_new(ni->i, (long)nsec); // Since the ruby C routines always create local time, the // offset and then a conversion to UTC keeps makes the time // match the expected value. parent->val = rb_funcall2(parent->val, oj_utc_id, 0, 0); } else if (ni->has_exp) { int64_t t = (int64_t)(ni->i + ni->exp); struct _timeInfo ti; VALUE args[8]; sec_as_time(t, &ti); args[0] = LONG2NUM(ti.year); args[1] = LONG2NUM(ti.mon); args[2] = LONG2NUM(ti.day); args[3] = LONG2NUM(ti.hour); args[4] = LONG2NUM(ti.min); args[5] = rb_float_new((double)ti.sec + ((double)nsec + 0.5) / 1000000000.0); args[6] = LONG2NUM(ni->exp); parent->val = rb_funcall2(rb_cTime, oj_new_id, 7, args); } else { parent->val = rb_time_nano_new(ni->i, (long)nsec); } rval = parent->val; } else { rb_hash_aset(parent->val, oj_calc_hash_key(pi, kval), rval); } break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rval); } } static void hash_set_value(ParseInfo pi, Val kval, VALUE value) { Val parent = stack_peek(&pi->stack); switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, value); break; case T_HASH: rb_hash_aset(parent->val, oj_calc_hash_key(pi, kval), value); break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value); } } static void array_append_num(ParseInfo pi, NumInfo ni) { Val parent = stack_peek(&pi->stack); volatile VALUE rval = oj_num_as_value(ni); rb_ary_push(parent->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval); } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_utf8_str_new(str, len); if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr)); return; } } rb_ary_push(stack_peek(&pi->stack)->val, rstr); if (Yes == pi->options.trace) { oj_trace_parse_call("append_string", pi, __FILE__, __LINE__, rstr); } } void oj_set_custom_callbacks(ParseInfo pi) { oj_set_compat_callbacks(pi); pi->hash_set_cstr = hash_set_cstr; pi->end_hash = end_hash; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; } VALUE oj_custom_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; oj_set_custom_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, false); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; oj_set_custom_callbacks(&pi); pi.end_hash = end_hash; return oj_pi_parse(argc, argv, &pi, json, len, false); } oj-3.13.9/ext/oj/val_stack.h0000644000004100000410000000737614136373754015631 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_VAL_STACK_H #define OJ_VAL_STACK_H #include #include "odd.h" #include "ruby.h" #ifdef HAVE_PTHREAD_MUTEX_INIT #include #endif #define STACK_INC 64 typedef enum { NEXT_NONE = 0, NEXT_ARRAY_NEW = 'a', NEXT_ARRAY_ELEMENT = 'e', NEXT_ARRAY_COMMA = ',', NEXT_HASH_NEW = 'h', NEXT_HASH_KEY = 'k', NEXT_HASH_COLON = ':', NEXT_HASH_VALUE = 'v', NEXT_HASH_COMMA = 'n', } ValNext; typedef struct _val { volatile VALUE val; const char * key; char karray[32]; volatile VALUE key_val; const char * classname; VALUE clas; OddArgs odd_args; uint16_t klen; uint16_t clen; char next; // ValNext char k1; // first original character in the key char kalloc; } * Val; typedef struct _valStack { struct _val base[STACK_INC]; Val head; // current stack Val end; // stack end Val tail; // pointer to one past last element name on stack #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_t mutex; #else VALUE mutex; #endif } * ValStack; extern VALUE oj_stack_init(ValStack stack); inline static int stack_empty(ValStack stack) { return (stack->head == stack->tail); } inline static void stack_cleanup(ValStack stack) { if (stack->base != stack->head) { xfree(stack->head); stack->head = NULL; } } inline static void stack_push(ValStack stack, VALUE val, ValNext next) { if (stack->end <= stack->tail) { size_t len = stack->end - stack->head; size_t toff = stack->tail - stack->head; Val head = stack->head; // A realloc can trigger a GC so make sure it happens outside the lock // but lock before changing pointers. if (stack->base == stack->head) { head = ALLOC_N(struct _val, len + STACK_INC); memcpy(head, stack->base, sizeof(struct _val) * len); } else { REALLOC_N(head, struct _val, len + STACK_INC); } #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_lock(&stack->mutex); #else rb_mutex_lock(stack->mutex); #endif stack->head = head; stack->tail = stack->head + toff; stack->end = stack->head + len + STACK_INC; #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_unlock(&stack->mutex); #else rb_mutex_unlock(stack->mutex); #endif } stack->tail->val = val; stack->tail->next = next; stack->tail->classname = NULL; stack->tail->clas = Qundef; stack->tail->odd_args = NULL; stack->tail->key = 0; stack->tail->key_val = Qundef; stack->tail->clen = 0; stack->tail->klen = 0; stack->tail->kalloc = 0; stack->tail++; } inline static size_t stack_size(ValStack stack) { return stack->tail - stack->head; } inline static Val stack_peek(ValStack stack) { if (stack->head < stack->tail) { return stack->tail - 1; } return 0; } inline static Val stack_peek_up(ValStack stack) { if (stack->head < stack->tail - 1) { return stack->tail - 2; } return 0; } inline static Val stack_prev(ValStack stack) { return stack->tail; } inline static VALUE stack_head_val(ValStack stack) { if (Qundef != stack->head->val) { return stack->head->val; } return Qnil; } inline static Val stack_pop(ValStack stack) { if (stack->head < stack->tail) { stack->tail--; return stack->tail; } return 0; } extern const char *oj_stack_next_string(ValNext n); #endif /* OJ_VAL_STACK_H */ oj-3.13.9/ext/oj/odd.h0000644000004100000410000000235714136373754014422 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_ODD_H #define OJ_ODD_H #include #include "ruby.h" #define MAX_ODD_ARGS 10 typedef VALUE (*AttrGetFunc)(VALUE obj); typedef struct _odd { const char *classname; size_t clen; VALUE clas; // Ruby class or module VALUE create_obj; ID create_op; int attr_cnt; bool is_module; bool raw; const char *attr_names[MAX_ODD_ARGS]; // NULL terminated attr names ID attrs[MAX_ODD_ARGS]; // 0 terminated attr IDs AttrGetFunc attrFuncs[MAX_ODD_ARGS]; } * Odd; typedef struct _oddArgs { Odd odd; VALUE args[MAX_ODD_ARGS]; } * OddArgs; extern void oj_odd_init(void); extern Odd oj_get_odd(VALUE clas); extern Odd oj_get_oddc(const char *classname, size_t len); extern OddArgs oj_odd_alloc_args(Odd odd); extern void oj_odd_free(OddArgs args); extern int oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value); extern void oj_reg_odd(VALUE clas, VALUE create_object, VALUE create_method, int mcnt, VALUE *members, bool raw); #endif /* OJ_ODD_H */ oj-3.13.9/ext/oj/object.c0000644000004100000410000005724214136373754015120 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #include "encode.h" #include "err.h" #include "intern.h" #include "odd.h" #include "oj.h" #include "parse.h" #include "resolve.h" #include "trace.h" #include "util.h" inline static long read_long(const char *str, size_t len) { long n = 0; for (; 0 < len; str++, len--) { if ('0' <= *str && *str <= '9') { n = n * 10 + (*str - '0'); } else { return -1; } } return n; } static VALUE calc_hash_key(ParseInfo pi, Val kval, char k1) { volatile VALUE rkey; if (':' == k1) { return ID2SYM(rb_intern3(kval->key + 1, kval->klen - 1, oj_utf8_encoding)); } if (Yes == pi->options.sym_key) { return ID2SYM(rb_intern3(kval->key, kval->klen, oj_utf8_encoding)); } #if HAVE_RB_ENC_INTERNED_STR rkey = rb_enc_interned_str(kval->key, kval->klen, oj_utf8_encoding); #else rkey = rb_utf8_str_new(kval->key, kval->klen); OBJ_FREEZE(rkey); #endif return rkey; } static VALUE str_to_value(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = Qnil; if (':' == *orig && 0 < len) { rstr = ID2SYM(rb_intern3(str + 1, len - 1, oj_utf8_encoding)); } else if (pi->circ_array && 3 <= len && '^' == *orig && 'r' == orig[1]) { long i = read_long(str + 2, len - 2); if (0 > i) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a valid ID number"); return Qnil; } rstr = oj_circ_array_get(pi->circ_array, i); } else { rstr = rb_utf8_str_new(str, len); } return rstr; } // The much faster approach (4x faster) static int parse_num(const char *str, const char *end, int cnt) { int n = 0; char c; int i; for (i = cnt; 0 < i; i--, str++) { c = *str; if (end <= str || c < '0' || '9' < c) { return -1; } n = n * 10 + (c - '0'); } return n; } VALUE oj_parse_xml_time(const char *str, int len) { VALUE args[8]; const char *end = str + len; int n; // year if (0 > (n = parse_num(str, end, 4))) { return Qnil; } str += 4; args[0] = LONG2NUM(n); if ('-' != *str++) { return Qnil; } // month if (0 > (n = parse_num(str, end, 2))) { return Qnil; } str += 2; args[1] = LONG2NUM(n); if ('-' != *str++) { return Qnil; } // day if (0 > (n = parse_num(str, end, 2))) { return Qnil; } str += 2; args[2] = LONG2NUM(n); if ('T' != *str++) { return Qnil; } // hour if (0 > (n = parse_num(str, end, 2))) { return Qnil; } str += 2; args[3] = LONG2NUM(n); if (':' != *str++) { return Qnil; } // minute if (0 > (n = parse_num(str, end, 2))) { return Qnil; } str += 2; args[4] = LONG2NUM(n); if (':' != *str++) { return Qnil; } // second if (0 > (n = parse_num(str, end, 2))) { return Qnil; } str += 2; if (str == end) { args[5] = LONG2NUM(n); args[6] = LONG2NUM(0); } else { char c = *str++; if ('.' == c) { long long nsec = 0; for (; str < end; str++) { c = *str; if (c < '0' || '9' < c) { str++; break; } nsec = nsec * 10 + (c - '0'); } args[5] = rb_float_new((double)n + ((double)nsec + 0.5) / 1000000000.0); } else { args[5] = rb_ll2inum(n); } if (end < str) { args[6] = LONG2NUM(0); } else { if ('Z' == c) { return rb_funcall2(rb_cTime, oj_utc_id, 6, args); } else if ('+' == c) { int hr = parse_num(str, end, 2); int min; str += 2; if (0 > hr || ':' != *str++) { return Qnil; } min = parse_num(str, end, 2); if (0 > min) { return Qnil; } args[6] = LONG2NUM(hr * 3600 + min * 60); } else if ('-' == c) { int hr = parse_num(str, end, 2); int min; str += 2; if (0 > hr || ':' != *str++) { return Qnil; } min = parse_num(str, end, 2); if (0 > min) { return Qnil; } args[6] = LONG2NUM(-(hr * 3600 + min * 60)); } else { args[6] = LONG2NUM(0); } } } return rb_funcall2(rb_cTime, oj_new_id, 7, args); } static int hat_cstr(ParseInfo pi, Val parent, Val kval, const char *str, size_t len) { const char *key = kval->key; int klen = kval->klen; if (2 == klen) { switch (key[1]) { case 'o': // object { // name2class sets an error if the class is not found or created VALUE clas = oj_name2class(pi, str, len, Yes == pi->options.auto_define, rb_eArgError); if (Qundef != clas) { parent->val = rb_obj_alloc(clas); } } break; case 'O': // odd object { Odd odd = oj_get_oddc(str, len); if (0 == odd) { return 0; } parent->val = odd->clas; parent->odd_args = oj_odd_alloc_args(odd); break; } case 'm': parent->val = ID2SYM(rb_intern3(str + 1, len - 1, oj_utf8_encoding)); break; case 's': parent->val = rb_utf8_str_new(str, len); break; case 'c': // class { VALUE clas = oj_name2class(pi, str, len, Yes == pi->options.auto_define, rb_eArgError); if (Qundef == clas) { return 0; } else { parent->val = clas; } break; } case 't': // time parent->val = oj_parse_xml_time(str, (int)len); break; default: return 0; break; } return 1; // handled } return 0; } static int hat_num(ParseInfo pi, Val parent, Val kval, NumInfo ni) { if (2 == kval->klen) { switch (kval->key[1]) { case 't': // time as a float if (0 == ni->div || 9 < ni->di) { rb_raise(rb_eArgError, "Invalid time decimal representation."); // parent->val = rb_time_nano_new(0, 0); } else { int64_t nsec = ni->num * 1000000000LL / ni->div; if (ni->neg) { ni->i = -ni->i; if (0 < nsec) { ni->i--; nsec = 1000000000LL - nsec; } } if (86400 == ni->exp) { // UTC time parent->val = rb_time_nano_new(ni->i, (long)nsec); // Since the ruby C routines always create local time, the // offset and then a conversion to UTC keeps makes the time // match the expected value. parent->val = rb_funcall2(parent->val, oj_utc_id, 0, 0); } else if (ni->has_exp) { int64_t t = (int64_t)(ni->i + ni->exp); struct _timeInfo ti; VALUE args[8]; sec_as_time(t, &ti); args[0] = LONG2NUM((long)(ti.year)); args[1] = LONG2NUM(ti.mon); args[2] = LONG2NUM(ti.day); args[3] = LONG2NUM(ti.hour); args[4] = LONG2NUM(ti.min); args[5] = rb_float_new((double)ti.sec + ((double)nsec + 0.5) / 1000000000.0); args[6] = LONG2NUM(ni->exp); parent->val = rb_funcall2(rb_cTime, oj_new_id, 7, args); } else { parent->val = rb_time_nano_new(ni->i, (long)nsec); } } break; case 'i': // circular index if (!ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum if (Qnil == parent->val) { parent->val = rb_hash_new(); } oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { return 0; } break; default: return 0; break; } return 1; // handled } return 0; } static int hat_value(ParseInfo pi, Val parent, const char *key, size_t klen, volatile VALUE value) { if (T_ARRAY == rb_type(value)) { int len = (int)RARRAY_LEN(value); if (2 == klen && 'u' == key[1]) { volatile VALUE sc; volatile VALUE e1; int slen; if (0 == len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); return 1; } e1 = *RARRAY_PTR(value); // check for anonymous Struct if (T_ARRAY == rb_type(e1)) { VALUE args[1024]; volatile VALUE rstr; int i, cnt = (int)RARRAY_LEN(e1); for (i = 0; i < cnt; i++) { rstr = rb_ary_entry(e1, i); args[i] = rb_funcall(rstr, oj_to_sym_id, 0); } sc = rb_funcall2(rb_cStruct, oj_new_id, cnt, args); } else { // If struct is not defined then we let this fail and raise an exception. sc = oj_name2struct(pi, *RARRAY_PTR(value), rb_eArgError); } // Create a properly initialized struct instance without calling the initialize method. parent->val = rb_obj_alloc(sc); // If the JSON array has more entries than the struct class allows, we record an error. #ifdef RSTRUCT_LEN #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT slen = (int)NUM2LONG(RSTRUCT_LEN(parent->val)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT slen = (int)RSTRUCT_LEN(parent->val); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT #else slen = FIX2INT(rb_funcall2(parent->val, oj_length_id, 0, 0)); #endif // MRI >= 1.9 if (len - 1 > slen) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); } else { int i; for (i = 0; i < len - 1; i++) { rb_struct_aset(parent->val, INT2FIX(i), RARRAY_PTR(value)[i + 1]); } } return 1; } else if (3 <= klen && '#' == key[1]) { volatile VALUE *a; if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return 1; } parent->val = rb_hash_new(); a = RARRAY_PTR(value); rb_hash_aset(parent->val, *a, a[1]); return 1; } } return 0; } void oj_set_obj_ivar(Val parent, Val kval, VALUE value) { rb_ivar_set(parent->val, oj_attr_intern(kval->key, kval->klen), value); } static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { const char * key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); volatile VALUE rval = Qnil; WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = NULL; // make sure it is NULL in case not odd if ('^' != *key || !hat_cstr(pi, parent, kval, str, len)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), str_to_value(pi, str, len, orig)); break; case T_STRING: rval = str_to_value(pi, str, len, orig); if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, rval); } else { oj_set_obj_ivar(parent, kval, rval); } break; case T_OBJECT: rval = str_to_value(pi, str, len, orig); oj_set_obj_ivar(parent, kval, rval); break; case T_CLASS: if (NULL == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else { rval = str_to_value(pi, str, len, orig); if (0 != oj_odd_set_arg(parent->odd_args, kval->key, kval->klen, rval)) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rval); } } static void hash_set_num(ParseInfo pi, Val kval, NumInfo ni) { const char * key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); volatile VALUE rval = Qnil; WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = NULL; // make sure it is NULL in case not odd if ('^' != *key || !hat_num(pi, parent, kval, ni)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rval = oj_num_as_value(ni); rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), rval); break; case T_OBJECT: if (2 == klen && '^' == *key && 'i' == key[1] && !ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { rval = oj_num_as_value(ni); oj_set_obj_ivar(parent, kval, rval); } break; case T_CLASS: if (NULL == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else { rval = oj_num_as_value(ni); if (0 != oj_odd_set_arg(parent->odd_args, key, klen, rval)) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } if (Yes == pi->options.trace) { oj_trace_parse_call("add_number", pi, __FILE__, __LINE__, rval); } } static void hash_set_value(ParseInfo pi, Val kval, VALUE value) { const char *key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = NULL; // make sure it is NULL in case not odd if ('^' != *key || !hat_value(pi, parent, key, klen, value)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: if (rb_cHash != rb_obj_class(parent->val)) { if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, value); } else { oj_set_obj_ivar(parent, kval, value); } } else { if (3 <= klen && '^' == *key && '#' == key[1] && T_ARRAY == rb_type(value)) { long len = RARRAY_LEN(value); volatile VALUE *a = RARRAY_PTR(value); if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return; } rb_hash_aset(parent->val, *a, a[1]); } else { rb_hash_aset(parent->val, calc_hash_key(pi, kval, parent->k1), value); } } break; case T_ARRAY: if (4 == klen && 's' == *key && 'e' == key[1] && 'l' == key[2] && 'f' == key[3]) { rb_funcall(parent->val, oj_replace_id, 1, value); } else { oj_set_obj_ivar(parent, kval, value); } break; case T_STRING: // for subclassed strings case T_OBJECT: oj_set_obj_ivar(parent, kval, value); break; case T_MODULE: case T_CLASS: if (NULL == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, value)) { char buf[256]; if ((int)sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } if (Yes == pi->options.trace) { oj_trace_parse_call("add_value", pi, __FILE__, __LINE__, value); } } static VALUE start_hash(ParseInfo pi) { if (Yes == pi->options.trace) { oj_trace_parse_in("start_hash", pi, __FILE__, __LINE__); } return Qnil; } static void end_hash(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (Qnil == parent->val) { parent->val = rb_hash_new(); } else if (NULL != parent->odd_args) { OddArgs oa = parent->odd_args; parent->val = rb_funcall2(oa->odd->create_obj, oa->odd->create_op, oa->odd->attr_cnt, oa->args); oj_odd_free(oa); parent->odd_args = NULL; } if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rval = Qnil; // orig lets us know whether the string was ^r1 or \u005er1 if (3 <= len && 0 != pi->circ_array && '^' == orig[0] && 0 == rb_array_len(stack_peek(&pi->stack)->val)) { if ('i' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { oj_circ_array_set(pi->circ_array, stack_peek(&pi->stack)->val, i); return; } } else if ('r' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { rb_ary_push(stack_peek(&pi->stack)->val, oj_circ_array_get(pi->circ_array, i)); return; } } } rval = str_to_value(pi, str, len, orig); rb_ary_push(stack_peek(&pi->stack)->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("append_string", pi, __FILE__, __LINE__, rval); } } static void array_append_num(ParseInfo pi, NumInfo ni) { volatile VALUE rval = oj_num_as_value(ni); rb_ary_push(stack_peek(&pi->stack)->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval); } } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { pi->stack.head->val = str_to_value(pi, str, len, orig); if (Yes == pi->options.trace) { oj_trace_parse_call("add_string", pi, __FILE__, __LINE__, pi->stack.head->val); } } static void add_num(ParseInfo pi, NumInfo ni) { pi->stack.head->val = oj_num_as_value(ni); if (Yes == pi->options.trace) { oj_trace_parse_call("add_num", pi, __FILE__, __LINE__, pi->stack.head->val); } } void oj_set_object_callbacks(ParseInfo pi) { oj_set_strict_callbacks(pi); pi->end_hash = end_hash; pi->start_hash = start_hash; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->add_cstr = add_cstr; pi->add_num = add_num; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; } VALUE oj_object_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_object_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, 1); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; oj_set_strict_callbacks(&pi); pi.end_hash = end_hash; pi.start_hash = start_hash; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.hash_set_value = hash_set_value; pi.add_cstr = add_cstr; pi.add_num = add_num; pi.array_append_cstr = array_append_cstr; pi.array_append_num = array_append_num; return oj_pi_parse(argc, argv, &pi, json, len, 1); } oj-3.13.9/ext/oj/oj.c0000644000004100000410000024232714136373754014262 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "oj.h" #include #include #include #include #include #include #include #include "dump.h" #include "encode.h" #include "intern.h" #include "odd.h" #include "parse.h" #include "rails.h" typedef struct _yesNoOpt { VALUE sym; char *attr; } * YesNoOpt; void Init_oj(); VALUE Oj = Qnil; ID oj_add_value_id; ID oj_array_append_id; ID oj_array_end_id; ID oj_array_start_id; ID oj_as_json_id; ID oj_begin_id; ID oj_bigdecimal_id; ID oj_end_id; ID oj_exclude_end_id; ID oj_error_id; ID oj_file_id; ID oj_fileno_id; ID oj_ftype_id; ID oj_hash_end_id; ID oj_hash_key_id; ID oj_hash_set_id; ID oj_hash_start_id; ID oj_iconv_id; ID oj_instance_variables_id; ID oj_json_create_id; ID oj_length_id; ID oj_new_id; ID oj_parse_id; ID oj_pos_id; ID oj_raw_json_id; ID oj_read_id; ID oj_readpartial_id; ID oj_replace_id; ID oj_stat_id; ID oj_string_id; ID oj_to_h_id; ID oj_to_hash_id; ID oj_to_json_id; ID oj_to_s_id; ID oj_to_sym_id; ID oj_to_time_id; ID oj_tv_nsec_id; ID oj_tv_sec_id; ID oj_tv_usec_id; ID oj_utc_id; ID oj_utc_offset_id; ID oj_utcq_id; ID oj_write_id; VALUE oj_bag_class; VALUE oj_bigdecimal_class; VALUE oj_cstack_class; VALUE oj_date_class; VALUE oj_datetime_class; VALUE oj_enumerable_class; VALUE oj_parse_error_class; VALUE oj_stream_writer_class; VALUE oj_string_writer_class; VALUE oj_stringio_class; VALUE oj_struct_class; VALUE oj_slash_string; VALUE oj_allow_nan_sym; VALUE oj_array_class_sym; VALUE oj_create_additions_sym; VALUE oj_decimal_class_sym; VALUE oj_hash_class_sym; VALUE oj_indent_sym; VALUE oj_object_class_sym; VALUE oj_quirks_mode_sym; VALUE oj_safe_sym; VALUE oj_symbolize_names_sym; VALUE oj_trace_sym; static VALUE allow_blank_sym; static VALUE allow_gc_sym; static VALUE allow_invalid_unicode_sym; static VALUE ascii_sym; static VALUE auto_define_sym; static VALUE auto_sym; static VALUE bigdecimal_as_decimal_sym; static VALUE bigdecimal_load_sym; static VALUE bigdecimal_sym; static VALUE cache_keys_sym; static VALUE cache_str_sym; static VALUE cache_string_sym; static VALUE circular_sym; static VALUE class_cache_sym; static VALUE compat_bigdecimal_sym; static VALUE compat_sym; static VALUE create_id_sym; static VALUE custom_sym; static VALUE empty_string_sym; static VALUE escape_mode_sym; static VALUE integer_range_sym; static VALUE fast_sym; static VALUE float_prec_sym; static VALUE float_sym; static VALUE huge_sym; static VALUE ignore_sym; static VALUE ignore_under_sym; static VALUE json_sym; static VALUE match_string_sym; static VALUE mode_sym; static VALUE nan_sym; static VALUE newline_sym; static VALUE nilnil_sym; static VALUE null_sym; static VALUE object_sym; static VALUE omit_nil_sym; static VALUE rails_sym; static VALUE raise_sym; static VALUE ruby_sym; static VALUE sec_prec_sym; static VALUE strict_sym; static VALUE symbol_keys_sym; static VALUE time_format_sym; static VALUE unicode_xss_sym; static VALUE unix_sym; static VALUE unix_zone_sym; static VALUE use_as_json_sym; static VALUE use_raw_json_sym; static VALUE use_to_hash_sym; static VALUE use_to_json_sym; static VALUE wab_sym; static VALUE word_sym; static VALUE xmlschema_sym; static VALUE xss_safe_sym; rb_encoding *oj_utf8_encoding = 0; #ifdef HAVE_PTHREAD_MUTEX_INIT pthread_mutex_t oj_cache_mutex; #else VALUE oj_cache_mutex = Qnil; #endif extern void oj_parser_init(); const char oj_json_class[] = "json_class"; struct _options oj_default_options = { 0, // indent No, // circular No, // auto_define No, // sym_key JSONEsc, // escape_mode ObjectMode, // mode Yes, // class_cache UnixTime, // time_format NotSet, // bigdec_as_num AutoDec, // bigdec_load false, // compat_bigdec No, // to_hash No, // to_json No, // as_json No, // raw_json No, // nilnil Yes, // empty_string Yes, // allow_gc Yes, // quirks_mode No, // allow_invalid No, // create_ok Yes, // allow_nan No, // trace No, // safe false, // sec_prec_set No, // ignore_under Yes, // cache_keys 0, // cache_str 0, // int_range_min 0, // int_range_max oj_json_class, // create_id 10, // create_id_len 9, // sec_prec 16, // float_prec "%0.15g", // float_fmt Qnil, // hash_class Qnil, // array_class { // dump_opts false, // use "", // indent "", // before_sep "", // after_sep "", // hash_nl "", // array_nl 0, // indent_size 0, // before_size 0, // after_size 0, // hash_size 0, // array_size AutoNan, // nan_dump false, // omit_nil MAX_DEPTH, // max_depth }, { // str_rx NULL, // head NULL, // tail {'\0'}, // err }, NULL, // ignore }; /* Document-method: default_options() * call-seq: default_options() * * Returns the default load and dump options as a Hash. The options are * - *:indent* [_Fixnum_|_String_|_nil_] number of spaces to indent each element in an JSON *document, zero or nil is no newline between JSON elements, negative indicates no newline between *top level JSON elements in a stream, a String indicates the string should be used for indentation * - *:circular* [_Boolean_|_nil_] support circular references while dumping as well as shared *references * - *:auto_define* [_Boolean_|_nil_] automatically define classes if they do not exist * - *:symbol_keys* [_Boolean_|_nil_] use symbols instead of strings for hash keys * - *:escape_mode* [_:newline_|_:json_|_:xss_safe_|_:ascii_|_unicode_xss_|_nil_] determines the *characters to escape * - *:class_cache* [_Boolean_|_nil_] cache classes for faster parsing (if dynamically modifying *classes or reloading classes then don't use this) * - *:mode* [_:object_|_:strict_|_:compat_|_:null_|_:custom_|_:rails_|_:wab_] load and dump modes *to use for JSON * - *:time_format* [_:unix_|_:unix_zone_|_:xmlschema_|_:ruby_] time format when dumping * - *:bigdecimal_as_decimal* [_Boolean_|_nil_] dump BigDecimal as a decimal number or as a String * - *:bigdecimal_load* [_:bigdecimal_|_:float_|_:auto_|_:fast_] load decimals as BigDecimal instead *of as a Float. :auto pick the most precise for the number of digits. :float should be the same as *ruby. :fast may require rounding but is must faster. * - *:compat_bigdecimal* [_true_|_false_] load decimals as BigDecimal instead of as a Float when in *compat or rails mode. * - *:create_id* [_String_|_nil_] create id for json compatible object encoding, default is *'json_class' * - *:create_additions* [_Boolean_|_nil_] if true allow creation of instances using create_id on *load. * - *:second_precision* [_Fixnum_|_nil_] number of digits after the decimal when dumping the *seconds portion of time * - *:float_precision* [_Fixnum_|_nil_] number of digits of precision when dumping floats, 0 *indicates use Ruby * - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false * - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false * - *:use_raw_json* [_Boolean_|_nil_] call raw_json() methods on dump, default is false * - *:nilnil* [_Boolean_|_nil_] if true a nil input to load will return nil and not raise an *Exception * - *:empty_string* [_Boolean_|_nil_] if true an empty input will not raise an Exception * - *:allow_gc* [_Boolean_|_nil_] allow or prohibit GC during parsing, default is true (allow) * - *:quirks_mode* [_true,_|_false_|_nil_] Allow single JSON values instead of documents, default *is true (allow) * - *:allow_invalid_unicode* [_true,_|_false_|_nil_] Allow invalid unicode, default is false (don't *allow) * - *:allow_nan* [_true,_|_false_|_nil_] Allow Nan, Infinity, and -Infinity to be parsed, default *is true (allow) * - *:indent_str* [_String_|_nil_] String to use for indentation, overriding the indent option is *not nil * - *:space* [_String_|_nil_] String to use for the space after the colon in JSON object fields * - *:space_before* [_String_|_nil_] String to use before the colon separator in JSON object fields * - *:object_nl* [_String_|_nil_] String to use after a JSON object field value * - *:array_nl* [_String_|_nil_] String to use after a JSON array value * - *:nan* [_:null_|_:huge_|_:word_|_:raise_|_:auto_] how to dump Infinity and NaN. :null places a *null, :huge places a huge number, :word places Infinity or NaN, :raise raises and exception, :auto *uses default for each mode. * - *:hash_class* [_Class_|_nil_] Class to use instead of Hash on load, :object_class can also be *used * - *:array_class* [_Class_|_nil_] Class to use instead of Array on load * - *:omit_nil* [_true_|_false_] if true Hash and Object attributes with nil values are omitted * - *:ignore* [_nil_|_Array_] either nil or an Array of classes to ignore when dumping * - *:ignore_under* [_Boolean_] if true then attributes that start with _ are ignored when dumping in *object or custom mode. * - *:cache_keys* [_Boolean_] if true then hash keys are cached if less than 35 bytes. * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less than this are cached) * - *:integer_range* [_Range_] Dump integers outside range as strings. * - *:trace* [_true,_|_false_] Trace all load and dump calls, default is false (trace is off) * - *:safe* [_true,_|_false_] Safe mimic breaks JSON mimic to be safer, default is false (safe is *off) * * Return [_Hash_] all current option settings. */ static VALUE get_def_opts(VALUE self) { VALUE opts = rb_hash_new(); if (0 == oj_default_options.dump_opts.indent_size) { rb_hash_aset(opts, oj_indent_sym, INT2FIX(oj_default_options.indent)); } else { rb_hash_aset(opts, oj_indent_sym, rb_str_new2(oj_default_options.dump_opts.indent_str)); } rb_hash_aset(opts, sec_prec_sym, INT2FIX(oj_default_options.sec_prec)); rb_hash_aset(opts, circular_sym, (Yes == oj_default_options.circular) ? Qtrue : ((No == oj_default_options.circular) ? Qfalse : Qnil)); rb_hash_aset( opts, class_cache_sym, (Yes == oj_default_options.class_cache) ? Qtrue : ((No == oj_default_options.class_cache) ? Qfalse : Qnil)); rb_hash_aset( opts, auto_define_sym, (Yes == oj_default_options.auto_define) ? Qtrue : ((No == oj_default_options.auto_define) ? Qfalse : Qnil)); rb_hash_aset(opts, symbol_keys_sym, (Yes == oj_default_options.sym_key) ? Qtrue : ((No == oj_default_options.sym_key) ? Qfalse : Qnil)); rb_hash_aset( opts, bigdecimal_as_decimal_sym, (Yes == oj_default_options.bigdec_as_num) ? Qtrue : ((No == oj_default_options.bigdec_as_num) ? Qfalse : Qnil)); rb_hash_aset( opts, oj_create_additions_sym, (Yes == oj_default_options.create_ok) ? Qtrue : ((No == oj_default_options.create_ok) ? Qfalse : Qnil)); rb_hash_aset(opts, use_to_json_sym, (Yes == oj_default_options.to_json) ? Qtrue : ((No == oj_default_options.to_json) ? Qfalse : Qnil)); rb_hash_aset(opts, use_to_hash_sym, (Yes == oj_default_options.to_hash) ? Qtrue : ((No == oj_default_options.to_hash) ? Qfalse : Qnil)); rb_hash_aset(opts, use_as_json_sym, (Yes == oj_default_options.as_json) ? Qtrue : ((No == oj_default_options.as_json) ? Qfalse : Qnil)); rb_hash_aset(opts, use_raw_json_sym, (Yes == oj_default_options.raw_json) ? Qtrue : ((No == oj_default_options.raw_json) ? Qfalse : Qnil)); rb_hash_aset(opts, nilnil_sym, (Yes == oj_default_options.nilnil) ? Qtrue : ((No == oj_default_options.nilnil) ? Qfalse : Qnil)); rb_hash_aset( opts, empty_string_sym, (Yes == oj_default_options.empty_string) ? Qtrue : ((No == oj_default_options.empty_string) ? Qfalse : Qnil)); rb_hash_aset(opts, allow_gc_sym, (Yes == oj_default_options.allow_gc) ? Qtrue : ((No == oj_default_options.allow_gc) ? Qfalse : Qnil)); rb_hash_aset( opts, oj_quirks_mode_sym, (Yes == oj_default_options.quirks_mode) ? Qtrue : ((No == oj_default_options.quirks_mode) ? Qfalse : Qnil)); rb_hash_aset( opts, allow_invalid_unicode_sym, (Yes == oj_default_options.allow_invalid) ? Qtrue : ((No == oj_default_options.allow_invalid) ? Qfalse : Qnil)); rb_hash_aset( opts, oj_allow_nan_sym, (Yes == oj_default_options.allow_nan) ? Qtrue : ((No == oj_default_options.allow_nan) ? Qfalse : Qnil)); rb_hash_aset(opts, oj_trace_sym, (Yes == oj_default_options.trace) ? Qtrue : ((No == oj_default_options.trace) ? Qfalse : Qnil)); rb_hash_aset(opts, oj_safe_sym, (Yes == oj_default_options.safe) ? Qtrue : ((No == oj_default_options.safe) ? Qfalse : Qnil)); rb_hash_aset(opts, float_prec_sym, INT2FIX(oj_default_options.float_prec)); rb_hash_aset(opts, cache_str_sym, INT2FIX(oj_default_options.cache_str)); rb_hash_aset( opts, ignore_under_sym, (Yes == oj_default_options.ignore_under) ? Qtrue : ((No == oj_default_options.ignore_under) ? Qfalse : Qnil)); rb_hash_aset( opts, cache_keys_sym, (Yes == oj_default_options.cache_keys) ? Qtrue : ((No == oj_default_options.cache_keys) ? Qfalse : Qnil)); switch (oj_default_options.mode) { case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break; case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break; case NullMode: rb_hash_aset(opts, mode_sym, null_sym); break; case ObjectMode: rb_hash_aset(opts, mode_sym, object_sym); break; case CustomMode: rb_hash_aset(opts, mode_sym, custom_sym); break; case RailsMode: rb_hash_aset(opts, mode_sym, rails_sym); break; case WabMode: rb_hash_aset(opts, mode_sym, wab_sym); break; default: rb_hash_aset(opts, mode_sym, object_sym); break; } if (oj_default_options.int_range_max != 0 || oj_default_options.int_range_min != 0) { VALUE range = rb_obj_alloc(rb_cRange); VALUE min = LONG2FIX(oj_default_options.int_range_min); VALUE max = LONG2FIX(oj_default_options.int_range_max); rb_ivar_set(range, oj_begin_id, min); rb_ivar_set(range, oj_end_id, max); rb_hash_aset(opts, integer_range_sym, range); } else { rb_hash_aset(opts, integer_range_sym, Qnil); } switch (oj_default_options.escape_mode) { case NLEsc: rb_hash_aset(opts, escape_mode_sym, newline_sym); break; case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break; case XSSEsc: rb_hash_aset(opts, escape_mode_sym, xss_safe_sym); break; case ASCIIEsc: rb_hash_aset(opts, escape_mode_sym, ascii_sym); break; case JXEsc: rb_hash_aset(opts, escape_mode_sym, unicode_xss_sym); break; default: rb_hash_aset(opts, escape_mode_sym, json_sym); break; } switch (oj_default_options.time_format) { case XmlTime: rb_hash_aset(opts, time_format_sym, xmlschema_sym); break; case RubyTime: rb_hash_aset(opts, time_format_sym, ruby_sym); break; case UnixZTime: rb_hash_aset(opts, time_format_sym, unix_zone_sym); break; case UnixTime: default: rb_hash_aset(opts, time_format_sym, unix_sym); break; } switch (oj_default_options.bigdec_load) { case BigDec: rb_hash_aset(opts, bigdecimal_load_sym, bigdecimal_sym); break; case FloatDec: rb_hash_aset(opts, bigdecimal_load_sym, float_sym); break; case FastDec: rb_hash_aset(opts, bigdecimal_load_sym, fast_sym); break; case AutoDec: default: rb_hash_aset(opts, bigdecimal_load_sym, auto_sym); break; } rb_hash_aset(opts, compat_bigdecimal_sym, oj_default_options.compat_bigdec ? Qtrue : Qfalse); rb_hash_aset(opts, create_id_sym, (NULL == oj_default_options.create_id) ? Qnil : rb_str_new2(oj_default_options.create_id)); rb_hash_aset( opts, oj_space_sym, (0 == oj_default_options.dump_opts.after_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.after_sep)); rb_hash_aset( opts, oj_space_before_sym, (0 == oj_default_options.dump_opts.before_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.before_sep)); rb_hash_aset( opts, oj_object_nl_sym, (0 == oj_default_options.dump_opts.hash_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.hash_nl)); rb_hash_aset( opts, oj_array_nl_sym, (0 == oj_default_options.dump_opts.array_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.array_nl)); switch (oj_default_options.dump_opts.nan_dump) { case NullNan: rb_hash_aset(opts, nan_sym, null_sym); break; case RaiseNan: rb_hash_aset(opts, nan_sym, raise_sym); break; case WordNan: rb_hash_aset(opts, nan_sym, word_sym); break; case HugeNan: rb_hash_aset(opts, nan_sym, huge_sym); break; case AutoNan: default: rb_hash_aset(opts, nan_sym, auto_sym); break; } rb_hash_aset(opts, omit_nil_sym, oj_default_options.dump_opts.omit_nil ? Qtrue : Qfalse); rb_hash_aset(opts, oj_hash_class_sym, oj_default_options.hash_class); rb_hash_aset(opts, oj_array_class_sym, oj_default_options.array_class); if (NULL == oj_default_options.ignore) { rb_hash_aset(opts, ignore_sym, Qnil); } else { VALUE * vp; volatile VALUE a = rb_ary_new(); for (vp = oj_default_options.ignore; Qnil != *vp; vp++) { rb_ary_push(a, *vp); } rb_hash_aset(opts, ignore_sym, a); } return opts; } /* Document-method: default_options= * call-seq: default_options=(opts) * * Sets the default options for load and dump. * - *opts* [_Hash_] options to change * - *:indent* [_Fixnum_|_String_|_nil_] number of spaces to indent each element in a JSON *document or the String to use for indentation. * - :circular [_Boolean_|_nil_] support circular references while dumping. * - *:auto_define* [_Boolean_|_nil_] automatically define classes if they do not exist. * - *:symbol_keys* [_Boolean_|_nil_] convert hash keys to symbols. * - *:class_cache* [_Boolean_|_nil_] cache classes for faster parsing. * - *:escape* [_:newline_|_:json_|_:xss_safe_|_:ascii_|_unicode_xss_|_nil_] mode encodes all *high-bit characters as escaped sequences if :ascii, :json is standand UTF-8 JSON encoding, *:newline is the same as :json but newlines are not escaped, :unicode_xss allows unicode but *escapes &, <, and >, and any \u20xx characters along with some others, and :xss_safe escapes &, <, *and >, and some others. * - *:bigdecimal_as_decimal* [_Boolean_|_nil_] dump BigDecimal as a decimal number or as a *String. * - *:bigdecimal_load* [_:bigdecimal_|_:float_|_:auto_|_nil_] load decimals as BigDecimal instead *of as a Float. :auto pick the most precise for the number of digits. * - *:compat_bigdecimal* [_true_|_false_] load decimals as BigDecimal instead of as a Float in *compat mode. * - *:mode* [_:object_|_:strict_|_:compat_|_:null_|_:custom_|_:rails_|_:wab_] load and dump mode *to use for JSON :strict raises an exception when a non-supported Object is encountered. :compat *attempts to extract variable values from an Object using to_json() or to_hash() then it walks the *Object's variables if neither is found. The :object mode ignores to_hash() and to_json() methods *and encodes variables using code internal to the Oj gem. The :null mode ignores non-supported *Objects and replaces them with a null. The :custom mode honors all dump options. The :rails more *mimics rails and Active behavior. * - *:time_format* [_:unix_|_:xmlschema_|_:ruby_] time format when dumping in :compat mode :unix *decimal number denoting the number of seconds since 1/1/1970, :unix_zone decimal number denoting *the number of seconds since 1/1/1970 plus the utc_offset in the exponent, :xmlschema date-time *format taken from XML Schema as a String, :ruby Time.to_s formatted String. * - *:create_id* [_String_|_nil_] create id for json compatible object encoding * - *:create_additions* [_Boolean_|_nil_] if true allow creation of instances using create_id on *load. * - *:second_precision* [_Fixnum_|_nil_] number of digits after the decimal when dumping the *seconds portion of time. * - *:float_precision* [_Fixnum_|_nil_] number of digits of precision when dumping floats, 0 *indicates use Ruby. * - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false. * - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false. * - *:use_to_hash* [_Boolean_|_nil_] call to_hash() methods on dump, default is false. * - *:use_raw_json* [_Boolean_|_nil_] call raw_json() methods on dump, default is false. * - *:nilnil* [_Boolean_|_nil_] if true a nil input to load will return nil and not raise an *Exception. * - *:allow_gc* [_Boolean_|_nil_] allow or prohibit GC during parsing, default is true (allow). * - *:quirks_mode* [_Boolean_|_nil_] allow single JSON values instead of documents, default is *true (allow). * - *:allow_invalid_unicode* [_Boolean_|_nil_] allow invalid unicode, default is false (don't *allow). * - *:allow_nan* [_Boolean_|_nil_] allow Nan, Infinity, and -Infinity, default is true (allow). * - *:space* [_String_|_nil_] String to use for the space after the colon in JSON object fields. * - *:space_before* [_String_|_nil_] String to use before the colon separator in JSON object *fields. * - *:object_nl* [_String_|_nil_] String to use after a JSON object field value. * - *:array_nl* [_String_|_nil_] String to use after a JSON array value * - *:nan* [_:null_|_:huge_|_:word_|_:raise_] how to dump Infinity and NaN in null, strict, and *compat mode. :null places a null, :huge places a huge number, :word places Infinity or NaN, :raise *raises and exception, :auto uses default for each mode. * - *:hash_class* [_Class_|_nil_] Class to use instead of Hash on load, :object_class can also be *used. * - *:array_class* [_Class_|_nil_] Class to use instead of Array on load. * - *:omit_nil* [_true_|_false_] if true Hash and Object attributes with nil values are omitted. * - *:ignore* [_nil_|Array] either nil or an Array of classes to ignore when dumping * - *:ignore_under* [_Boolean_] if true then attributes that start with _ are ignored when *dumping in object or custom mode. * - *:cache_keys* [_Boolean_] if true then hash keys are cached * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less than this are cached) * - *:integer_range* [_Range_] Dump integers outside range as strings. * - *:trace* [_Boolean_] turn trace on or off. * - *:safe* [_Boolean_] turn safe mimic on or off. */ static VALUE set_def_opts(VALUE self, VALUE opts) { Check_Type(opts, T_HASH); oj_parse_options(opts, &oj_default_options); return Qnil; } bool oj_hash_has_key(VALUE hash, VALUE key) { if (Qundef == rb_hash_lookup2(hash, key, Qundef)) { return false; } return true; } bool set_yesno_options(VALUE key, VALUE value, Options copts) { struct _yesNoOpt ynos[] = {{circular_sym, &copts->circular}, {auto_define_sym, &copts->auto_define}, {symbol_keys_sym, &copts->sym_key}, {class_cache_sym, &copts->class_cache}, {bigdecimal_as_decimal_sym, &copts->bigdec_as_num}, {use_to_hash_sym, &copts->to_hash}, {use_to_json_sym, &copts->to_json}, {use_as_json_sym, &copts->as_json}, {use_raw_json_sym, &copts->raw_json}, {nilnil_sym, &copts->nilnil}, {allow_blank_sym, &copts->nilnil}, // same as nilnil {empty_string_sym, &copts->empty_string}, {allow_gc_sym, &copts->allow_gc}, {oj_quirks_mode_sym, &copts->quirks_mode}, {allow_invalid_unicode_sym, &copts->allow_invalid}, {oj_allow_nan_sym, &copts->allow_nan}, {oj_trace_sym, &copts->trace}, {oj_safe_sym, &copts->safe}, {ignore_under_sym, &copts->ignore_under}, {oj_create_additions_sym, &copts->create_ok}, {cache_keys_sym, &copts->cache_keys}, {Qnil, 0}}; YesNoOpt o; for (o = ynos; 0 != o->attr; o++) { if (key == o->sym) { if (Qnil == value) { *o->attr = NotSet; } else if (Qtrue == value) { *o->attr = Yes; } else if (Qfalse == value) { *o->attr = No; } else { rb_raise(rb_eArgError, "%s must be true, false, or nil.", rb_id2name(key)); } return true; } } return false; } static int parse_options_cb(VALUE k, VALUE v, VALUE opts) { Options copts = (Options)opts; size_t len; if (set_yesno_options(k, v, copts)) { return ST_CONTINUE; } if (oj_indent_sym == k) { switch (rb_type(v)) { case T_NIL: copts->dump_opts.indent_size = 0; *copts->dump_opts.indent_str = '\0'; copts->indent = 0; break; case T_FIXNUM: copts->dump_opts.indent_size = 0; *copts->dump_opts.indent_str = '\0'; copts->indent = FIX2INT(v); break; case T_STRING: if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "indent string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.indent_str)); } strcpy(copts->dump_opts.indent_str, StringValuePtr(v)); copts->dump_opts.indent_size = (uint8_t)len; copts->indent = 0; break; default: rb_raise(rb_eTypeError, "indent must be a Fixnum, String, or nil."); break; } } else if (float_prec_sym == k) { int n; #ifdef RUBY_INTEGER_UNIFICATION if (rb_cInteger != rb_obj_class(v)) { rb_raise(rb_eArgError, ":float_precision must be a Integer."); } #else if (T_FIXNUM != rb_type(v)) { rb_raise(rb_eArgError, ":float_precision must be a Fixnum."); } #endif n = FIX2INT(v); if (0 >= n) { *copts->float_fmt = '\0'; copts->float_prec = 0; } else { if (20 < n) { n = 20; } sprintf(copts->float_fmt, "%%0.%dg", n); copts->float_prec = n; } } else if (cache_str_sym == k || cache_string_sym == k) { int n; #ifdef RUBY_INTEGER_UNIFICATION if (rb_cInteger != rb_obj_class(v)) { rb_raise(rb_eArgError, ":cache_str must be a Integer."); } #else if (T_FIXNUM != rb_type(v)) { rb_raise(rb_eArgError, ":cache_str must be a Fixnum."); } #endif n = FIX2INT(v); if (0 >= n) { copts->cache_str = 0; } else { if (32 < n) { n = 32; } copts->cache_str = (char)n; } } else if (sec_prec_sym == k) { int n; #ifdef RUBY_INTEGER_UNIFICATION if (rb_cInteger != rb_obj_class(v)) { rb_raise(rb_eArgError, ":second_precision must be a Integer."); } #else if (T_FIXNUM != rb_type(v)) { rb_raise(rb_eArgError, ":second_precision must be a Fixnum."); } #endif n = NUM2INT(v); if (0 > n) { n = 0; copts->sec_prec_set = false; } else if (9 < n) { n = 9; copts->sec_prec_set = true; } else { copts->sec_prec_set = true; } copts->sec_prec = n; } else if (mode_sym == k) { if (wab_sym == v) { copts->mode = WabMode; } else if (object_sym == v) { copts->mode = ObjectMode; } else if (strict_sym == v) { copts->mode = StrictMode; } else if (compat_sym == v || json_sym == v) { copts->mode = CompatMode; } else if (null_sym == v) { copts->mode = NullMode; } else if (custom_sym == v) { copts->mode = CustomMode; } else if (rails_sym == v) { copts->mode = RailsMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, :null, :custom, :rails, or :wab."); } } else if (time_format_sym == k) { if (unix_sym == v) { copts->time_format = UnixTime; } else if (unix_zone_sym == v) { copts->time_format = UnixZTime; } else if (xmlschema_sym == v) { copts->time_format = XmlTime; } else if (ruby_sym == v) { copts->time_format = RubyTime; } else { rb_raise(rb_eArgError, ":time_format must be :unix, :unix_zone, :xmlschema, or :ruby."); } } else if (escape_mode_sym == k) { if (newline_sym == v) { copts->escape_mode = NLEsc; } else if (json_sym == v) { copts->escape_mode = JSONEsc; } else if (xss_safe_sym == v) { copts->escape_mode = XSSEsc; } else if (ascii_sym == v) { copts->escape_mode = ASCIIEsc; } else if (unicode_xss_sym == v) { copts->escape_mode = JXEsc; } else { rb_raise(rb_eArgError, ":encoding must be :newline, :json, :xss_safe, :unicode_xss, or :ascii."); } } else if (bigdecimal_load_sym == k) { if (Qnil == v) { return ST_CONTINUE; } if (bigdecimal_sym == v || Qtrue == v) { copts->bigdec_load = BigDec; } else if (float_sym == v) { copts->bigdec_load = FloatDec; } else if (fast_sym == v) { copts->bigdec_load = FastDec; } else if (auto_sym == v || Qfalse == v) { copts->bigdec_load = AutoDec; } else { rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto."); } } else if (compat_bigdecimal_sym == k) { if (Qnil == v) { return ST_CONTINUE; } copts->compat_bigdec = (Qtrue == v); } else if (oj_decimal_class_sym == k) { if (rb_cFloat == v) { copts->compat_bigdec = false; } else if (oj_bigdecimal_class == v) { copts->compat_bigdec = true; } else { rb_raise(rb_eArgError, ":decimal_class must be BigDecimal or Float."); } } else if (create_id_sym == k) { if (Qnil == v) { if (oj_json_class != oj_default_options.create_id && NULL != copts->create_id) { xfree((char *)oj_default_options.create_id); } copts->create_id = NULL; copts->create_id_len = 0; } else if (T_STRING == rb_type(v)) { const char *str = StringValuePtr(v); len = RSTRING_LEN(v); if (len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) { copts->create_id = ALLOC_N(char, len + 1); strcpy((char *)copts->create_id, str); copts->create_id_len = len; } } else { rb_raise(rb_eArgError, ":create_id must be string."); } } else if (oj_space_sym == k) { if (Qnil == v) { copts->dump_opts.after_size = 0; *copts->dump_opts.after_sep = '\0'; } else { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.after_sep) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "space string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.after_sep)); } strcpy(copts->dump_opts.after_sep, StringValuePtr(v)); copts->dump_opts.after_size = (uint8_t)len; } } else if (oj_space_before_sym == k) { if (Qnil == v) { copts->dump_opts.before_size = 0; *copts->dump_opts.before_sep = '\0'; } else { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.before_sep) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "sapce_before string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.before_sep)); } strcpy(copts->dump_opts.before_sep, StringValuePtr(v)); copts->dump_opts.before_size = (uint8_t)len; } } else if (oj_object_nl_sym == k) { if (Qnil == v) { copts->dump_opts.hash_size = 0; *copts->dump_opts.hash_nl = '\0'; } else { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.hash_nl) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "object_nl string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.hash_nl)); } strcpy(copts->dump_opts.hash_nl, StringValuePtr(v)); copts->dump_opts.hash_size = (uint8_t)len; } } else if (oj_array_nl_sym == k) { if (Qnil == v) { copts->dump_opts.array_size = 0; *copts->dump_opts.array_nl = '\0'; } else { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.array_nl) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "array_nl string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.array_nl)); } strcpy(copts->dump_opts.array_nl, StringValuePtr(v)); copts->dump_opts.array_size = (uint8_t)len; } } else if (nan_sym == k) { if (Qnil == v) { return ST_CONTINUE; } if (null_sym == v) { copts->dump_opts.nan_dump = NullNan; } else if (huge_sym == v) { copts->dump_opts.nan_dump = HugeNan; } else if (word_sym == v) { copts->dump_opts.nan_dump = WordNan; } else if (raise_sym == v) { copts->dump_opts.nan_dump = RaiseNan; } else if (auto_sym == v) { copts->dump_opts.nan_dump = AutoNan; } else { rb_raise(rb_eArgError, ":nan must be :null, :huge, :word, :raise, or :auto."); } } else if (omit_nil_sym == k) { if (Qnil == v) { return ST_CONTINUE; } if (Qtrue == v) { copts->dump_opts.omit_nil = true; } else if (Qfalse == v) { copts->dump_opts.omit_nil = false; } else { rb_raise(rb_eArgError, ":omit_nil must be true or false."); } } else if (oj_ascii_only_sym == k) { // This is here only for backwards compatibility with the original Oj. if (Qtrue == v) { copts->escape_mode = ASCIIEsc; } else if (Qfalse == v) { copts->escape_mode = JSONEsc; } } else if (oj_hash_class_sym == k) { if (Qnil == v) { copts->hash_class = Qnil; } else { rb_check_type(v, T_CLASS); copts->hash_class = v; } } else if (oj_object_class_sym == k) { if (Qnil == v) { copts->hash_class = Qnil; } else { rb_check_type(v, T_CLASS); copts->hash_class = v; } } else if (oj_array_class_sym == k) { if (Qnil == v) { copts->array_class = Qnil; } else { rb_check_type(v, T_CLASS); copts->array_class = v; } } else if (ignore_sym == k) { xfree(copts->ignore); copts->ignore = NULL; if (Qnil != v) { int cnt; rb_check_type(v, T_ARRAY); cnt = (int)RARRAY_LEN(v); if (0 < cnt) { int i; copts->ignore = ALLOC_N(VALUE, cnt + 1); for (i = 0; i < cnt; i++) { copts->ignore[i] = rb_ary_entry(v, i); } copts->ignore[i] = Qnil; } } } else if (integer_range_sym == k) { if (Qnil == v) { return ST_CONTINUE; } if (TYPE(v) == T_STRUCT && rb_obj_class(v) == rb_cRange) { VALUE min = rb_funcall(v, oj_begin_id, 0); VALUE max = rb_funcall(v, oj_end_id, 0); if (TYPE(min) != T_FIXNUM || TYPE(max) != T_FIXNUM) { rb_raise(rb_eArgError, ":integer_range range bounds is not Fixnum."); } copts->int_range_min = FIX2LONG(min); copts->int_range_max = FIX2LONG(max); } else if (Qfalse != v) { rb_raise(rb_eArgError, ":integer_range must be a range of Fixnum."); } } else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) { if (Qnil == v) { return ST_CONTINUE; } copts->sym_key = (Qtrue == v) ? Yes : No; } return ST_CONTINUE; } void oj_parse_options(VALUE ropts, Options copts) { if (T_HASH != rb_type(ropts)) { return; } rb_hash_foreach(ropts, parse_options_cb, (VALUE)copts); oj_parse_opt_match_string(&copts->str_rx, ropts); copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size || 0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size || 0 < copts->dump_opts.array_size); return; } static int match_string_cb(VALUE key, VALUE value, VALUE rx) { RxClass rc = (RxClass)rx; if (T_CLASS != rb_type(value)) { rb_raise(rb_eArgError, "for :match_string, the hash values must be a Class."); } switch (rb_type(key)) { case T_REGEXP: oj_rxclass_rappend(rc, key, value); break; case T_STRING: if (0 != oj_rxclass_append(rc, StringValuePtr(key), value)) { rb_raise(rb_eArgError, "%s", rc->err); } break; default: rb_raise(rb_eArgError, "for :match_string, keys must either a String or RegExp."); break; } return ST_CONTINUE; } void oj_parse_opt_match_string(RxClass rc, VALUE ropts) { VALUE v; if (Qnil != (v = rb_hash_lookup(ropts, match_string_sym))) { rb_check_type(v, T_HASH); // Zero out rc. Pattern are not appended but override. rc->head = NULL; rc->tail = NULL; *rc->err = '\0'; rb_hash_foreach(v, match_string_cb, (VALUE)rc); } } /* Document-method: load * call-seq: load(json, options={}) { _|_obj, start, len_|_ } * * Parses a JSON document String into a Object, Hash, Array, String, Fixnum, * Float, true, false, or nil according to the default mode or the mode * specified. Raises an exception if the JSON is malformed or the classes * specified are not valid. If the string input is not a valid JSON document (an * empty string is not a valid JSON document) an exception is raised. * * When used with a document that has multiple JSON elements the block, if * any, will be yielded to. If no block then the last element read will be * returned. * * This parser operates on string and will attempt to load files into memory if * a file object is passed as the first argument. A stream input will be parsed * using a stream parser but others use the slightly faster string parser. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *json* [_String_|_IO_] JSON String or an Object that responds to read() * - *options* [_Hash_] load options (same as default_options) * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ static VALUE load(int argc, VALUE *argv, VALUE self) { Mode mode = oj_default_options.mode; if (1 > argc) { rb_raise(rb_eArgError, "Wrong number of arguments to load()."); } if (2 <= argc) { VALUE ropts = argv[1]; VALUE v; if (Qnil != ropts || CompatMode != mode) { Check_Type(ropts, T_HASH); if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) { if (object_sym == v) { mode = ObjectMode; } else if (strict_sym == v) { mode = StrictMode; } else if (compat_sym == v || json_sym == v) { mode = CompatMode; } else if (null_sym == v) { mode = NullMode; } else if (custom_sym == v) { mode = CustomMode; } else if (rails_sym == v) { mode = RailsMode; } else if (wab_sym == v) { mode = WabMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, :null, :custom, :rails, or " ":wab."); } } } } switch (mode) { case StrictMode: case NullMode: return oj_strict_parse(argc, argv, self); case CompatMode: case RailsMode: return oj_compat_parse(argc, argv, self); case CustomMode: return oj_custom_parse(argc, argv, self); case WabMode: return oj_wab_parse(argc, argv, self); case ObjectMode: default: break; } return oj_object_parse(argc, argv, self); } /* Document-method: load_file * call-seq: load_file(path, options={}) { _|_obj, start, len_|_ } * * Parses a JSON document String into a Object, Hash, Array, String, Fixnum, * Float, true, false, or nil according to the default mode or the mode * specified. Raises an exception if the JSON is malformed or the classes * specified are not valid. If the string input is not a valid JSON document (an * empty string is not a valid JSON document) an exception is raised. * * When used with a document that has multiple JSON elements the block, if * any, will be yielded to. If no block then the last element read will be * returned. * * If the input file is not a valid JSON document (an empty file is not a valid * JSON document) an exception is raised. * * This is a stream based parser which allows a large or huge file to be loaded * without pulling the whole file into memory. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *path* [_String_] to a file containing a JSON document * - *options* [_Hash_] load options (same as default_options) * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Object_|_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ static VALUE load_file(int argc, VALUE *argv, VALUE self) { char * path; int fd; Mode mode = oj_default_options.mode; struct _parseInfo pi; if (1 > argc) { rb_raise(rb_eArgError, "Wrong number of arguments to load()."); } Check_Type(*argv, T_STRING); parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; if (2 <= argc) { VALUE ropts = argv[1]; VALUE v; Check_Type(ropts, T_HASH); if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) { if (object_sym == v) { mode = ObjectMode; } else if (strict_sym == v) { mode = StrictMode; } else if (compat_sym == v || json_sym == v) { mode = CompatMode; } else if (null_sym == v) { mode = NullMode; } else if (custom_sym == v) { mode = CustomMode; } else if (rails_sym == v) { mode = RailsMode; } else if (wab_sym == v) { mode = WabMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, :null, :custom, :rails, or :wab."); } } } path = StringValuePtr(*argv); if (0 == (fd = open(path, O_RDONLY))) { rb_raise(rb_eIOError, "%s", strerror(errno)); } switch (mode) { case StrictMode: case NullMode: oj_set_strict_callbacks(&pi); return oj_pi_sparse(argc, argv, &pi, fd); case CustomMode: oj_set_custom_callbacks(&pi); return oj_pi_sparse(argc, argv, &pi, fd); case CompatMode: case RailsMode: oj_set_compat_callbacks(&pi); return oj_pi_sparse(argc, argv, &pi, fd); case WabMode: oj_set_wab_callbacks(&pi); return oj_pi_sparse(argc, argv, &pi, fd); case ObjectMode: default: break; } oj_set_object_callbacks(&pi); return oj_pi_sparse(argc, argv, &pi, fd); } /* Document-method: safe_load * call-seq: safe_load(doc) * * Loads a JSON document in strict mode with :auto_define and :symbol_keys * turned off. This function should be safe to use with JSON received on an * unprotected public interface. * * - *doc* [_String__|_IO_] JSON String or IO to load. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Bignum_|_BigDecimal_|_nil_|_True_|_False_] */ static VALUE safe_load(VALUE self, VALUE doc) { struct _parseInfo pi; VALUE args[1]; parse_info_init(&pi); pi.err_class = Qnil; pi.max_depth = 0; pi.options = oj_default_options; pi.options.auto_define = No; pi.options.sym_key = No; pi.options.mode = StrictMode; oj_set_strict_callbacks(&pi); *args = doc; return oj_pi_parse(1, args, &pi, 0, 0, 1); } /* Document-method: saj_parse * call-seq: saj_parse(handler, io) * * Parses an IO stream or file containing a JSON document. Raises an exception * if the JSON is malformed. This is a callback parser that calls the methods in * the handler if they exist. A sample is the Oj::Saj class which can be used as * a base class for the handler. * * - *handler* [_Oj::Saj_] responds to Oj::Saj methods * - *io* [_IO_|_String_] IO Object to read from */ /* Document-method: sc_parse * call-seq: sc_parse(handler, io) * * Parses an IO stream or file containing a JSON document. Raises an exception * if the JSON is malformed. This is a callback parser (Simple Callback Parser) * that calls the methods in the handler if they exist. A sample is the * Oj::ScHandler class which can be used as a base class for the handler. This * callback parser is slightly more efficient than the Saj callback parser and * requires less argument checking. * * - *handler* [_Oj_::ScHandler_] responds to Oj::ScHandler methods * - *io* [_IO__|_String_] IO Object to read from */ struct dump_arg { struct _out * out; struct _options *copts; int argc; VALUE * argv; }; static VALUE dump_body(VALUE a) { volatile struct dump_arg *arg = (void *)a; VALUE rstr; oj_dump_obj_to_json_using_params(*arg->argv, arg->copts, arg->out, arg->argc - 1, arg->argv + 1); if (0 == arg->out->buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(arg->out->buf); rstr = oj_encode(rstr); return rstr; } static VALUE dump_ensure(VALUE a) { volatile struct dump_arg *arg = (void *)a; if (arg->out->allocated) { xfree(arg->out->buf); } return Qnil; } /* Document-method: dump * call-seq: dump(obj, options={}) * * Dumps an Object (obj) to a string. * - *obj* [_Object_] Object to serialize as an JSON document String * - *options* [_Hash_] same as default_options */ static VALUE dump(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct dump_arg arg; struct _out out; struct _options copts = oj_default_options; if (1 > argc) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)."); } if (CompatMode == copts.mode) { copts.dump_opts.nan_dump = WordNan; } if (2 == argc) { oj_parse_options(argv[1], &copts); } if (CompatMode == copts.mode && copts.escape_mode != ASCIIEsc) { copts.escape_mode = JSONEsc; } arg.out = &out; arg.copts = &copts; arg.argc = argc; arg.argv = argv; arg.out->buf = buf; arg.out->end = buf + sizeof(buf) - 10; arg.out->allocated = false; arg.out->omit_nil = copts.dump_opts.omit_nil; arg.out->caller = CALLER_DUMP; return rb_ensure(dump_body, (VALUE)&arg, dump_ensure, (VALUE)&arg); } /* Document-method: to_json * call-seq: to_json(obj, options) * * Dumps an Object (obj) to a string. If the object has a to_json method that * will be called. The mode is set to :compat. * - *obj* [_Object_] Object to serialize as an JSON document String * - *options* [_Hash_] * - *:max_nesting* [_boolean_] It true nesting is limited to 100. The option to detect circular * references is available but is not compatible with the json gem., default is false * - *:allow_nan* [_boolean_] If true non JSON compliant words such as Nan and Infinity will be * used as appropriate, default is true. * - *:quirks_mode* [_boolean_] Allow single JSON values instead of documents, default is true * (allow). * - *:indent* [_String_|_nil_] String to use for indentation, overriding the indent option if not * nil. * - *:space* [_String_|_nil_] String to use for the space after the colon in JSON object fields. * - *:space_before* [_String_|_nil_] String to use before the colon separator in JSON object * fields. * - *:object_nl* [_String_|_nil_] String to use after a JSON object field value. * - *:array_nl* [_String_|_nil_] String to use after a JSON array value. * - *:trace* [_Boolean_] If true trace is turned on. * * Returns [_String_] the encoded JSON. */ static VALUE to_json(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct _out out; struct _options copts = oj_default_options; VALUE rstr; if (1 > argc) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)."); } copts.escape_mode = JXEsc; copts.dump_opts.nan_dump = RaiseNan; if (2 == argc) { oj_parse_mimic_dump_options(argv[1], &copts); } copts.mode = CompatMode; copts.to_json = Yes; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.omit_nil = copts.dump_opts.omit_nil; // For obj.to_json or generate nan is not allowed but if called from dump // it is. oj_dump_obj_to_json_using_params(*argv, &copts, &out, argc - 1, argv + 1); if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (out.allocated) { xfree(out.buf); } return rstr; } /* Document-method: to_file * call-seq: to_file(file_path, obj, options={}) * * Dumps an Object to the specified file. * - *file* [_String_] _path file path to write the JSON document to * - *obj* [_Object_] Object to serialize as an JSON document String * - *options* [_Hash_] formatting options * - *:indent* [_Fixnum_] format expected * - *:circular* [_Boolean_] allow circular references, default: false */ static VALUE to_file(int argc, VALUE *argv, VALUE self) { struct _options copts = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &copts); } Check_Type(*argv, T_STRING); oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts); return Qnil; } /* Document-method: to_stream * call-seq: to_stream(io, obj, options={}) * * Dumps an Object to the specified IO stream. * - *io* [_IO_] IO stream to write the JSON document to * - *obj* [_Object_] Object to serialize as an JSON document String * - *options* [_Hash_] formatting options * - *:indent* [_Fixnum_] format expected * - *:circular* [_Boolean_] allow circular references, default: false */ static VALUE to_stream(int argc, VALUE *argv, VALUE self) { struct _options copts = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &copts); } oj_write_obj_to_stream(argv[1], *argv, &copts); return Qnil; } /* Document-method: register_odd * call-seq: register_odd(clas, create_object, create_method, *members) * * Registers a class as special. This is useful for working around subclasses of * primitive types as is done with ActiveSupport classes. The use of this * function should be limited to just classes that can not be handled in the * normal way. It is not intended as a hook for changing the output of all * classes as it is not optimized for large numbers of classes. * * - *clas* [_Class__|_Module_] Class or Module to be made special * - *create_object* [_Object_] object to call the create method on * - *create_method* [_Symbol_] method on the clas that will create a new instance of the clas when * given all the member values in the order specified. * - *members* [_Symbol__|_String_] methods used to get the member values from instances of the * clas. */ static VALUE register_odd(int argc, VALUE *argv, VALUE self) { if (3 > argc) { rb_raise(rb_eArgError, "incorrect number of arguments."); } switch (rb_type(*argv)) { case T_CLASS: case T_MODULE: break; default: rb_raise(rb_eTypeError, "expected a class or module."); break; } Check_Type(argv[2], T_SYMBOL); if (MAX_ODD_ARGS < argc - 2) { rb_raise(rb_eArgError, "too many members."); } oj_reg_odd(argv[0], argv[1], argv[2], argc - 3, argv + 3, false); return Qnil; } /* Document-method: register_odd_raw * call-seq: register_odd_raw(clas, create_object, create_method, dump_method) * * Registers a class as special and expect the output to be a string that can be * included in the dumped JSON directly. This is useful for working around * subclasses of primitive types as is done with ActiveSupport classes. The use * of this function should be limited to just classes that can not be handled in * the normal way. It is not intended as a hook for changing the output of all * classes as it is not optimized for large numbers of classes. Be careful with * this option as the JSON may be incorrect if invalid JSON is returned. * * - *clas* [_Class_|_Module_] Class or Module to be made special * - *create_object* [_Object_] object to call the create method on * - *create_method* [_Symbol_] method on the clas that will create a new instance of the clas when *given all the member values in the order specified. * - *dump_method* [_Symbol_|_String_] method to call on the object being serialized to generate the *raw JSON. */ static VALUE register_odd_raw(int argc, VALUE *argv, VALUE self) { if (3 > argc) { rb_raise(rb_eArgError, "incorrect number of arguments."); } switch (rb_type(*argv)) { case T_CLASS: case T_MODULE: break; default: rb_raise(rb_eTypeError, "expected a class or module."); break; } Check_Type(argv[2], T_SYMBOL); if (MAX_ODD_ARGS < argc - 2) { rb_raise(rb_eArgError, "too many members."); } oj_reg_odd(argv[0], argv[1], argv[2], 1, argv + 3, true); return Qnil; } //////////////////////////////////////////////////////////////////////////////// // RDoc entries must be in the same file as the rb_define_method and must be // directly above the C method function. The extern declaration is enough to // get it to work. //////////////////////////////////////////////////////////////////////////////// /* Document-method: strict_load * call-seq: strict_load(json, options) { _|_obj, start, len_|_ } * * Parses a JSON document String into an Hash, Array, String, Fixnum, Float, * true, false, or nil. It parses using a mode that is strict in that it maps * each primitive JSON type to a similar Ruby type. The :create_id is not * honored in this mode. Note that a Ruby Hash is used to represent the JSON * Object type. These two are not the same since the JSON Object type can have * repeating entries with the same key and Ruby Hash can not. * * When used with a document that has multiple JSON elements the block, if * any, will be yielded to. If no block then the last element read will be * returned. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *json* [_String_|_IO_] JSON String or an Object that responds to read(). * - *options* [_Hash_] load options (same as default_options). * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ extern VALUE oj_strict_parse(int argc, VALUE *argv, VALUE self); /* Document-method: compat_load * call-seq: compat_load(json, options) { _|_obj, start, len_|_ } * * Parses a JSON document String into an Object, Hash, Array, String, Fixnum, * Float, true, false, or nil. It parses using a mode that is generally * compatible with other Ruby JSON parsers in that it will create objects based * on the :create_id value. It is not compatible in every way to every other * parser though as each parser has it's own variations. * * When used with a document that has multiple JSON elements the block, if * any, will be yielded to. If no block then the last element read will be * returned. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *json* [_String_|_IO_] JSON String or an Object that responds to read(). * - *options* [_Hash_] load options (same as default_options). * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ extern VALUE oj_compat_parse(int argc, VALUE *argv, VALUE self); /* Document-method: object_load * call-seq: object_load(json, options) { _|_obj, start, len_|_ } * * Parses a JSON document String into an Object, Hash, Array, String, Fixnum, * Float, true, false, or nil. In the :object mode the JSON should have been * generated by Oj.dump(). The parser will reconstitute the original marshalled * or dumped Object. The :auto_define and :circular options have meaning with * this parsing mode. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *json* [_String_|_IO_] JSON String or an Object that responds to read(). * - *options* [_Hash_] load options (same as default_options). * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ extern VALUE oj_object_parse(int argc, VALUE *argv, VALUE self); /* Document-method: wab_load * call-seq: wab_load(json, options) { _|_obj, start, len_|_ } * * Parses a JSON document String into an Hash, Array, String, Fixnum, Float, * true, false, or nil. It parses using a mode that is :wab in that it maps * each primitive JSON type to a similar Ruby type. The :create_id is not * honored in this mode. Note that a Ruby Hash is used to represent the JSON * Object type. These two are not the same since the JSON Object type can have * repeating entries with the same key and Ruby Hash can not. * * When used with a document that has multiple JSON elements the block, if * any, will be yielded to. If no block then the last element read will be * returned. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * A block can be provided with a single argument. That argument will be the * parsed JSON document. This is useful when parsing a string that includes * multiple JSON documents. The block can take up to 3 arguments, the parsed * object, the position in the string or stream of the start of the JSON for * that object, and the length of the JSON for that object plus trailing * whitespace. * * - *json* [_String_|_IO_] JSON String or an Object that responds to read(). * - *options* [_Hash_] load options (same as default_options). * - *obj* [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] parsed object. * - *start* [_optional, _Integer_] start position of parsed JSON for obj. * - *len* [_optional, _Integer_] length of parsed JSON for obj. * * Returns [_Hash_|_Array_|_String_|_Fixnum_|_Float_|_Boolean_|_nil_] */ extern VALUE oj_wab_parse(int argc, VALUE *argv, VALUE self); /* Document-method: add_to_json * call-seq: add_to_json(*args) * * Override simple to_s dump behavior in :compat mode to instead use an * optimized dump that includes the classname and attributes so that the * object can be re-created on load. The format is the same as the json gem * but does not use the ruby methods for encoding. * * The classes supported for optimization are: Array, BigDecimal, Complex, * Date, DateTime, Exception, Hash, Integer, OpenStruct, Range, Rational, * Regexp, Struct, and Time. Providing no classes will result in all those * classes being optimized.q * * - *args( [_Class_] zero or more classes to optimize. */ extern VALUE oj_add_to_json(int argc, VALUE *argv, VALUE self); /* @!method remove_to_json(*args) * * Reverts back to the to_s dump behavior in :compat mode to instead use an * optimized dump that includes the classname and attributes so that the * object can be re-created on load. The format is the same as the json gem * but does not use the ruby methods for encoding. * * The classes supported for optimization are: Array, BigDecimal, Complex, * Date, DateTime, Exception, Hash, Integer, OpenStruct, Range, Rational, * Regexp, Struct, and Time. Providing no classes will result in all those * classes being reverted from the optimized mode. * * - *args* [_Class_] zero or more classes to optimize. */ extern VALUE oj_remove_to_json(int argc, VALUE *argv, VALUE self); /* Document-method: mimic_JSON * call-seq: mimic_JSON() * * Creates the JSON module with methods and classes to mimic the JSON gem. After * this method is invoked calls that expect the JSON module will use Oj instead * and be faster than the original JSON. Most options that could be passed to * the JSON methods are supported. The calls to set parser or generator will not * raise an Exception but will not have any effect. The method can also be * called after the json gem is loaded. The necessary methods on the json gem * will be replaced with Oj methods. * * Note that this also sets the default options of :mode to :compat and * :encoding to :unicode_xss. * * Returns [_Module_] the JSON module. */ extern VALUE oj_define_mimic_json(int argc, VALUE *argv, VALUE self); /* Document-method: generate * call-seq: generate(obj, opts=nil) * * Encode obj as a JSON String. The obj argument must be a Hash, Array, or * respond to to_h or to_json. Options other than those listed such as * +:allow_nan+ or +:max_nesting+ are ignored. Calling this method will call * Oj.mimic_JSON if it is not already called. * * - *obj* [_Object__|_Hash_|_Array_] object to convert to a JSON String * - *opts* [_Hash_] options * - *:indent* [_String_] String to use for indentation. * - *:space* [_String_] String placed after a , or : delimiter * - *:space_before* [_String_] String placed before a : delimiter * - *:object_nl* [_String_] String placed after a JSON object * - *:array_nl* [_String_] String placed after a JSON array * - *:ascii_only* [_Boolean_] if not nil or false then use only ascii characters in the output. * Note JSON.generate does support this even if it is not documented. * * Returns [_String_]generated JSON. */ extern VALUE oj_mimic_generate(int argc, VALUE *argv, VALUE self); /* Document-module: Oj.optimize_rails() * * Sets the Oj as the Rails encoder and decoder. Oj::Rails.optimize is also * called. */ extern VALUE oj_optimize_rails(VALUE self); /* extern void oj_hash_test(); static VALUE hash_test(VALUE self) { oj_hash_test(); return Qnil; } */ /* extern void oj_hash_sizes(); static VALUE hash_test(VALUE self) { oj_hash_sizes(); return Qnil; } */ static VALUE protect_require(VALUE x) { rb_require("time"); rb_require("bigdecimal"); return Qnil; } /* Document-module: Oj * * Optimized JSON (Oj), as the name implies was written to provide speed * optimized JSON handling. * * Oj uses modes to control how object are encoded and decoded. In addition * global and options to methods allow additional behavior modifications. The * modes are: * * - *:strict* mode will only allow the 7 basic JSON types to be serialized. Any other Object * will raise an Exception. * * - *:null* mode is similar to the :strict mode except any Object that is not * one of the JSON base types is replaced by a JSON null. * * - *:object* mode will dump any Object as a JSON Object with keys that match * the Ruby Object's variable names without the '@' character. This is the * highest performance mode. * * - *:compat* or *:json* mode is the compatible mode for the json gem. It mimics * the json gem including the options, defaults, and restrictions. * * - *:rails* is the compatibility mode for Rails or Active support. * * - *:custom* is the most configurable mode. * * - *:wab* specifically for WAB data exchange. */ void Init_oj() { int err = 0; #if HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif Oj = rb_define_module("Oj"); oj_cstack_class = rb_define_class_under(Oj, "CStack", rb_cObject); oj_string_writer_init(); oj_stream_writer_init(); rb_require("date"); // On Rubinius the require fails but can be done from a ruby file. rb_protect(protect_require, Qnil, &err); rb_require("stringio"); oj_utf8_encoding = rb_enc_find("UTF-8"); // rb_define_module_function(Oj, "hash_test", hash_test, 0); rb_define_module_function(Oj, "default_options", get_def_opts, 0); rb_define_module_function(Oj, "default_options=", set_def_opts, 1); rb_define_module_function(Oj, "mimic_JSON", oj_define_mimic_json, -1); rb_define_module_function(Oj, "load", load, -1); rb_define_module_function(Oj, "load_file", load_file, -1); rb_define_module_function(Oj, "safe_load", safe_load, 1); rb_define_module_function(Oj, "strict_load", oj_strict_parse, -1); rb_define_module_function(Oj, "compat_load", oj_compat_parse, -1); rb_define_module_function(Oj, "object_load", oj_object_parse, -1); rb_define_module_function(Oj, "wab_load", oj_wab_parse, -1); rb_define_module_function(Oj, "dump", dump, -1); rb_define_module_function(Oj, "to_file", to_file, -1); rb_define_module_function(Oj, "to_stream", to_stream, -1); // JSON gem compatibility rb_define_module_function(Oj, "to_json", to_json, -1); rb_define_module_function(Oj, "generate", oj_mimic_generate, -1); rb_define_module_function(Oj, "fast_generate", oj_mimic_generate, -1); rb_define_module_function(Oj, "add_to_json", oj_add_to_json, -1); rb_define_module_function(Oj, "remove_to_json", oj_remove_to_json, -1); rb_define_module_function(Oj, "register_odd", register_odd, -1); rb_define_module_function(Oj, "register_odd_raw", register_odd_raw, -1); rb_define_module_function(Oj, "saj_parse", oj_saj_parse, -1); rb_define_module_function(Oj, "sc_parse", oj_sc_parse, -1); rb_define_module_function(Oj, "optimize_rails", oj_optimize_rails, 0); oj_add_value_id = rb_intern("add_value"); oj_array_append_id = rb_intern("array_append"); oj_array_end_id = rb_intern("array_end"); oj_array_start_id = rb_intern("array_start"); oj_as_json_id = rb_intern("as_json"); oj_begin_id = rb_intern("begin"); oj_bigdecimal_id = rb_intern("BigDecimal"); oj_end_id = rb_intern("end"); oj_error_id = rb_intern("error"); oj_exclude_end_id = rb_intern("exclude_end?"); oj_file_id = rb_intern("file?"); oj_fileno_id = rb_intern("fileno"); oj_ftype_id = rb_intern("ftype"); oj_hash_end_id = rb_intern("hash_end"); oj_hash_key_id = rb_intern("hash_key"); oj_hash_set_id = rb_intern("hash_set"); oj_hash_start_id = rb_intern("hash_start"); oj_iconv_id = rb_intern("iconv"); oj_instance_variables_id = rb_intern("instance_variables"); oj_json_create_id = rb_intern("json_create"); oj_length_id = rb_intern("length"); oj_new_id = rb_intern("new"); oj_parse_id = rb_intern("parse"); oj_pos_id = rb_intern("pos"); oj_raw_json_id = rb_intern("raw_json"); oj_read_id = rb_intern("read"); oj_readpartial_id = rb_intern("readpartial"); oj_replace_id = rb_intern("replace"); oj_stat_id = rb_intern("stat"); oj_string_id = rb_intern("string"); oj_to_h_id = rb_intern("to_h"); oj_to_hash_id = rb_intern("to_hash"); oj_to_json_id = rb_intern("to_json"); oj_to_s_id = rb_intern("to_s"); oj_to_sym_id = rb_intern("to_sym"); oj_to_time_id = rb_intern("to_time"); oj_tv_nsec_id = rb_intern("tv_nsec"); oj_tv_sec_id = rb_intern("tv_sec"); oj_tv_usec_id = rb_intern("tv_usec"); oj_utc_id = rb_intern("utc"); oj_utc_offset_id = rb_intern("utc_offset"); oj_utcq_id = rb_intern("utc?"); oj_write_id = rb_intern("write"); rb_require("oj/bag"); rb_require("oj/error"); rb_require("oj/mimic"); rb_require("oj/saj"); rb_require("oj/schandler"); oj_bag_class = rb_const_get_at(Oj, rb_intern("Bag")); rb_gc_register_mark_object(oj_bag_class); oj_bigdecimal_class = rb_const_get(rb_cObject, rb_intern("BigDecimal")); rb_gc_register_mark_object(oj_bigdecimal_class); oj_date_class = rb_const_get(rb_cObject, rb_intern("Date")); rb_gc_register_mark_object(oj_date_class); oj_datetime_class = rb_const_get(rb_cObject, rb_intern("DateTime")); rb_gc_register_mark_object(oj_datetime_class); oj_enumerable_class = rb_const_get(rb_cObject, rb_intern("Enumerable")); rb_gc_register_mark_object(oj_enumerable_class); oj_parse_error_class = rb_const_get_at(Oj, rb_intern("ParseError")); rb_gc_register_mark_object(oj_parse_error_class); oj_stringio_class = rb_const_get(rb_cObject, rb_intern("StringIO")); rb_gc_register_mark_object(oj_stringio_class); oj_struct_class = rb_const_get(rb_cObject, rb_intern("Struct")); rb_gc_register_mark_object(oj_struct_class); oj_json_parser_error_class = rb_eEncodingError; // replaced if mimic is called oj_json_generator_error_class = rb_eEncodingError; // replaced if mimic is called allow_blank_sym = ID2SYM(rb_intern("allow_blank")); rb_gc_register_address(&allow_blank_sym); allow_gc_sym = ID2SYM(rb_intern("allow_gc")); rb_gc_register_address(&allow_gc_sym); allow_invalid_unicode_sym = ID2SYM(rb_intern("allow_invalid_unicode")); rb_gc_register_address(&allow_invalid_unicode_sym); ascii_sym = ID2SYM(rb_intern("ascii")); rb_gc_register_address(&ascii_sym); auto_define_sym = ID2SYM(rb_intern("auto_define")); rb_gc_register_address(&auto_define_sym); auto_sym = ID2SYM(rb_intern("auto")); rb_gc_register_address(&auto_sym); bigdecimal_as_decimal_sym = ID2SYM(rb_intern("bigdecimal_as_decimal")); rb_gc_register_address(&bigdecimal_as_decimal_sym); bigdecimal_load_sym = ID2SYM(rb_intern("bigdecimal_load")); rb_gc_register_address(&bigdecimal_load_sym); bigdecimal_sym = ID2SYM(rb_intern("bigdecimal")); rb_gc_register_address(&bigdecimal_sym); cache_keys_sym = ID2SYM(rb_intern("cache_keys")); rb_gc_register_address(&cache_keys_sym); cache_str_sym = ID2SYM(rb_intern("cache_str")); rb_gc_register_address(&cache_str_sym); cache_string_sym = ID2SYM(rb_intern("cache_string")); rb_gc_register_address(&cache_string_sym); circular_sym = ID2SYM(rb_intern("circular")); rb_gc_register_address(&circular_sym); class_cache_sym = ID2SYM(rb_intern("class_cache")); rb_gc_register_address(&class_cache_sym); compat_bigdecimal_sym = ID2SYM(rb_intern("compat_bigdecimal")); rb_gc_register_address(&compat_bigdecimal_sym); compat_sym = ID2SYM(rb_intern("compat")); rb_gc_register_address(&compat_sym); create_id_sym = ID2SYM(rb_intern("create_id")); rb_gc_register_address(&create_id_sym); custom_sym = ID2SYM(rb_intern("custom")); rb_gc_register_address(&custom_sym); empty_string_sym = ID2SYM(rb_intern("empty_string")); rb_gc_register_address(&empty_string_sym); escape_mode_sym = ID2SYM(rb_intern("escape_mode")); rb_gc_register_address(&escape_mode_sym); integer_range_sym = ID2SYM(rb_intern("integer_range")); rb_gc_register_address(&integer_range_sym); fast_sym = ID2SYM(rb_intern("fast")); rb_gc_register_address(&fast_sym); float_prec_sym = ID2SYM(rb_intern("float_precision")); rb_gc_register_address(&float_prec_sym); float_sym = ID2SYM(rb_intern("float")); rb_gc_register_address(&float_sym); huge_sym = ID2SYM(rb_intern("huge")); rb_gc_register_address(&huge_sym); ignore_sym = ID2SYM(rb_intern("ignore")); rb_gc_register_address(&ignore_sym); ignore_under_sym = ID2SYM(rb_intern("ignore_under")); rb_gc_register_address(&ignore_under_sym); json_sym = ID2SYM(rb_intern("json")); rb_gc_register_address(&json_sym); match_string_sym = ID2SYM(rb_intern("match_string")); rb_gc_register_address(&match_string_sym); mode_sym = ID2SYM(rb_intern("mode")); rb_gc_register_address(&mode_sym); nan_sym = ID2SYM(rb_intern("nan")); rb_gc_register_address(&nan_sym); newline_sym = ID2SYM(rb_intern("newline")); rb_gc_register_address(&newline_sym); nilnil_sym = ID2SYM(rb_intern("nilnil")); rb_gc_register_address(&nilnil_sym); null_sym = ID2SYM(rb_intern("null")); rb_gc_register_address(&null_sym); object_sym = ID2SYM(rb_intern("object")); rb_gc_register_address(&object_sym); oj_allow_nan_sym = ID2SYM(rb_intern("allow_nan")); rb_gc_register_address(&oj_allow_nan_sym); oj_array_class_sym = ID2SYM(rb_intern("array_class")); rb_gc_register_address(&oj_array_class_sym); oj_array_nl_sym = ID2SYM(rb_intern("array_nl")); rb_gc_register_address(&oj_array_nl_sym); oj_ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&oj_ascii_only_sym); oj_create_additions_sym = ID2SYM(rb_intern("create_additions")); rb_gc_register_address(&oj_create_additions_sym); oj_decimal_class_sym = ID2SYM(rb_intern("decimal_class")); rb_gc_register_address(&oj_decimal_class_sym); oj_hash_class_sym = ID2SYM(rb_intern("hash_class")); rb_gc_register_address(&oj_hash_class_sym); oj_indent_sym = ID2SYM(rb_intern("indent")); rb_gc_register_address(&oj_indent_sym); oj_max_nesting_sym = ID2SYM(rb_intern("max_nesting")); rb_gc_register_address(&oj_max_nesting_sym); oj_object_class_sym = ID2SYM(rb_intern("object_class")); rb_gc_register_address(&oj_object_class_sym); oj_object_nl_sym = ID2SYM(rb_intern("object_nl")); rb_gc_register_address(&oj_object_nl_sym); oj_quirks_mode_sym = ID2SYM(rb_intern("quirks_mode")); rb_gc_register_address(&oj_quirks_mode_sym); oj_safe_sym = ID2SYM(rb_intern("safe")); rb_gc_register_address(&oj_safe_sym); oj_space_before_sym = ID2SYM(rb_intern("space_before")); rb_gc_register_address(&oj_space_before_sym); oj_space_sym = ID2SYM(rb_intern("space")); rb_gc_register_address(&oj_space_sym); oj_trace_sym = ID2SYM(rb_intern("trace")); rb_gc_register_address(&oj_trace_sym); omit_nil_sym = ID2SYM(rb_intern("omit_nil")); rb_gc_register_address(&omit_nil_sym); rails_sym = ID2SYM(rb_intern("rails")); rb_gc_register_address(&rails_sym); raise_sym = ID2SYM(rb_intern("raise")); rb_gc_register_address(&raise_sym); ruby_sym = ID2SYM(rb_intern("ruby")); rb_gc_register_address(&ruby_sym); sec_prec_sym = ID2SYM(rb_intern("second_precision")); rb_gc_register_address(&sec_prec_sym); strict_sym = ID2SYM(rb_intern("strict")); rb_gc_register_address(&strict_sym); symbol_keys_sym = ID2SYM(rb_intern("symbol_keys")); rb_gc_register_address(&symbol_keys_sym); oj_symbolize_names_sym = ID2SYM(rb_intern("symbolize_names")); rb_gc_register_address(&oj_symbolize_names_sym); time_format_sym = ID2SYM(rb_intern("time_format")); rb_gc_register_address(&time_format_sym); unicode_xss_sym = ID2SYM(rb_intern("unicode_xss")); rb_gc_register_address(&unicode_xss_sym); unix_sym = ID2SYM(rb_intern("unix")); rb_gc_register_address(&unix_sym); unix_zone_sym = ID2SYM(rb_intern("unix_zone")); rb_gc_register_address(&unix_zone_sym); use_as_json_sym = ID2SYM(rb_intern("use_as_json")); rb_gc_register_address(&use_as_json_sym); use_raw_json_sym = ID2SYM(rb_intern("use_raw_json")); rb_gc_register_address(&use_raw_json_sym); use_to_hash_sym = ID2SYM(rb_intern("use_to_hash")); rb_gc_register_address(&use_to_hash_sym); use_to_json_sym = ID2SYM(rb_intern("use_to_json")); rb_gc_register_address(&use_to_json_sym); wab_sym = ID2SYM(rb_intern("wab")); rb_gc_register_address(&wab_sym); word_sym = ID2SYM(rb_intern("word")); rb_gc_register_address(&word_sym); xmlschema_sym = ID2SYM(rb_intern("xmlschema")); rb_gc_register_address(&xmlschema_sym); xss_safe_sym = ID2SYM(rb_intern("xss_safe")); rb_gc_register_address(&xss_safe_sym); oj_slash_string = rb_str_new2("/"); rb_gc_register_address(&oj_slash_string); OBJ_FREEZE(oj_slash_string); oj_default_options.mode = ObjectMode; oj_hash_init(); oj_odd_init(); oj_mimic_rails_init(); #ifdef HAVE_PTHREAD_MUTEX_INIT if (0 != (err = pthread_mutex_init(&oj_cache_mutex, 0))) { rb_raise(rb_eException, "failed to initialize a mutex. %s", strerror(err)); } #else oj_cache_mutex = rb_mutex_new(); rb_gc_register_address(&oj_cache_mutex); #endif oj_init_doc(); oj_parser_init(); } oj-3.13.9/ext/oj/odd.c0000644000004100000410000001407614136373754014416 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "odd.h" #include static struct _odd _odds[4]; // bump up if new initial Odd classes are added static struct _odd *odds = _odds; static long odd_cnt = 0; static ID sec_id; static ID sec_fraction_id; static ID to_f_id; static ID numerator_id; static ID denominator_id; static ID rational_id; static VALUE rational_class; static void set_class(Odd odd, const char *classname) { const char **np; ID * idp; odd->classname = classname; odd->clen = strlen(classname); odd->clas = rb_const_get(rb_cObject, rb_intern(classname)); odd->create_obj = odd->clas; odd->create_op = rb_intern("new"); odd->is_module = (T_MODULE == rb_type(odd->clas)); odd->raw = 0; for (np = odd->attr_names, idp = odd->attrs; 0 != *np; np++, idp++) { *idp = rb_intern(*np); } *idp = 0; } static VALUE get_datetime_secs(VALUE obj) { volatile VALUE rsecs = rb_funcall(obj, sec_id, 0); volatile VALUE rfrac = rb_funcall(obj, sec_fraction_id, 0); long sec = NUM2LONG(rsecs); long long num = rb_num2ll(rb_funcall(rfrac, numerator_id, 0)); long long den = rb_num2ll(rb_funcall(rfrac, denominator_id, 0)); num += sec * den; return rb_funcall(rb_cObject, rational_id, 2, rb_ll2inum(num), rb_ll2inum(den)); } void oj_odd_init() { Odd odd; const char **np; sec_id = rb_intern("sec"); sec_fraction_id = rb_intern("sec_fraction"); to_f_id = rb_intern("to_f"); numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); rational_id = rb_intern("Rational"); rational_class = rb_const_get(rb_cObject, rational_id); memset(_odds, 0, sizeof(_odds)); odd = odds; // Rational np = odd->attr_names; *np++ = "numerator"; *np++ = "denominator"; *np = 0; set_class(odd, "Rational"); odd->create_obj = rb_cObject; odd->create_op = rational_id; odd->attr_cnt = 2; // Date odd++; np = odd->attr_names; *np++ = "year"; *np++ = "month"; *np++ = "day"; *np++ = "start"; *np++ = 0; set_class(odd, "Date"); odd->attr_cnt = 4; // DateTime odd++; np = odd->attr_names; *np++ = "year"; *np++ = "month"; *np++ = "day"; *np++ = "hour"; *np++ = "min"; *np++ = "sec"; *np++ = "offset"; *np++ = "start"; *np++ = 0; set_class(odd, "DateTime"); odd->attr_cnt = 8; odd->attrFuncs[5] = get_datetime_secs; // Range odd++; np = odd->attr_names; *np++ = "begin"; *np++ = "end"; *np++ = "exclude_end?"; *np++ = 0; set_class(odd, "Range"); odd->attr_cnt = 3; odd_cnt = odd - odds + 1; } Odd oj_get_odd(VALUE clas) { Odd odd; const char *classname = NULL; for (odd = odds + odd_cnt - 1; odds <= odd; odd--) { if (clas == odd->clas) { return odd; } if (odd->is_module) { if (NULL == classname) { classname = rb_class2name(clas); } if (0 == strncmp(odd->classname, classname, odd->clen) && ':' == classname[odd->clen]) { return odd; } } } return NULL; } Odd oj_get_oddc(const char *classname, size_t len) { Odd odd; for (odd = odds + odd_cnt - 1; odds <= odd; odd--) { if (len == odd->clen && 0 == strncmp(classname, odd->classname, len)) { return odd; } if (odd->is_module && 0 == strncmp(odd->classname, classname, odd->clen) && ':' == classname[odd->clen]) { return odd; } } return 0; } OddArgs oj_odd_alloc_args(Odd odd) { OddArgs oa = ALLOC_N(struct _oddArgs, 1); VALUE * a; int i; oa->odd = odd; for (i = odd->attr_cnt, a = oa->args; 0 < i; i--, a++) { *a = Qnil; } return oa; } void oj_odd_free(OddArgs args) { xfree(args); } int oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value) { const char **np; VALUE * vp; int i; for (i = args->odd->attr_cnt, np = args->odd->attr_names, vp = args->args; 0 < i; i--, np++, vp++) { if (0 == strncmp(key, *np, klen) && '\0' == *((*np) + klen)) { *vp = value; return 0; } } return -1; } void oj_reg_odd(VALUE clas, VALUE create_object, VALUE create_method, int mcnt, VALUE *members, bool raw) { Odd odd; const char **np; ID * ap; AttrGetFunc *fp; if (_odds == odds) { odds = ALLOC_N(struct _odd, odd_cnt + 1); memcpy(odds, _odds, sizeof(struct _odd) * odd_cnt); } else { REALLOC_N(odds, struct _odd, odd_cnt + 1); } odd = odds + odd_cnt; odd->clas = clas; if (NULL == (odd->classname = strdup(rb_class2name(clas)))) { rb_raise(rb_eNoMemError, "for attribute name."); } odd->clen = strlen(odd->classname); odd->create_obj = create_object; odd->create_op = SYM2ID(create_method); odd->attr_cnt = mcnt; odd->is_module = (T_MODULE == rb_type(clas)); odd->raw = raw; for (ap = odd->attrs, np = odd->attr_names, fp = odd->attrFuncs; 0 < mcnt; mcnt--, ap++, np++, members++, fp++) { *fp = 0; switch (rb_type(*members)) { case T_STRING: if (NULL == (*np = strdup(RSTRING_PTR(*members)))) { rb_raise(rb_eNoMemError, "for attribute name."); } break; case T_SYMBOL: *np = rb_id2name(SYM2ID(*members)); break; default: rb_raise(rb_eArgError, "registered member identifiers must be Strings or Symbols."); break; } *ap = rb_intern(*np); } *np = 0; *ap = 0; odd_cnt++; } oj-3.13.9/ext/oj/resolve.c0000644000004100000410000000443514136373754015325 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include #include #include #ifdef HAVE_PTHREAD_MUTEX_INIT #include #endif #include "err.h" #include "intern.h" #include "oj.h" #include "parse.h" inline static VALUE resolve_classname(VALUE mod, const char *classname, int auto_define) { VALUE clas; ID ci = rb_intern(classname); if (rb_const_defined_at(mod, ci)) { clas = rb_const_get_at(mod, ci); } else if (auto_define) { clas = rb_define_class_under(mod, classname, oj_bag_class); } else { clas = Qundef; } return clas; } static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int auto_define, VALUE error_class) { char class_name[1024]; VALUE clas; char * end = class_name + sizeof(class_name) - 1; char * s; const char *n = name; clas = rb_cObject; for (s = class_name; 0 < len; n++, len--) { if (':' == *n) { *s = '\0'; n++; len--; if (':' != *n) { return Qundef; } if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { return Qundef; } s = class_name; } else if (end <= s) { return Qundef; } else { *s++ = *n; } } *s = '\0'; if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { oj_set_error_at(pi, error_class, __FILE__, __LINE__, "class %s is not defined", name); if (Qnil != error_class) { pi->err_class = error_class; } } return clas; } VALUE oj_name2class(ParseInfo pi, const char *name, size_t len, int auto_define, VALUE error_class) { if (No == pi->options.class_cache) { return resolve_classpath(pi, name, len, auto_define, error_class); } return oj_class_intern(name, len, true, pi, auto_define, error_class); } VALUE oj_name2struct(ParseInfo pi, VALUE nameVal, VALUE error_class) { size_t len = RSTRING_LEN(nameVal); const char *str = StringValuePtr(nameVal); return resolve_classpath(pi, str, len, 0, error_class); } oj-3.13.9/ext/oj/saj.c0000644000004100000410000005101414136373754014416 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #if !IS_WINDOWS #include /* for getrlimit() on linux */ #endif #include #include #include #include #include #include // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0 / 0.0) #include "encode.h" #include "oj.h" typedef struct _parseInfo { char *str; /* buffer being read from */ char *s; /* current position in buffer */ void *stack_min; VALUE handler; int has_hash_start; int has_hash_end; int has_array_start; int has_array_end; int has_add_value; int has_error; } * ParseInfo; static void read_next(ParseInfo pi, const char *key); static void read_hash(ParseInfo pi, const char *key); static void read_array(ParseInfo pi, const char *key); static void read_str(ParseInfo pi, const char *key); static void read_num(ParseInfo pi, const char *key); static void read_true(ParseInfo pi, const char *key); static void read_false(ParseInfo pi, const char *key); static void read_nil(ParseInfo pi, const char *key); static void next_non_white(ParseInfo pi); static char *read_quoted_value(ParseInfo pi); static void skip_comment(ParseInfo pi); /* This JSON parser is a single pass, destructive, callback parser. It is a * single pass parse since it only make one pass over the characters in the * JSON document string. It is destructive because it re-uses the content of * the string for values in the callback and places \0 characters at various * places to mark the end of tokens and strings. It is a callback parser like * a SAX parser because it uses callback when document elements are * encountered. * * Parsing is very tolerant. Lack of headers and even misspelled element * endings are passed over without raising an error. A best attempt is made in * all cases to parse the string. */ inline static void call_error(const char *msg, ParseInfo pi, const char *file, int line) { char buf[128]; const char *s = pi->s; int jline = 1; int col = 1; for (; pi->str < s && '\n' != *s; s--) { col++; } for (; pi->str < s; s--) { if ('\n' == *s) { jline++; } } sprintf(buf, "%s at line %d, column %d [%s:%d]", msg, jline, col, file, line); rb_funcall(pi->handler, oj_error_id, 3, rb_str_new2(buf), LONG2NUM(jline), LONG2NUM(col)); } inline static void next_non_white(ParseInfo pi) { for (; 1; pi->s++) { switch (*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': break; case '/': skip_comment(pi); break; default: return; } } } inline static void call_add_value(VALUE handler, VALUE value, const char *key) { volatile VALUE k; if (0 == key) { k = Qnil; } else { k = rb_str_new2(key); k = oj_encode(k); } rb_funcall(handler, oj_add_value_id, 2, value, k); } inline static void call_no_value(VALUE handler, ID method, const char *key) { volatile VALUE k; if (0 == key) { k = Qnil; } else { k = rb_str_new2(key); k = oj_encode(k); } rb_funcall(handler, method, 1, k); } static void skip_comment(ParseInfo pi) { pi->s++; /* skip first / */ if ('*' == *pi->s) { pi->s++; for (; '\0' != *pi->s; pi->s++) { if ('*' == *pi->s && '/' == *(pi->s + 1)) { pi->s++; return; } else if ('\0' == *pi->s) { if (pi->has_error) { call_error("comment not terminated", pi, __FILE__, __LINE__); } else { raise_error("comment not terminated", pi->str, pi->s); } } } } else if ('/' == *pi->s) { for (; 1; pi->s++) { switch (*pi->s) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { if (pi->has_error) { call_error("invalid comment", pi, __FILE__, __LINE__); } else { raise_error("invalid comment", pi->str, pi->s); } } } static void read_next(ParseInfo pi, const char *key) { VALUE obj; if ((void *)&obj < pi->stack_min) { rb_raise(rb_eSysStackError, "JSON is too deeply nested"); } next_non_white(pi); /* skip white space */ switch (*pi->s) { case '{': read_hash(pi, key); break; case '[': read_array(pi, key); break; case '"': read_str(pi, key); break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': read_num(pi, key); break; case 'I': read_num(pi, key); break; case 't': read_true(pi, key); break; case 'f': read_false(pi, key); break; case 'n': read_nil(pi, key); break; case '\0': return; default: return; } } static void read_hash(ParseInfo pi, const char *key) { const char *ks; if (pi->has_hash_start) { call_no_value(pi->handler, oj_hash_start_id, key); } pi->s++; next_non_white(pi); if ('}' == *pi->s) { pi->s++; } else { while (1) { next_non_white(pi); ks = read_quoted_value(pi); next_non_white(pi); if (':' == *pi->s) { pi->s++; } else { if (pi->has_error) { call_error("invalid format, expected :", pi, __FILE__, __LINE__); } raise_error("invalid format, expected :", pi->str, pi->s); } read_next(pi, ks); next_non_white(pi); if ('}' == *pi->s) { pi->s++; break; } else if (',' == *pi->s) { pi->s++; } else { if (pi->has_error) { call_error("invalid format, expected , or } while in an object", pi, __FILE__, __LINE__); } raise_error("invalid format, expected , or } while in an object", pi->str, pi->s); } } } if (pi->has_hash_end) { call_no_value(pi->handler, oj_hash_end_id, key); } } static void read_array(ParseInfo pi, const char *key) { if (pi->has_array_start) { call_no_value(pi->handler, oj_array_start_id, key); } pi->s++; next_non_white(pi); if (']' == *pi->s) { pi->s++; } else { while (1) { read_next(pi, 0); next_non_white(pi); if (',' == *pi->s) { pi->s++; } else if (']' == *pi->s) { pi->s++; break; } else { if (pi->has_error) { call_error("invalid format, expected , or ] while in an array", pi, __FILE__, __LINE__); } raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s); } } } if (pi->has_array_end) { call_no_value(pi->handler, oj_array_end_id, key); } } static void read_str(ParseInfo pi, const char *key) { char *text; text = read_quoted_value(pi); if (pi->has_add_value) { VALUE s = rb_str_new2(text); s = oj_encode(s); call_add_value(pi->handler, s, key); } } #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif static void read_num(ParseInfo pi, const char *key) { char * start = pi->s; int64_t n = 0; long a = 0; long div = 1; long e = 0; int neg = 0; int eneg = 0; int big = 0; if ('-' == *pi->s) { pi->s++; neg = 1; } else if ('+' == *pi->s) { pi->s++; } if ('I' == *pi->s) { if (0 != strncmp("Infinity", pi->s, 8)) { if (pi->has_error) { call_error("number or other value", pi, __FILE__, __LINE__); } raise_error("number or other value", pi->str, pi->s); } pi->s += 8; if (neg) { if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(-OJ_INFINITY), key); } } else { if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(OJ_INFINITY), key); } } return; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { if (big) { big++; } else { n = n * 10 + (*pi->s - '0'); if (NUM_MAX <= n) { big = 1; } } } if ('.' == *pi->s) { pi->s++; for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { a = a * 10 + (*pi->s - '0'); div *= 10; if (NUM_MAX <= div) { big = 1; } } } if ('e' == *pi->s || 'E' == *pi->s) { pi->s++; if ('-' == *pi->s) { pi->s++; eneg = 1; } else if ('+' == *pi->s) { pi->s++; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { e = e * 10 + (*pi->s - '0'); if (NUM_MAX <= e) { big = 1; } } } if (0 == e && 0 == a && 1 == div) { if (big) { char c = *pi->s; *pi->s = '\0'; if (pi->has_add_value) { call_add_value(pi->handler, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new2(start)), key); } *pi->s = c; } else { if (neg) { n = -n; } if (pi->has_add_value) { call_add_value(pi->handler, LONG2NUM(n), key); } } return; } else { /* decimal */ if (big) { char c = *pi->s; *pi->s = '\0'; if (pi->has_add_value) { call_add_value(pi->handler, rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new2(start)), key); } *pi->s = c; } else { double d = (double)n + (double)a / (double)div; if (neg) { d = -d; } if (1 < big) { e += big - 1; } if (0 != e) { if (eneg) { e = -e; } d *= pow(10.0, e); } if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(d), key); } } } } static void read_true(ParseInfo pi, const char *key) { pi->s++; if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) { if (pi->has_error) { call_error("invalid format, expected 'true'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'true'", pi->str, pi->s); } pi->s += 3; if (pi->has_add_value) { call_add_value(pi->handler, Qtrue, key); } } static void read_false(ParseInfo pi, const char *key) { pi->s++; if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) { if (pi->has_error) { call_error("invalid format, expected 'false'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'false'", pi->str, pi->s); } pi->s += 4; if (pi->has_add_value) { call_add_value(pi->handler, Qfalse, key); } } static void read_nil(ParseInfo pi, const char *key) { pi->s++; if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) { if (pi->has_error) { call_error("invalid format, expected 'null'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'null'", pi->str, pi->s); } pi->s += 3; if (pi->has_add_value) { call_add_value(pi->handler, Qnil, key); } } static uint32_t read_hex(ParseInfo pi, char *h) { uint32_t b = 0; int i; /* TBD this can be made faster with a table */ for (i = 0; i < 4; i++, h++) { b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { pi->s = h; if (pi->has_error) { call_error("invalid hex character", pi, __FILE__, __LINE__); } raise_error("invalid hex character", pi->str, pi->s); } } return b; } static char *unicode_to_chars(ParseInfo pi, char *t, uint32_t code) { if (0x0000007F >= code) { *t = (char)code; } else if (0x000007FF >= code) { *t++ = 0xC0 | (code >> 6); *t = 0x80 | (0x3F & code); } else if (0x0000FFFF >= code) { *t++ = 0xE0 | (code >> 12); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x001FFFFF >= code) { *t++ = 0xF0 | (code >> 18); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x03FFFFFF >= code) { *t++ = 0xF8 | (code >> 24); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x7FFFFFFF >= code) { *t++ = 0xFC | (code >> 30); *t++ = 0x80 | ((code >> 24) & 0x3F); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else { if (pi->has_error) { call_error("invalid Unicode", pi, __FILE__, __LINE__); } raise_error("invalid Unicode", pi->str, pi->s); } return t; } /* Assume the value starts immediately and goes until the quote character is * reached again. Do not read the character after the terminating quote. */ static char *read_quoted_value(ParseInfo pi) { char * value = 0; char * h = pi->s; /* head */ char * t = h; /* tail */ uint32_t code; h++; /* skip quote character */ t++; value = h; for (; '"' != *h; h++, t++) { if ('\0' == *h) { pi->s = h; raise_error("quoted string not terminated", pi->str, pi->s); } else if ('\\' == *h) { h++; switch (*h) { case 'n': *t = '\n'; break; case 'r': *t = '\r'; break; case 't': *t = '\t'; break; case 'f': *t = '\f'; break; case 'b': *t = '\b'; break; case '"': *t = '"'; break; case '/': *t = '/'; break; case '\\': *t = '\\'; break; case 'u': h++; code = read_hex(pi, h); h += 3; if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; h++; if ('\\' != *h || 'u' != *(h + 1)) { pi->s = h; if (pi->has_error) { call_error("invalid escaped character", pi, __FILE__, __LINE__); } raise_error("invalid escaped character", pi->str, pi->s); } h += 2; c2 = read_hex(pi, h); h += 3; c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } t = unicode_to_chars(pi, t, code); break; default: pi->s = h; if (pi->has_error) { call_error("invalid escaped character", pi, __FILE__, __LINE__); } raise_error("invalid escaped character", pi->str, pi->s); break; } } else if (t != h) { *t = *h; } } *t = '\0'; /* terminate value */ pi->s = h + 1; return value; } static void saj_parse(VALUE handler, char *json) { volatile VALUE obj = Qnil; struct _parseInfo pi; if (0 == json) { if (pi.has_error) { call_error("Invalid arg, xml string can not be null", &pi, __FILE__, __LINE__); } raise_error("Invalid arg, xml string can not be null", json, 0); } /* skip UTF-8 BOM if present */ if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) { json += 3; } /* initialize parse info */ pi.str = json; pi.s = json; #if IS_WINDOWS pi.stack_min = (void *)((char *)&obj - (512 * 1024)); /* assume a 1M stack and give half to ruby */ #else { struct rlimit lim; if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) { pi.stack_min = (void *)((char *)&obj - (lim.rlim_cur / 4 * 3)); /* let 3/4ths of the stack be used only */ } else { pi.stack_min = 0; /* indicates not to check stack limit */ } } #endif pi.handler = handler; pi.has_hash_start = rb_respond_to(handler, oj_hash_start_id); pi.has_hash_end = rb_respond_to(handler, oj_hash_end_id); pi.has_array_start = rb_respond_to(handler, oj_array_start_id); pi.has_array_end = rb_respond_to(handler, oj_array_end_id); pi.has_add_value = rb_respond_to(handler, oj_add_value_id); pi.has_error = rb_respond_to(handler, oj_error_id); read_next(&pi, 0); next_non_white(&pi); if ('\0' != *pi.s) { if (pi.has_error) { call_error("invalid format, extra characters", &pi, __FILE__, __LINE__); } else { raise_error("invalid format, extra characters", pi.str, pi.s); } } } /* call-seq: saj_parse(handler, io) * * Parses an IO stream or file containing an JSON document. Raises an exception * if the JSON is malformed. * @param [Oj::Saj] handler Saj (responds to Oj::Saj methods) like handler * @param [IO|String] io IO Object to read from * @deprecated The sc_parse() method along with the ScHandler is the preferred * callback parser. It is slightly faster and handles streams while the * saj_parse() method requires a complete read before parsing. * @see sc_parse */ VALUE oj_saj_parse(int argc, VALUE *argv, VALUE self) { char * json = 0; size_t len = 0; VALUE input = argv[1]; if (argc < 2) { rb_raise(rb_eArgError, "Wrong number of arguments to saj_parse.\n"); } if (rb_type(input) == T_STRING) { // the json string gets modified so make a copy of it len = RSTRING_LEN(input) + 1; json = ALLOC_N(char, len); strcpy(json, StringValuePtr(input)); } else { VALUE clas = rb_obj_class(input); volatile VALUE s; if (oj_stringio_class == clas) { s = rb_funcall2(input, oj_string_id, 0, 0); len = RSTRING_LEN(s) + 1; json = ALLOC_N(char, len); strcpy(json, rb_string_value_cstr((VALUE *)&s)); #if !IS_WINDOWS } else if (rb_cFile == clas && 0 == FIX2INT(rb_funcall(input, oj_pos_id, 0))) { int fd = FIX2INT(rb_funcall(input, oj_fileno_id, 0)); ssize_t cnt; len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); json = ALLOC_N(char, len + 1); if (0 >= (cnt = read(fd, json, len)) || cnt != (ssize_t)len) { rb_raise(rb_eIOError, "failed to read from IO Object."); } json[len] = '\0'; #endif } else if (rb_respond_to(input, oj_read_id)) { s = rb_funcall2(input, oj_read_id, 0, 0); len = RSTRING_LEN(s) + 1; json = ALLOC_N(char, len); strcpy(json, rb_string_value_cstr((VALUE *)&s)); } else { rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object."); } } saj_parse(*argv, json); xfree(json); return Qnil; } oj-3.13.9/ext/oj/string_writer.c0000644000004100000410000003605114136373754016547 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "dump.h" #include "encode.h" extern VALUE Oj; bool string_writer_optimized = false; static void key_check(StrWriter sw, const char *key) { DumpType type = sw->types[sw->depth]; if (0 == key && (ObjectNew == type || ObjectType == type)) { rb_raise(rb_eStandardError, "Can not push onto an Object without a key."); } } static void push_type(StrWriter sw, DumpType type) { if (sw->types_end <= sw->types + sw->depth + 1) { size_t size = (sw->types_end - sw->types) * 2; REALLOC_N(sw->types, char, size); sw->types_end = sw->types + size; } sw->depth++; sw->types[sw->depth] = type; } static void maybe_comma(StrWriter sw) { switch (sw->types[sw->depth]) { case ObjectNew: sw->types[sw->depth] = ObjectType; break; case ArrayNew: sw->types[sw->depth] = ArrayType; break; case ObjectType: case ArrayType: // Always have a few characters available in the out.buf. *sw->out.cur++ = ','; break; } } // Used by stream writer also. void oj_str_writer_init(StrWriter sw, int buf_size) { sw->opts = oj_default_options; sw->depth = 0; sw->types = ALLOC_N(char, 256); sw->types_end = sw->types + 256; *sw->types = '\0'; sw->keyWritten = 0; if (0 == buf_size) { buf_size = 4096; } else if (buf_size < 1024) { buf_size = 1024; } sw->out.buf = ALLOC_N(char, buf_size); sw->out.end = sw->out.buf + buf_size - 10; sw->out.allocated = true; sw->out.cur = sw->out.buf; *sw->out.cur = '\0'; sw->out.circ_cache = NULL; sw->out.circ_cnt = 0; sw->out.hash_cnt = 0; sw->out.opts = &sw->opts; sw->out.indent = sw->opts.indent; sw->out.depth = 0; sw->out.argc = 0; sw->out.argv = NULL; sw->out.caller = 0; sw->out.ropts = NULL; sw->out.omit_nil = oj_default_options.dump_opts.omit_nil; } void oj_str_writer_push_key(StrWriter sw, const char *key) { DumpType type = sw->types[sw->depth]; long size; if (sw->keyWritten) { rb_raise(rb_eStandardError, "Can not push more than one key before pushing a non-key."); } if (ObjectNew != type && ObjectType != type) { rb_raise(rb_eStandardError, "Can only push a key onto an Object."); } size = sw->depth * sw->out.indent + 3; assure_size(&sw->out, size); maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } oj_dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; sw->keyWritten = 1; } void oj_str_writer_push_object(StrWriter sw, const char *key) { if (sw->keyWritten) { sw->keyWritten = 0; assure_size(&sw->out, 1); } else { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; assure_size(&sw->out, size); maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { oj_dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } } *sw->out.cur++ = '{'; push_type(sw, ObjectNew); } void oj_str_writer_push_array(StrWriter sw, const char *key) { if (sw->keyWritten) { sw->keyWritten = 0; assure_size(&sw->out, 1); } else { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; assure_size(&sw->out, size); maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { oj_dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } } *sw->out.cur++ = '['; push_type(sw, ArrayNew); } void oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key) { Out out = &sw->out; if (sw->keyWritten) { sw->keyWritten = 0; } else { long size; key_check(sw, key); size = sw->depth * out->indent + 3; assure_size(out, size); maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { oj_dump_cstr(key, strlen(key), 0, 0, out); *out->cur++ = ':'; } } switch (out->opts->mode) { case StrictMode: oj_dump_strict_val(val, sw->depth, out); break; case NullMode: oj_dump_null_val(val, sw->depth, out); break; case ObjectMode: oj_dump_obj_val(val, sw->depth, out); break; case CompatMode: oj_dump_compat_val(val, sw->depth, out, Yes == out->opts->to_json); break; case RailsMode: oj_dump_rails_val(val, sw->depth, out); break; case CustomMode: oj_dump_custom_val(val, sw->depth, out, true); break; default: oj_dump_custom_val(val, sw->depth, out, true); break; } } void oj_str_writer_push_json(StrWriter sw, const char *json, const char *key) { if (sw->keyWritten) { sw->keyWritten = 0; } else { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; assure_size(&sw->out, size); maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { oj_dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } } oj_dump_raw(json, strlen(json), &sw->out); } void oj_str_writer_pop(StrWriter sw) { long size; DumpType type = sw->types[sw->depth]; if (sw->keyWritten) { sw->keyWritten = 0; rb_raise(rb_eStandardError, "Can not pop after writing a key but no value."); } sw->depth--; if (0 > sw->depth) { rb_raise(rb_eStandardError, "Can not pop with no open array or object."); } size = sw->depth * sw->out.indent + 2; assure_size(&sw->out, size); fill_indent(&sw->out, sw->depth); switch (type) { case ObjectNew: case ObjectType: *sw->out.cur++ = '}'; break; case ArrayNew: case ArrayType: *sw->out.cur++ = ']'; break; } if (0 == sw->depth && 0 <= sw->out.indent) { *sw->out.cur++ = '\n'; } } void oj_str_writer_pop_all(StrWriter sw) { while (0 < sw->depth) { oj_str_writer_pop(sw); } } static void str_writer_free(void *ptr) { StrWriter sw; if (0 == ptr) { return; } sw = (StrWriter)ptr; xfree(sw->out.buf); xfree(sw->types); xfree(ptr); } /* Document-method: new * call-seq: new(io, options) * * Creates a new StringWriter. Options are supported according the the * specified mode or the mode in the default options. Note that if mimic_JSON * or Oj.optimize_rails has not been called then the behavior of the modes may * not be the same as if they were. * * In addition to the regular dump options for the various modes a * _:buffer_size_ option is available. It should be set to a positive * integer. It is considered a hint of how large the initial internal buffer * should be. * * - *io* [_IO_] stream to write to * - *options* [_Hash_] formatting options */ static VALUE str_writer_new(int argc, VALUE *argv, VALUE self) { StrWriter sw = ALLOC(struct _strWriter); oj_str_writer_init(sw, 0); if (1 == argc) { oj_parse_options(argv[0], &sw->opts); } sw->out.argc = argc - 1; sw->out.argv = argv + 1; sw->out.indent = sw->opts.indent; return Data_Wrap_Struct(oj_string_writer_class, 0, str_writer_free, sw); } /* Document-method: push_key * call-seq: push_key(key) * * Pushes a key onto the JSON document. The key will be used for the next push * if currently in a JSON object and ignored otherwise. If a key is provided on * the next push then that new key will be ignored. * - *key* [_String_] the key pending for the next push */ static VALUE str_writer_push_key(VALUE self, VALUE key) { StrWriter sw = (StrWriter)DATA_PTR(self); rb_check_type(key, T_STRING); oj_str_writer_push_key(sw, StringValuePtr(key)); return Qnil; } /* Document-method: push_object * call-seq: push_object(key=nil) * * Pushes an object onto the JSON document. Future pushes will be to this object * until a pop() is called. * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE str_writer_push_object(int argc, VALUE *argv, VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_object(sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_object(sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_object(sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (rb_block_given_p()) { rb_yield(Qnil); oj_str_writer_pop(sw); } return Qnil; } /* Document-method: push_array * call-seq: push_array(key=nil) * * Pushes an array onto the JSON document. Future pushes will be to this object * until a pop() is called. * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE str_writer_push_array(int argc, VALUE *argv, VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_array(sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_array(sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_array(sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (rb_block_given_p()) { rb_yield(Qnil); oj_str_writer_pop(sw); } return Qnil; } /* Document-method: push_value * call-seq: push_value(value, key=nil) * * Pushes a value onto the JSON document. * - *value* [_Object_] value to add to the JSON document * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE str_writer_push_value(int argc, VALUE *argv, VALUE self) { switch (argc) { case 1: oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'."); break; } return Qnil; } /* Document-method: push_json * call-seq: push_json(value, key=nil) * * Pushes a string onto the JSON document. The String must be a valid JSON * encoded string. No additional checking is done to verify the validity of the * string. * - *value* [_Object_] value to add to the JSON document * - *key* [_String_] the key if adding to an object in the JSON document */ static VALUE str_writer_push_json(int argc, VALUE *argv, VALUE self) { rb_check_type(argv[0], T_STRING); switch (argc) { case 1: oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'."); break; } return Qnil; } /* Document-method: pop * call-seq: pop() * * Pops up a level in the JSON document closing the array or object that is * currently open. */ static VALUE str_writer_pop(VALUE self) { oj_str_writer_pop((StrWriter)DATA_PTR(self)); return Qnil; } /* Document-method: pop_all * call-seq: pop_all() * * Pops all level in the JSON document closing all the array or object that is * currently open. */ static VALUE str_writer_pop_all(VALUE self) { oj_str_writer_pop_all((StrWriter)DATA_PTR(self)); return Qnil; } /* Document-method: reset * call-seq: reset() * * Reset the writer back to the empty state. */ static VALUE str_writer_reset(VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); sw->depth = 0; *sw->types = '\0'; sw->keyWritten = 0; sw->out.cur = sw->out.buf; *sw->out.cur = '\0'; return Qnil; } /* Document-method: to_s * call-seq: to_s() * * Returns the JSON document string in what ever state the construction is at. * * *return* [_String_] */ static VALUE str_writer_to_s(VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); VALUE rstr = rb_str_new(sw->out.buf, sw->out.cur - sw->out.buf); return oj_encode(rstr); } /* Document-method: as_json * call-seq: as_json() * * Returns the contents of the writer as a JSON element. If called from inside * an array or hash by Oj the raw buffer will be used othersize a more * inefficient parse of the contents and a return of the result is * completed. The parse uses the trict mode. * * *return* [_Hash_|_Array_|_String_|_Integer_|_Float_|_True_|_False_|_nil|) */ static VALUE str_writer_as_json(VALUE self) { if (string_writer_optimized) { return self; } return rb_hash_new(); } /* Document-class: Oj::StringWriter * * Supports building a JSON document one element at a time. Build the document * by pushing values into the document. Pushing an array or an object will * create that element in the JSON document and subsequent pushes will add the * elements to that array or object until a pop() is called. When complete * calling to_s() will return the JSON document. Note that calling to_s() before * construction is complete will return the document in it's current state. */ void oj_string_writer_init() { oj_string_writer_class = rb_define_class_under(Oj, "StringWriter", rb_cObject); rb_define_module_function(oj_string_writer_class, "new", str_writer_new, -1); rb_define_method(oj_string_writer_class, "push_key", str_writer_push_key, 1); rb_define_method(oj_string_writer_class, "push_object", str_writer_push_object, -1); rb_define_method(oj_string_writer_class, "push_array", str_writer_push_array, -1); rb_define_method(oj_string_writer_class, "push_value", str_writer_push_value, -1); rb_define_method(oj_string_writer_class, "push_json", str_writer_push_json, -1); rb_define_method(oj_string_writer_class, "pop", str_writer_pop, 0); rb_define_method(oj_string_writer_class, "pop_all", str_writer_pop_all, 0); rb_define_method(oj_string_writer_class, "reset", str_writer_reset, 0); rb_define_method(oj_string_writer_class, "to_s", str_writer_to_s, 0); rb_define_method(oj_string_writer_class, "raw_json", str_writer_to_s, 0); rb_define_method(oj_string_writer_class, "as_json", str_writer_as_json, 0); } oj-3.13.9/ext/oj/debug.c0000644000004100000410000001017614136373754014733 0ustar www-datawww-data// Copyright (c) 2021, Peter Ohler, All rights reserved. #include "parser.h" static void add_null(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_null at top\n"); break; case ARRAY_FUN: printf("*** add_null to array\n"); break; case OBJECT_FUN: printf("*** add_null with '%s'\n", buf_str(&p->key)); break; } } static void add_true(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_true at top\n"); break; case ARRAY_FUN: printf("*** add_true to array\n"); break; case OBJECT_FUN: printf("*** add_true with '%s'\n", buf_str(&p->key)); break; } } static void add_false(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_false at top\n"); break; case ARRAY_FUN: printf("*** add_false to array\n"); break; case OBJECT_FUN: printf("*** add_false with '%s'\n", buf_str(&p->key)); break; } } static void add_int(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_int %lld at top\n", (long long)p->num.fixnum); break; case ARRAY_FUN: printf("*** add_int %lld to array\n", (long long)p->num.fixnum); break; case OBJECT_FUN: printf("*** add_int %lld with '%s'\n", (long long)p->num.fixnum, buf_str(&p->key)); break; } } static void add_float(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_float %Lf at top\n", p->num.dub); break; case ARRAY_FUN: printf("*** add_float %Lf to array\n", p->num.dub); break; case OBJECT_FUN: printf("*** add_float %Lf with '%s'\n", p->num.dub, buf_str(&p->key)); break; } } static void add_big(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_big %s at top\n", buf_str(&p->buf)); break; case ARRAY_FUN: printf("*** add_big %s to array\n", buf_str(&p->buf)); break; case OBJECT_FUN: printf("*** add_big %s with '%s'\n", buf_str(&p->buf), buf_str(&p->key)); break; } } static void add_str(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** add_str '%s' at top\n", buf_str(&p->buf)); break; case ARRAY_FUN: printf("*** add_str '%s' to array\n", buf_str(&p->buf)); break; case OBJECT_FUN: printf("*** add_str '%s' with '%s'\n", buf_str(&p->buf), buf_str(&p->key)); break; } } static void open_array(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** open_array at top\n"); break; case ARRAY_FUN: printf("*** open_array to array\n"); break; case OBJECT_FUN: printf("*** open_array with '%s'\n", buf_str(&p->key)); break; } } static void close_array(struct _ojParser *p) { printf("*** close_array\n"); } static void open_object(struct _ojParser *p) { switch (p->stack[p->depth]) { case TOP_FUN: printf("*** open_object at top\n"); break; case ARRAY_FUN: printf("*** open_object to array\n"); break; case OBJECT_FUN: printf("*** open_object with '%s'\n", buf_str(&p->key)); break; } } static void close_object(struct _ojParser *p) { printf("*** close_object\n"); } static VALUE option(ojParser p, const char *key, VALUE value) { rb_raise(rb_eArgError, "%s is not an option for the debug delegate", key); return Qnil; } static VALUE result(struct _ojParser *p) { return Qnil; } static void start(struct _ojParser *p) { printf("*** start\n"); } static void dfree(struct _ojParser *p) { } static void mark(struct _ojParser *p) { } void oj_set_parser_debug(ojParser p) { Funcs end = p->funcs + 3; Funcs f; for (f = p->funcs; f < end; f++) { f->add_null = add_null; f->add_true = add_true; f->add_false = add_false; f->add_int = add_int; f->add_float = add_float; f->add_big = add_big; f->add_str = add_str; f->open_array = open_array; f->close_array = close_array; f->open_object = open_object; f->close_object = close_object; } p->option = option; p->result = result; p->free = dfree; p->mark = mark; p->start = start; } oj-3.13.9/ext/oj/parse.h0000644000004100000410000000705414136373754014765 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_PARSE_H #define OJ_PARSE_H #include #include #include #include "circarray.h" #include "oj.h" #include "reader.h" #include "ruby.h" #include "rxclass.h" #include "val_stack.h" struct _rxClass; typedef struct _numInfo { int64_t i; int64_t num; int64_t div; int64_t di; const char *str; size_t len; long exp; int big; int infinity; int nan; int neg; int has_exp; int no_big; int bigdec_load; } * NumInfo; typedef struct _parseInfo { // used for the string parser const char *json; const char *cur; const char *end; // used for the stream parser struct _reader rd; struct _err err; struct _options options; VALUE handler; struct _valStack stack; CircArray circ_array; struct _rxClass str_rx; int expect_value; int max_depth; // just for the json gem VALUE proc; VALUE (*start_hash)(struct _parseInfo *pi); void (*end_hash)(struct _parseInfo *pi); VALUE (*hash_key)(struct _parseInfo *pi, const char *key, size_t klen); void (*hash_set_cstr)(struct _parseInfo *pi, Val kval, const char * str, size_t len, const char * orig); void (*hash_set_num)(struct _parseInfo *pi, Val kval, NumInfo ni); void (*hash_set_value)(struct _parseInfo *pi, Val kval, VALUE value); VALUE (*start_array)(struct _parseInfo *pi); void (*end_array)(struct _parseInfo *pi); void (*array_append_cstr)(struct _parseInfo *pi, const char *str, size_t len, const char *orig); void (*array_append_num)(struct _parseInfo *pi, NumInfo ni); void (*array_append_value)(struct _parseInfo *pi, VALUE value); void (*add_cstr)(struct _parseInfo *pi, const char *str, size_t len, const char *orig); void (*add_num)(struct _parseInfo *pi, NumInfo ni); void (*add_value)(struct _parseInfo *pi, VALUE val); VALUE err_class; bool has_callbacks; } * ParseInfo; extern void oj_parse2(ParseInfo pi); extern void oj_set_error_at(ParseInfo pi, VALUE err_clas, const char *file, int line, const char *format, ...); extern VALUE oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len, int yieldOk); extern VALUE oj_num_as_value(NumInfo ni); extern void oj_set_strict_callbacks(ParseInfo pi); extern void oj_set_object_callbacks(ParseInfo pi); extern void oj_set_compat_callbacks(ParseInfo pi); extern void oj_set_custom_callbacks(ParseInfo pi); extern void oj_set_wab_callbacks(ParseInfo pi); extern void oj_sparse2(ParseInfo pi); extern VALUE oj_pi_sparse(int argc, VALUE *argv, ParseInfo pi, int fd); extern VALUE oj_cstr_to_value(const char *str, size_t len, size_t cache_str); extern VALUE oj_calc_hash_key(ParseInfo pi, Val parent); static inline void parse_info_init(ParseInfo pi) { memset(pi, 0, sizeof(struct _parseInfo)); } static inline bool empty_ok(Options options) { switch (options->mode) { case ObjectMode: case WabMode: return true; case CompatMode: case RailsMode: return false; case StrictMode: case NullMode: case CustomMode: default: break; } return Yes == options->empty_string; } #endif /* OJ_PARSE_H */ oj-3.13.9/ext/oj/mimic_json.c0000644000004100000410000010023614136373754015771 0ustar www-datawww-data// Copyright (c) 2012, 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #include "dump.h" #include "encode.h" #include "oj.h" #include "parse.h" extern const char oj_json_class[]; VALUE oj_array_nl_sym; VALUE oj_ascii_only_sym; VALUE oj_json_generator_error_class; VALUE oj_json_parser_error_class; VALUE oj_max_nesting_sym; VALUE oj_object_nl_sym; VALUE oj_space_before_sym; VALUE oj_space_sym; static VALUE state_class = Qundef; // mimic JSON documentation /* Document-module: JSON::Ext * * The Ext module is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-class: JSON::Ext::Parser * * The JSON::Ext::Parser is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-class: JSON::Ext::Generator * * The JSON::Ext::Generator is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-method: parser= * call-seq: parser=(parser) * * Does nothing other than provide compatibility. * - *parser* [_Object_] ignored */ /* Document-method: generator= * call-seq: generator=(generator) * * Does nothing other than provide compatibility. * - *generator* [_Object_] ignored */ VALUE oj_get_json_err_class(const char *err_classname) { volatile VALUE json_module; volatile VALUE clas; volatile VALUE json_error_class; if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) { json_module = rb_const_get_at(rb_cObject, rb_intern("JSON")); } else { json_module = rb_define_module("JSON"); } if (rb_const_defined_at(json_module, rb_intern("JSONError"))) { json_error_class = rb_const_get(json_module, rb_intern("JSONError")); } else { json_error_class = rb_define_class_under(json_module, "JSONError", rb_eStandardError); } if (0 == strcmp(err_classname, "JSONError")) { clas = json_error_class; } else { if (rb_const_defined_at(json_module, rb_intern(err_classname))) { clas = rb_const_get(json_module, rb_intern(err_classname)); } else { clas = rb_define_class_under(json_module, err_classname, json_error_class); } } return clas; } void oj_parse_mimic_dump_options(VALUE ropts, Options copts) { VALUE v; size_t len; if (T_HASH != rb_type(ropts)) { if (rb_respond_to(ropts, oj_to_hash_id)) { ropts = rb_funcall(ropts, oj_to_hash_id, 0); } else if (rb_respond_to(ropts, oj_to_h_id)) { ropts = rb_funcall(ropts, oj_to_h_id, 0); } else if (Qnil == ropts) { return; } else { rb_raise(rb_eArgError, "options must be a hash."); } } v = rb_hash_lookup(ropts, oj_max_nesting_sym); if (Qtrue == v) { copts->dump_opts.max_depth = 100; } else if (Qfalse == v || Qnil == v) { copts->dump_opts.max_depth = MAX_DEPTH; } else if (T_FIXNUM == rb_type(v)) { copts->dump_opts.max_depth = NUM2INT(v); if (0 >= copts->dump_opts.max_depth) { copts->dump_opts.max_depth = MAX_DEPTH; } } if (Qnil != (v = rb_hash_lookup(ropts, oj_allow_nan_sym))) { if (Qtrue == v) { copts->dump_opts.nan_dump = WordNan; } else { copts->dump_opts.nan_dump = RaiseNan; } } if (Qnil != (v = rb_hash_lookup(ropts, oj_indent_sym))) { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "indent string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.indent_str)); } strcpy(copts->dump_opts.indent_str, StringValuePtr(v)); copts->dump_opts.indent_size = (uint8_t)len; copts->dump_opts.use = true; } if (Qnil != (v = rb_hash_lookup(ropts, oj_space_sym))) { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.after_sep) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "space string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.after_sep)); } strcpy(copts->dump_opts.after_sep, StringValuePtr(v)); copts->dump_opts.after_size = (uint8_t)len; copts->dump_opts.use = true; } if (Qnil != (v = rb_hash_lookup(ropts, oj_space_before_sym))) { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.before_sep) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "space_before string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.before_sep)); } strcpy(copts->dump_opts.before_sep, StringValuePtr(v)); copts->dump_opts.before_size = (uint8_t)len; copts->dump_opts.use = true; } if (Qnil != (v = rb_hash_lookup(ropts, oj_object_nl_sym))) { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.hash_nl) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "object_nl string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.hash_nl)); } strcpy(copts->dump_opts.hash_nl, StringValuePtr(v)); copts->dump_opts.hash_size = (uint8_t)len; copts->dump_opts.use = true; } if (Qnil != (v = rb_hash_lookup(ropts, oj_array_nl_sym))) { rb_check_type(v, T_STRING); if (sizeof(copts->dump_opts.array_nl) <= (len = RSTRING_LEN(v))) { rb_raise(rb_eArgError, "array_nl string is limited to %lu characters.", (unsigned long)sizeof(copts->dump_opts.array_nl)); } strcpy(copts->dump_opts.array_nl, StringValuePtr(v)); copts->dump_opts.array_size = (uint8_t)len; copts->dump_opts.use = true; } if (Qnil != (v = rb_hash_lookup(ropts, oj_quirks_mode_sym))) { copts->quirks_mode = (Qtrue == v) ? Yes : No; } if (Qnil != (v = rb_hash_lookup(ropts, oj_ascii_only_sym))) { // generate seems to assume anything except nil and false are true. if (Qfalse == v) { copts->escape_mode = JXEsc; } else { copts->escape_mode = ASCIIEsc; } } } static int mimic_limit_arg(VALUE a) { if (Qnil == a || T_FIXNUM != rb_type(a)) { return -1; } return NUM2INT(a); } /* Document-method: dump * call-seq: dump(obj, anIO=nil, limit=nil) * * Encodes an object as a JSON String. * * - *obj* [_Object_] object to convert to encode as JSON * - *anIO* [_IO_] an IO that allows writing * - *limit* [_Fixnum_] ignored * * Returns [_String_] a JSON string. */ static VALUE mimic_dump(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct _out out; struct _options copts = oj_default_options; VALUE rstr; VALUE active_hack[1]; copts.str_rx.head = NULL; copts.str_rx.tail = NULL; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.caller = CALLER_DUMP; copts.escape_mode = JXEsc; copts.mode = CompatMode; /* seems like this is not correct if (No == copts.nilnil && Qnil == *argv) { rb_raise(rb_eTypeError, "nil not allowed."); } */ copts.dump_opts.max_depth = MAX_DEPTH; // when using dump there is no limit out.omit_nil = copts.dump_opts.omit_nil; if (2 <= argc) { int limit; // The json gem take a more liberal approach to optional // arguments. Expected are (obj, anIO=nil, limit=nil) yet the io // argument can be left off completely and the 2nd argument is then // the limit. if (0 <= (limit = mimic_limit_arg(argv[1]))) { copts.dump_opts.max_depth = limit; } if (3 <= argc && 0 <= (limit = mimic_limit_arg(argv[2]))) { copts.dump_opts.max_depth = limit; } } // ActiveSupport in active_support/core_ext/object/json.rb check the // optional argument type to to_json and it the argument is a // ::JSON::State it calls the JSON gem code otherwise it calls the active // support encoder code. To make sure the desired branch is called a // default ::JSON::State argument is passed in. Basically a hack to get // around the active support hack so two wrongs make a right this time. active_hack[0] = rb_funcall(state_class, oj_new_id, 0); oj_dump_obj_to_json_using_params(*argv, &copts, &out, 1, active_hack); if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (2 <= argc && Qnil != argv[1] && rb_respond_to(argv[1], oj_write_id)) { VALUE io = argv[1]; VALUE args[1]; *args = rstr; rb_funcall2(io, oj_write_id, 1, args); rstr = io; } if (out.allocated) { xfree(out.buf); } return rstr; } // This is the signature for the hash_foreach callback also. static int mimic_walk(VALUE key, VALUE obj, VALUE proc) { switch (rb_type(obj)) { case T_HASH: rb_hash_foreach(obj, mimic_walk, proc); break; case T_ARRAY: { size_t cnt = RARRAY_LEN(obj); size_t i; for (i = 0; i < cnt; i++) { mimic_walk(Qnil, rb_ary_entry(obj, i), proc); } break; } default: break; } if (Qnil == proc) { if (rb_block_given_p()) { rb_yield(obj); } } else { VALUE args[1]; *args = obj; rb_proc_call_with_block(proc, 1, args, Qnil); } return ST_CONTINUE; } /* Document-method: restore * call-seq: restore(source, proc=nil) * * Loads a Ruby Object from a JSON source that can be either a String or an * IO. If Proc is given or a block is provided it is called with each nested * element of the loaded Object. * * - *source* [_String_|IO] JSON source * - *proc* [_Proc_] to yield to on each element or nil * * Returns [_Object_] the decoded Object. */ /* Document-method: load * call-seq: load(source, proc=nil) * * Loads a Ruby Object from a JSON source that can be either a String or an * IO. If Proc is given or a block is provided it is called with each nested * element of the loaded Object. * * - *source* [_String_|IO] JSON source * - *proc* [_Proc_] to yield to on each element or nil * * Returns [_Object_] the decode Object. */ static VALUE mimic_load(int argc, VALUE *argv, VALUE self) { VALUE obj; VALUE p = Qnil; obj = oj_compat_load(argc, argv, self); if (2 <= argc) { if (rb_cProc == rb_obj_class(argv[1])) { p = argv[1]; } else if (3 <= argc) { if (rb_cProc == rb_obj_class(argv[2])) { p = argv[2]; } } } mimic_walk(Qnil, obj, p); return obj; } /* Document-method: [] * call-seq: [](obj, opts={}) * * If the obj argument is a String then it is assumed to be a JSON String and * parsed otherwise the obj is encoded as a JSON String. * * - *obj* [_String_|Hash|Array] object to convert * - *opts* [_Hash_] same options as either generate or parse * * Returns [_Object_] */ static VALUE mimic_dump_load(int argc, VALUE *argv, VALUE self) { if (1 > argc) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)"); } else if (T_STRING == rb_type(*argv)) { return mimic_load(argc, argv, self); } else { return mimic_dump(argc, argv, self); } return Qnil; } static VALUE mimic_generate_core(int argc, VALUE *argv, Options copts) { char buf[4096]; struct _out out; VALUE rstr; memset(buf, 0, sizeof(buf)); out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.omit_nil = copts->dump_opts.omit_nil; out.caller = CALLER_GENERATE; // For obj.to_json or generate nan is not allowed but if called from dump // it is. copts->dump_opts.nan_dump = RaiseNan; copts->mode = CompatMode; copts->to_json = Yes; if (2 == argc && Qnil != argv[1]) { oj_parse_mimic_dump_options(argv[1], copts); } /* seems like this is not correct if (No == copts->nilnil && Qnil == *argv) { rb_raise(rb_eTypeError, "nil not allowed."); } */ if (1 < argc) { oj_dump_obj_to_json_using_params(*argv, copts, &out, argc - 1, argv + 1); } else { VALUE active_hack[1]; if (Qundef == state_class) { oj_define_mimic_json(0, NULL, Qnil); } active_hack[0] = rb_funcall(state_class, oj_new_id, 0); oj_dump_obj_to_json_using_params(*argv, copts, &out, 1, active_hack); } if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (out.allocated) { xfree(out.buf); } return rstr; } /* Document-method: fast_generate * call-seq: fast_generate(obj, opts=nil) * Same as generate(). * @see generate */ /* Document-method: generate * call-seq: generate(obj, opts=nil) * * Encode obj as a JSON String. The obj argument must be a Hash, Array, or * respond to to_h or to_json. Options other than those listed such as * +:allow_nan+ or +:max_nesting+ are ignored. * * - *obj* [_Object_|Hash|Array] object to convert to a JSON String * - *opts* [_Hash_] options * - - *:indent* [_String_] String to use for indentation. * - *:space* [_String_] String placed after a , or : delimiter * - *:space_before* [_String_] String placed before a : delimiter * - *:object_nl* [_String_] String placed after a JSON object * - *:array_nl* [_String_] String placed after a JSON array * - *:ascii_only* [_Boolean_] if not nil or false then use only ascii characters in the output. * Note JSON.generate does support this even if it is not documented. * * Returns [_String_] generated JSON. */ VALUE oj_mimic_generate(int argc, VALUE *argv, VALUE self) { struct _options copts = oj_default_options; copts.str_rx.head = NULL; copts.str_rx.tail = NULL; return mimic_generate_core(argc, argv, &copts); } /* Document-method: pretty_generate * call-seq: pretty_generate(obj, opts=nil) * * Same as generate() but with different defaults for the spacing options. * @see generate * * Return [_String_] the generated JSON. */ VALUE oj_mimic_pretty_generate(int argc, VALUE *argv, VALUE self) { struct _options copts = oj_default_options; VALUE rargs[2]; volatile VALUE h; // Some (all?) json gem to_json methods need a State instance and not just // a Hash. I haven't dug deep enough to find out why but using a State // instance and not a Hash gives the desired behavior. *rargs = *argv; if (1 == argc) { h = rb_hash_new(); } else { h = argv[1]; } if (!oj_hash_has_key(h, oj_indent_sym)) { rb_hash_aset(h, oj_indent_sym, rb_str_new2(" ")); } if (!oj_hash_has_key(h, oj_space_before_sym)) { rb_hash_aset(h, oj_space_before_sym, rb_str_new2("")); } if (!oj_hash_has_key(h, oj_space_sym)) { rb_hash_aset(h, oj_space_sym, rb_str_new2(" ")); } if (!oj_hash_has_key(h, oj_object_nl_sym)) { rb_hash_aset(h, oj_object_nl_sym, rb_str_new2("\n")); } if (!oj_hash_has_key(h, oj_array_nl_sym)) { rb_hash_aset(h, oj_array_nl_sym, rb_str_new2("\n")); } if (Qundef == state_class) { oj_define_mimic_json(0, NULL, Qnil); } rargs[1] = rb_funcall(state_class, oj_new_id, 1, h); copts.str_rx.head = NULL; copts.str_rx.tail = NULL; strcpy(copts.dump_opts.indent_str, " "); copts.dump_opts.indent_size = (uint8_t)strlen(copts.dump_opts.indent_str); strcpy(copts.dump_opts.before_sep, ""); copts.dump_opts.before_size = (uint8_t)strlen(copts.dump_opts.before_sep); strcpy(copts.dump_opts.after_sep, " "); copts.dump_opts.after_size = (uint8_t)strlen(copts.dump_opts.after_sep); strcpy(copts.dump_opts.hash_nl, "\n"); copts.dump_opts.hash_size = (uint8_t)strlen(copts.dump_opts.hash_nl); strcpy(copts.dump_opts.array_nl, "\n"); copts.dump_opts.array_size = (uint8_t)strlen(copts.dump_opts.array_nl); copts.dump_opts.use = true; return mimic_generate_core(2, rargs, &copts); } static VALUE mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) { struct _parseInfo pi; VALUE ropts; VALUE args[1]; rb_scan_args(argc, argv, "11", NULL, &ropts); parse_info_init(&pi); oj_set_compat_callbacks(&pi); pi.err_class = oj_json_parser_error_class; // pi.err_class = Qnil; pi.options = oj_default_options; pi.options.auto_define = No; pi.options.quirks_mode = Yes; pi.options.allow_invalid = Yes; pi.options.empty_string = No; pi.options.create_ok = No; pi.options.allow_nan = (bang ? Yes : No); pi.options.nilnil = No; pi.options.bigdec_load = RubyDec; pi.options.mode = CompatMode; pi.max_depth = 100; if (Qnil != ropts) { VALUE v; if (T_HASH != rb_type(ropts)) { rb_raise(rb_eArgError, "options must be a hash."); } if (Qnil != (v = rb_hash_lookup(ropts, oj_symbolize_names_sym))) { pi.options.sym_key = (Qtrue == v) ? Yes : No; } if (Qnil != (v = rb_hash_lookup(ropts, oj_quirks_mode_sym))) { pi.options.quirks_mode = (Qtrue == v) ? Yes : No; } if (Qnil != (v = rb_hash_lookup(ropts, oj_create_additions_sym))) { pi.options.create_ok = (Qtrue == v) ? Yes : No; } if (Qnil != (v = rb_hash_lookup(ropts, oj_allow_nan_sym))) { pi.options.allow_nan = (Qtrue == v) ? Yes : No; } if (oj_hash_has_key(ropts, oj_hash_class_sym)) { if (Qnil == (v = rb_hash_lookup(ropts, oj_hash_class_sym))) { pi.options.hash_class = Qnil; } else { rb_check_type(v, T_CLASS); pi.options.hash_class = v; } } if (oj_hash_has_key(ropts, oj_object_class_sym)) { if (Qnil == (v = rb_hash_lookup(ropts, oj_object_class_sym))) { pi.options.hash_class = Qnil; } else { rb_check_type(v, T_CLASS); pi.options.hash_class = v; } } if (oj_hash_has_key(ropts, oj_array_class_sym)) { if (Qnil == (v = rb_hash_lookup(ropts, oj_array_class_sym))) { pi.options.array_class = Qnil; } else { rb_check_type(v, T_CLASS); pi.options.array_class = v; } } if (oj_hash_has_key(ropts, oj_decimal_class_sym)) { pi.options.compat_bigdec = (oj_bigdecimal_class == rb_hash_lookup(ropts, oj_decimal_class_sym)); } v = rb_hash_lookup(ropts, oj_max_nesting_sym); if (Qtrue == v) { pi.max_depth = 100; } else if (Qfalse == v || Qnil == v) { pi.max_depth = 0; } else if (T_FIXNUM == rb_type(v)) { pi.max_depth = NUM2INT(v); } oj_parse_opt_match_string(&pi.options.str_rx, ropts); if (Yes == pi.options.create_ok && Yes == pi.options.sym_key) { rb_raise(rb_eArgError, ":symbolize_names and :create_additions can not both be true."); } } *args = *argv; if (T_STRING == rb_type(*args)) { return oj_pi_parse(1, args, &pi, 0, 0, false); } else { return oj_pi_sparse(1, args, &pi, 0); } } /* Document-method: parse * call-seq: parse(source, opts=nil) * * Parses a JSON String or IO into a Ruby Object. Options other than those * listed such as +:allow_nan+ or +:max_nesting+ are ignored. +:object_class+ and * +:array_object+ are not supported. * * - *source* [_String_|IO] source to parse * - *opts* [_Hash_] options * - *:symbolize* [Boolean] _names flag indicating JSON object keys should be Symbols instead of * Strings * - *:create_additions* [Boolean] flag indicating a key matching +create_id+ in a JSON object * should trigger the creation of Ruby Object * * Returns [Object] * @see create_id= */ VALUE oj_mimic_parse(int argc, VALUE *argv, VALUE self) { return mimic_parse_core(argc, argv, self, false); } /* Document-method: parse! * call-seq: parse!(source, opts=nil) * * Same as parse(). * @see parse */ static VALUE mimic_parse_bang(int argc, VALUE *argv, VALUE self) { return mimic_parse_core(argc, argv, self, true); } /* Document-method: recurse_proc * call-seq: recurse_proc(obj, &proc) * * Yields to the proc for every element in the obj recursively. * * - *obj* [_Hash_|Array] object to walk * - *proc* [_Proc_] to yield to on each element */ static VALUE mimic_recurse_proc(VALUE self, VALUE obj) { rb_need_block(); mimic_walk(Qnil, obj, Qnil); return Qnil; } /* Document-method: create_id= * call-seq: create_id=(id) * * Sets the create_id tag to look for in JSON document. That key triggers the * creation of a class with the same name. * * - *id* [_nil_|String] new create_id * * Returns [_String_] the id. */ static VALUE mimic_set_create_id(VALUE self, VALUE id) { Check_Type(id, T_STRING); if (NULL != oj_default_options.create_id) { if (oj_json_class != oj_default_options.create_id) { xfree((char *)oj_default_options.create_id); } oj_default_options.create_id = NULL; oj_default_options.create_id_len = 0; } if (Qnil != id) { size_t len = RSTRING_LEN(id) + 1; oj_default_options.create_id = ALLOC_N(char, len); strcpy((char *)oj_default_options.create_id, StringValuePtr(id)); oj_default_options.create_id_len = len - 1; } return id; } /* Document-method: create_id * call-seq: create_id() * * Returns [_String_] the create_id. */ static VALUE mimic_create_id(VALUE self) { if (NULL != oj_default_options.create_id) { return rb_utf8_str_new(oj_default_options.create_id, oj_default_options.create_id_len); } return rb_str_new_cstr(oj_json_class); } static struct _options mimic_object_to_json_options = {0, // indent No, // circular No, // auto_define No, // sym_key JXEsc, // escape_mode CompatMode, // mode No, // class_cache RubyTime, // time_format No, // bigdec_as_num RubyDec, // bigdec_load false, // compat_bigdec No, // to_hash No, // to_json No, // as_json No, // raw_json No, // nilnil No, // empty_string Yes, // allow_gc Yes, // quirks_mode Yes, // allow_invalid No, // create_ok No, // allow_nan No, // trace No, // safe false, // sec_prec_set No, // ignore_under Yes, // cache_keys 0, // cache_str 0, // int_range_min 0, // int_range_max oj_json_class, // create_id 10, // create_id_len 3, // sec_prec 0, // float_prec "%0.16g", // float_fmt Qnil, // hash_class Qnil, // array_class { // dump_opts false, // use "", // indent "", // before_sep "", // after_sep "", // hash_nl "", // array_nl 0, // indent_size 0, // before_size 0, // after_size 0, // hash_size 0, // array_size RaiseNan, // nan_dump false, // omit_nil 100, // max_depth }, { // str_rx NULL, // head NULL, // tail {'\0'}, // err }}; static VALUE mimic_object_to_json(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct _out out; VALUE rstr; struct _options copts = oj_default_options; copts.str_rx.head = NULL; copts.str_rx.tail = NULL; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = false; out.omit_nil = copts.dump_opts.omit_nil; copts.mode = CompatMode; copts.to_json = No; if (1 <= argc && Qnil != argv[0]) { oj_parse_mimic_dump_options(argv[0], &copts); } // To be strict the mimic_object_to_json_options should be used but people // seem to prefer the option of changing that. // oj_dump_obj_to_json(self, &mimic_object_to_json_options, &out); oj_dump_obj_to_json_using_params(self, &copts, &out, argc, argv); if (NULL == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (out.allocated) { xfree(out.buf); } return rstr; } /* Document-method: state * call-seq: state() * * Returns [_JSON::State_] the JSON::State class. */ static VALUE mimic_state(VALUE self) { return state_class; } void oj_mimic_json_methods(VALUE json) { VALUE json_error; VALUE generator; VALUE ext; rb_define_module_function(json, "create_id=", mimic_set_create_id, 1); rb_define_module_function(json, "create_id", mimic_create_id, 0); rb_define_module_function(json, "dump", mimic_dump, -1); rb_define_module_function(json, "load", mimic_load, -1); rb_define_module_function(json, "restore", mimic_load, -1); rb_define_module_function(json, "recurse_proc", mimic_recurse_proc, 1); rb_define_module_function(json, "[]", mimic_dump_load, -1); rb_define_module_function(json, "generate", oj_mimic_generate, -1); rb_define_module_function(json, "fast_generate", oj_mimic_generate, -1); rb_define_module_function(json, "pretty_generate", oj_mimic_pretty_generate, -1); // For older versions of JSON, the deprecated unparse methods. rb_define_module_function(json, "unparse", oj_mimic_generate, -1); rb_define_module_function(json, "fast_unparse", oj_mimic_generate, -1); rb_define_module_function(json, "pretty_unparse", oj_mimic_pretty_generate, -1); rb_define_module_function(json, "parse", oj_mimic_parse, -1); rb_define_module_function(json, "parse!", mimic_parse_bang, -1); rb_define_module_function(json, "state", mimic_state, 0); if (rb_const_defined_at(json, rb_intern("JSONError"))) { json_error = rb_const_get(json, rb_intern("JSONError")); } else { json_error = rb_define_class_under(json, "JSONError", rb_eStandardError); } if (rb_const_defined_at(json, rb_intern("ParserError"))) { oj_json_parser_error_class = rb_const_get(json, rb_intern("ParserError")); } else { oj_json_parser_error_class = rb_define_class_under(json, "ParserError", json_error); } if (rb_const_defined_at(json, rb_intern("GeneratorError"))) { oj_json_generator_error_class = rb_const_get(json, rb_intern("GeneratorError")); } else { oj_json_generator_error_class = rb_define_class_under(json, "GeneratorError", json_error); } if (rb_const_defined_at(json, rb_intern("NestingError"))) { rb_const_get(json, rb_intern("NestingError")); } else { rb_define_class_under(json, "NestingError", json_error); } if (rb_const_defined_at(json, rb_intern("Ext"))) { ext = rb_const_get_at(json, rb_intern("Ext")); } else { ext = rb_define_module_under(json, "Ext"); } if (rb_const_defined_at(ext, rb_intern("Generator"))) { generator = rb_const_get_at(ext, rb_intern("Generator")); } else { generator = rb_define_module_under(ext, "Generator"); } if (!rb_const_defined_at(generator, rb_intern("State"))) { rb_require("oj/state"); } // Pull in the JSON::State mimic file. state_class = rb_const_get_at(generator, rb_intern("State")); rb_gc_register_mark_object(state_class); } /* Document-module: JSON * * A mimic of the json gem module. */ VALUE oj_define_mimic_json(int argc, VALUE *argv, VALUE self) { VALUE dummy; VALUE verbose; VALUE json; // Either set the paths to indicate JSON has been loaded or replaces the // methods if it has been loaded. if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) { json = rb_const_get_at(rb_cObject, rb_intern("JSON")); } else { json = rb_define_module("JSON"); } verbose = rb_gv_get("$VERBOSE"); rb_gv_set("$VERBOSE", Qfalse); rb_define_module_function(rb_cObject, "JSON", mimic_dump_load, -1); dummy = rb_gv_get("$LOADED_FEATURES"); if (rb_type(dummy) == T_ARRAY) { rb_ary_push(dummy, rb_str_new2("json")); if (0 < argc) { VALUE mimic_args[1]; *mimic_args = *argv; rb_funcall2(Oj, rb_intern("mimic_loaded"), 1, mimic_args); } else { rb_funcall2(Oj, rb_intern("mimic_loaded"), 0, 0); } } oj_mimic_json_methods(json); rb_define_method(rb_cObject, "to_json", mimic_object_to_json, -1); rb_gv_set("$VERBOSE", verbose); oj_default_options = mimic_object_to_json_options; oj_default_options.to_json = Yes; return json; } oj-3.13.9/ext/oj/reader.h0000644000004100000410000000623314136373754015113 0ustar www-datawww-data// Copyright (c) 2011 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_READER_H #define OJ_READER_H typedef struct _reader { char base[0x00001000]; char *head; char *end; char *tail; char *read_end; /* one past last character read */ char *pro; /* protection start, buffer can not slide past this point */ char *str; /* start of current string being read */ long pos; int line; int col; int free_head; int (*read_func)(struct _reader *reader); union { int fd; VALUE io; const char *in_str; }; } * Reader; extern void oj_reader_init(Reader reader, VALUE io, int fd, bool to_s); extern int oj_reader_read(Reader reader); static inline char reader_get(Reader reader) { // printf("*** drive get from '%s' from start: %ld buf: %p from read_end: %ld\n", // reader->tail, reader->tail - reader->head, reader->head, reader->read_end - reader->tail); if (reader->read_end <= reader->tail) { if (0 != oj_reader_read(reader)) { return '\0'; } } if ('\n' == *reader->tail) { reader->line++; reader->col = 0; } reader->col++; reader->pos++; return *reader->tail++; } static inline void reader_backup(Reader reader) { reader->tail--; reader->col--; reader->pos--; if (0 >= reader->col) { reader->line--; // allow col to be negative since we never backup twice in a row } } static inline void reader_protect(Reader reader) { reader->pro = reader->tail; reader->str = reader->tail; // can't have str before pro } static inline void reader_release(Reader reader) { reader->pro = 0; } /* Starts by reading a character so it is safe to use with an empty or * compacted buffer. */ static inline char reader_next_non_white(Reader reader) { char c; while ('\0' != (c = reader_get(reader))) { switch (c) { case ' ': case '\t': case '\f': case '\n': case '\r': break; default: return c; } } return '\0'; } /* Starts by reading a character so it is safe to use with an empty or * compacted buffer. */ static inline char reader_next_white(Reader reader) { char c; while ('\0' != (c = reader_get(reader))) { switch (c) { case ' ': case '\t': case '\f': case '\n': case '\r': case '\0': return c; default: break; } } return '\0'; } static inline int reader_expect(Reader reader, const char *s) { for (; '\0' != *s; s++) { if (reader_get(reader) != *s) { return -1; } } return 0; } static inline void reader_cleanup(Reader reader) { if (reader->free_head && 0 != reader->head) { xfree((char *)reader->head); reader->head = 0; reader->free_head = 0; } } static inline int is_white(char c) { switch (c) { case ' ': case '\t': case '\f': case '\n': case '\r': return 1; default: break; } return 0; } #endif /* OJ_READER_H */ oj-3.13.9/ext/oj/intern.h0000644000004100000410000000155014136373754015145 0ustar www-datawww-data// Copyright (c) 2011, 2021 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_INTERN_H #define OJ_INTERN_H #include #include struct _parseInfo; extern void oj_hash_init(); extern VALUE oj_str_intern(const char *key, size_t len); extern VALUE oj_sym_intern(const char *key, size_t len); extern ID oj_attr_intern(const char *key, size_t len); extern VALUE oj_class_intern(const char * key, size_t len, bool safe, struct _parseInfo *pi, int auto_define, VALUE error_class); extern char *oj_strndup(const char *s, size_t len); #endif /* OJ_INTERN_H */ oj-3.13.9/ext/oj/rails.h0000644000004100000410000000072214136373754014760 0ustar www-datawww-data// Copyright (c) 2017 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for license details. #ifndef OJ_RAILS_H #define OJ_RAILS_H #include "dump.h" extern void oj_mimic_rails_init(); extern ROpt oj_rails_get_opt(ROptTable rot, VALUE clas); extern bool oj_rails_hash_opt; extern bool oj_rails_array_opt; extern bool oj_rails_float_opt; extern VALUE oj_optimize_rails(VALUE self); #endif /* OJ_RAILS_H */ oj-3.13.9/ext/oj/circarray.h0000644000004100000410000000124714136373754015630 0ustar www-datawww-data// Copyright (c) 2012 Peter Ohler. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for // license details. #ifndef OJ_CIRCARRAY_H #define OJ_CIRCARRAY_H #include "ruby.h" typedef struct _circArray { VALUE obj_array[1024]; VALUE * objs; unsigned long size; // allocated size or initial array size unsigned long cnt; } * CircArray; extern CircArray oj_circ_array_new(void); extern void oj_circ_array_free(CircArray ca); extern void oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id); extern VALUE oj_circ_array_get(CircArray ca, unsigned long id); #endif /* OJ_CIRCARRAY_H */