oj-3.16.3/ 0000755 0000041 0000041 00000000000 14540127115 012244 5 ustar www-data www-data oj-3.16.3/test/ 0000755 0000041 0000041 00000000000 14540127115 013223 5 ustar www-data www-data oj-3.16.3/test/perf_file.rb 0000755 0000041 0000041 00000003622 14540127115 015511 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << '../lib'
$LOAD_PATH << '../ext'
if __FILE__ == $PROGRAM_NAME && (i = ARGV.index('-I'))
_, path = ARGV.slice!(i, 2)
$LOAD_PATH << path
end
require 'optparse'
require 'oj'
require 'perf'
@indent = 0
@iter = 1
@size = 1
opts = OptionParser.new
opts.on('-r', 'read') { true }
opts.on('-c', '--count [Int]', Integer, 'iterations') { |v| @iter = v }
opts.on('-i', '--indent [Int]', Integer, 'indent') { |v| @indent = v }
opts.on('-s', '--size [Int]', Integer, 'size in Mbytes') { |s| @size = s }
opts.on('-h', '--help', 'Show this display') { puts opts; Process.exit!(0) }
opts.parse(ARGV)
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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' => 12_345_678_901_234_567_890_123_456_789, # _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.16.3/test/perf.rb 0000644 0000041 0000041 00000004524 14540127115 014511 0 ustar www-data www-data # frozen_string_literal: true
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
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|
puts '%*s %11.3f %14.3f' % [width, i.title, i.duration, i.rate ] unless i.duration.nil?
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)
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 # Item
end # Perf
oj-3.16.3/test/test_file.rb 0000755 0000041 0000041 00000013553 14540127115 015540 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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 to_json(*_args)
%{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}}
end
def self.json_create(h)
new(h['x'], h['y'])
end
end # Jeez
class Orange < Jam
def as_json
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
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(12_345, false)
dump_and_load(-54_321, 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(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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", 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
skip 'Windows does not support dates before 1970.' 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 $ruby == 'ruby'
assert_equal(%{{"^u":["Range",1,7,false]}}, json)
else
assert_equal(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":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 test_load_unicode_path
json =<<~JSON
{
"x":true,
"y":58,
"z": [1,2,3]
}
JSON
Tempfile.create('file_test_conceição1.json') do |f|
f.write(json)
f.close
objects = Oj.load_file(f.path)
assert_equal(Oj.load(json), objects)
end
end
def dump_and_load(obj, trace=false)
filename = File.join(__dir__, '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.16.3/test/test_writer.rb 0000755 0000041 0000041 00000021246 14540127115 016133 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: false
$LOAD_PATH << __dir__
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(__dir__, '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 => 16_000)
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.16.3/test/sample_json.rb 0000755 0000041 0000041 00000001415 14540127115 016066 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
if $PROGRAM_NAME == __FILE__
$LOAD_PATH << '.'
$LOAD_PATH << '..'
$LOAD_PATH << '../lib'
$LOAD_PATH << '../ext'
end
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 $PROGRAM_NAME == __FILE__
File.write('sample.json', Oj.dump(sample_json(3), :indent => 2))
end
oj-3.16.3/test/sample.rb 0000755 0000041 0000041 00000002516 14540127115 015040 0 ustar www-data www-data #!/usr/bin/env ruby -wW2
# frozen_string_literal: true
if $PROGRAM_NAME == __FILE__
$LOAD_PATH << '.'
$LOAD_PATH << '..'
$LOAD_PATH << '../lib'
$LOAD_PATH << '../ext'
end
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.16.3/test/test_null.rb 0000755 0000041 0000041 00000021072 14540127115 015566 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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(__dir__, 'open_file_test.json')
File.write(filename, %{{
"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.16.3/test/test_wab.rb 0000755 0000041 0000041 00000015707 14540127115 015375 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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 StandardError.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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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
skip 'TruffleRuby causes SEGV' if RUBY_ENGINE == 'truffleruby'
begin
n = 10_000
Oj.wab_load(('[' * n) + (']' * n))
rescue Exception => e
refute(e.message)
end
end
# Hash
def test_hash
dump_and_load({}, false)
dump_and_load({ tru: true, fals: false}, false)
dump_and_load({ tru: 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(__dir__, 'open_file_test.json')
File.write(filename, %{{
"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, 123_456.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.16.3/test/test_fast.rb 0000755 0000041 0000041 00000032073 14540127115 015554 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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(doc.fetch())
end
end
def test_false
json = %{false}
Oj::Doc.open(json) do |doc|
assert_equal(FalseClass, doc.type)
refute(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|
assert_equal(Integer, doc.type)
assert_equal(12_345, doc.fetch())
end
end
def test_float
json = %{12345.6789}
Oj::Doc.open(json) do |doc|
assert_equal(Float, doc.type)
assert_in_delta(12_345.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(12_345.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_empty(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_empty(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
Oj::Doc.open(@json1) do |doc|
[
['/', Hash],
['/array', Array],
['/array/1', Hash],
['/array/1/num', Integer],
['/array/1/string', String],
['/array/1/hash/h2/a', Array],
['/array/1/hash/../num', Integer],
['/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_nested_each_child
h = {}
Oj::Doc.open('{"a":1,"c":[2],"d":3}') do |doc|
doc.each_child('/') do |child|
h[child.path] = child.fetch
child.each_child do |grandchild|
h[grandchild.path] = grandchild.fetch
end
end
end
assert_equal({'/a'=>1, '/c'=>[2], '/c/1'=>2, '/d'=>3}, h)
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(__dir__, 'open_file_test.json')
File.write(filename, '{"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(__dir__, 'open_file_test.json')
File.write(filename, '{"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'))
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_doc_empty
result = Oj::Doc.open('') { |doc| doc.each_child {} }
assert_nil(result)
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.16.3/test/test_generate.rb 0000755 0000041 0000041 00000000565 14540127115 016412 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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.16.3/test/helper.rb 0000644 0000041 0000041 00000002303 14540127115 015025 0 ustar www-data www-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 'oj'
def verify_gc_compaction
# 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.
if defined?(GC.verify_compaction_references) == 'method' && RbConfig::CONFIG['host_os'] !~ /(mingw|mswin)/
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.0')
GC.verify_compaction_references(expand_heap: true, toward: :empty)
else
GC.verify_compaction_references(double_heap: true, toward: :empty)
end
end
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' => exclude_end? }
end
end
oj-3.16.3/test/mem.rb 0000755 0000041 0000041 00000001246 14540127115 014334 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
require 'English'
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'oj'
Oj.default_options = { mode: :rails, cache_keys: false, cache_str: -1 }
def mem
`ps -o rss= -p #{$PROCESS_ID}`.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}"
Oj.load(%|{ "#{key}": 101}|)
# Oj.dump(x)
}
}
}
}
puts "#{a}#{b} #{mem}"
}
}
Oj::Parser.new(:usual)
oj-3.16.3/test/test_rails.rb 0000755 0000041 0000041 00000001446 14540127115 015731 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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.16.3/test/_test_mimic_rails.rb 0000755 0000041 0000041 00000005457 14540127115 017254 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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
ActiveSupport::JSON.decode('{')
puts 'Failed'
rescue ActiveSupport::JSON.parse_error
assert(true)
rescue Exception
assert(false, 'Expected a JSON::ParserError')
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)
serializer.to_json()
puts "*** serializer.to_json() #{serializer.to_json()}"
serializer.as_json()
puts "*** serializer.as_json() #{serializer.as_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.16.3/test/isolated/ 0000755 0000041 0000041 00000000000 14540127115 015027 5 ustar www-data www-data oj-3.16.3/test/isolated/test_mimic_redefine.rb 0000755 0000041 0000041 00000000473 14540127115 021361 0 ustar www-data www-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.16.3/test/isolated/test_mimic_rails_after.rb 0000755 0000041 0000041 00000000547 14540127115 022075 0 ustar www-data www-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.16.3/test/isolated/test_mimic_define.rb 0000755 0000041 0000041 00000001073 14540127115 021027 0 ustar www-data www-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.16.3/test/isolated/test_mimic_alone.rb 0000755 0000041 0000041 00000000305 14540127115 020670 0 ustar www-data www-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.16.3/test/isolated/test_mimic_before.rb 0000755 0000041 0000041 00000000326 14540127115 021037 0 ustar www-data www-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.16.3/test/isolated/test_mimic_as_json.rb 0000755 0000041 0000041 00000001671 14540127115 021235 0 ustar www-data www-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.16.3/test/isolated/test_mimic_rails_before.rb 0000755 0000041 0000041 00000000550 14540127115 022230 0 ustar www-data www-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.16.3/test/isolated/test_mimic_after.rb 0000755 0000041 0000041 00000000324 14540127115 020674 0 ustar www-data www-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.16.3/test/isolated/shared.rb 0000644 0000041 0000041 00000017267 14540127115 016637 0 ustar www-data www-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)
assert_nothing_raised { JSON.create_id = nil }
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.16.3/test/test_object.rb 0000755 0000041 0000041 00000062617 14540127115 016074 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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)
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 to_hash
{ 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y }
end
def self.json_create(h)
new(h['x'], h['y'])
end
end # Jazz
class Orange < Jam
def as_json
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
new(h['x'], h['y'])
end
end
module One
module Two
module Three
class Deep
def eql?(o)
self.class == o.class
end
alias == eql?
def to_hash
{'json_class' => self.class.name.to_s}
end
def to_json(*_a)
%{{"json_class":"#{self.class.name}"}}
end
def self.json_create(_h)
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
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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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_predicate(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", 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(__dir__, 'open_file_test.json')
File.write(filename, %{{
"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, 12_345/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, 12_345)
# 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 < StandardError
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 'ruby' == $ruby
assert_equal(%{{"^u":["Range",1,7,false]}}, json)
else
assert_equal(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":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)
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_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(7_123_456_789, 1_000_000_000)), false)
end
def test_odd_xml_time
str = "2023-01-01T00:00:00Z"
assert_equal(Time.parse(str), Oj.load('{"^t":"' + str + '"}', mode: :object))
str = "2023-01-01T00:00:00.3Z"
assert_equal(Time.parse(str), Oj.load('{"^t":"' + str + '"}', mode: :object))
str = "2023-01-01T00:00:00.123456789123456789Z"
assert_equal(Time.parse(str), Oj.load('{"^t":"' + str + '"}', mode: :object))
str = "2023-01-01T00:00:00.123456789123456789123456789Z"
assert_equal(Time.parse(str), Oj.load('{"^t":"' + str + '"}', mode: :object))
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(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_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.16.3/test/test_saj.rb 0000755 0000041 0000041 00000010565 14540127115 015376 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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 = []
super
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, 12_345, nil]], handler.calls)
end
def test_float
handler = AllSaj.new()
json = %{12345.6789}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, 12_345.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((12_345.6789e7 * 10_000).to_i, (handler.calls[0][1] * 10_000).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, 12_345, 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-Za-z]:/)?(?:[a-z.]+/)*saj\.c:\d+\]}, message)
end
end
oj-3.16.3/test/perf_wab.rb 0000755 0000041 0000041 00000010172 14540127115 015341 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
require 'perf'
require 'oj'
@verbose = false
@indent = 0
@iter = 20_000
@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) }
opts.parse(ARGV)
@obj = {
a: 'Alpha', # string
b: true, # boolean
c: 12_345, # number
d: [ true, [false, [-123_456_789, 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] = 12_345_678_901_234_567_890_123_456_789 if @with_bignum
Oj.default_options = { :indent => @indent, :mode => :wab }
if 0 < @size
ob = @obj
@obj = []
(4 * @size).times do
@obj << ob
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)
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
# 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.key?('JSON::Ext')
perf.add('JSON::Ext', 'parse') { JSON.parse(@json) }
perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
end
unless @failed.key?('JSON::Pure')
perf.add('JSON::Pure', 'parse') { JSON.parse(@json) }
perf.before('JSON::Pure') { JSON.parser = JSON::Pure::Parser }
end
unless @failed.key?('Oj:wab')
perf.add('Oj:wab', 'wab_load') { Oj.wab_load(@json) }
end
perf.add('Yajl', 'parse') { Yajl::Parser.parse(@json) } unless @failed.key?('Yajl')
perf.run(@iter)
puts '-' * 80
puts 'Wab Dump Performance'
perf = Perf.new()
unless @failed.key?('JSON::Ext')
perf.add('JSON::Ext', 'dump') { JSON.generate(@obj) }
perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator }
end
unless @failed.key?('JSON::Pure')
perf.add('JSON::Pure', 'generate') { JSON.generate(@obj) }
perf.before('JSON::Pure') { JSON.generator = JSON::Pure::Generator }
end
unless @failed.key?('Oj:wab')
perf.add('Oj:wab', 'dump') { Oj.dump(@obj, :mode => :wab) }
end
perf.add('Yajl', 'encode') { Yajl::Encoder.encode(@obj) } unless @failed.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.16.3/test/perf_object.rb 0000755 0000041 0000041 00000007575 14540127115 016053 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << '../lib'
$LOAD_PATH << '../ext'
if __FILE__ == $PROGRAM_NAME && (i = ARGV.index('-I'))
_, path = ARGV.slice!(i, 2)
$LOAD_PATH << path
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') { |v| @iter = v }
opts.on('-m', '--multiply [Int]', Integer, 'multiplier') { |v| @mult = v }
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.write('sample.xml', @xml)
File.write('sample.json', @json)
File.write('sample.marshal', @mars)
else
puts "loading and parsing #{files}\n\n"
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.16.3/test/tests.rb 0000755 0000041 0000041 00000001433 14540127115 014716 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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_rails'
require 'test_wab'
require 'test_writer'
require 'test_integer_range'
at_exit do
require 'helper'
if '3.1.0' <= RUBY_VERSION && RbConfig::CONFIG['host_os'] !~ /(mingw|mswin)/
# Oj::debug_odd("teardown before GC.verify_compaction_references")
verify_gc_compaction
# Oj::debug_odd("teardown after GC.verify_compaction_references")
end
end
oj-3.16.3/test/perf_simple.rb 0000755 0000041 0000041 00000015703 14540127115 016066 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# frozen_string_literal: true
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../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(*_args)
%{
{ "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 = 10_000
$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) }
opts.parse(ARGV)
if $with_nums
obj = {
'a' => 'Alpha',
'b' => true,
'c' => 12_345,
'd' => [ true, [false, [12_345, nil], 3.967, ['something', false], nil]],
'e' => { 'one' => 1, 'two' => 2 },
'f' => nil,
}
obj['g'] = Jazz.new() if $with_object
obj['h'] = 12_345_678_901_234_567_890_123_456_789 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, dt2|
if dt2 <= 0.0
puts "#{name} failed to generate JSON"
next
end
puts '%-7s %6.3f %5.1f %4.1f' % [name, dt2, $iter/dt/1000.0, base_dt/dt2]
end
puts
# Back to object mode for best performance when dumping.
Oj.default_options = { :indent => $indent, :mode => :object }
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, dt2|
if dt2 <= 0.0
puts "#{name} failed to generate JSON"
next
end
puts '%-7s %6.3f %5.1f %4.1f' % [name, dt2, $iter/dt/1000.0, base_dt/dt2]
end
puts
oj-3.16.3/test/test_various.rb 0000755 0000041 0000041 00000047120 14540127115 016306 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: false
$LOAD_PATH << __dir__
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 to_json(*_args)
%{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}}
end
def self.json_create(h)
new(h['x'], h['y'])
end
end # Jeez
# contributed by sauliusg to fix as_json
class Orange < Jam
def as_json
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
new(h['x'], h['y'])
end
end
class Melon < Jam
def as_json(_options)
"#{x} #{y}"
end
def self.json_create(h)
new(h['x'], h['y'])
end
end
class Jazz < Jam
def to_hash
{ 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y }
end
def self.json_create(h)
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_format: '%0.13g',
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,
omit_null_byte: false,
}
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(12_345, false)
dump_and_load(-54_321, 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_slashes_by_default_when_configured_to_do_so
hash = {'key' => 'I <3 this '}
Oj.default_options = {:escape_mode => :slash}
out = Oj.dump hash
assert_equal(%{{"key":"I <3 this <\\/script>"}}, 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
def test_dump_float
json = Oj.dump(1.23e-2, :mode => :null, :float_format => '%0.4f')
assert_equal('0.0123', json)
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(obj.x)
assert_equal(58, obj.y)
end
# Stream Deeply Nested
def test_deep_nest_dump
begin
a = []
10_000.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(__dir__, '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
def test_io_stream
skip 'needs fork' unless Process.respond_to?(:fork)
IO.pipe do |r, w|
if fork
r.close
# w.nonblock = false
a = []
10_000.times do |i|
a << i
end
Oj.to_stream(w, a, indent: 2)
w.close
else
w.close
sleep(0.1) # to force a busy
cnt = 0
r.each_line { cnt += 1 }
r.close
Process.exit(0)
end
end
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(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_in_delta(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
assert_raises Oj::ParseError do
Oj.load(%|{ "big": -e123456789 }|, mode: :strict)
end
end
def test_quirks_array_mode
assert_empty(Oj.load('[]', :quirks_mode => false))
assert_empty(Oj.load('[]', :quirks_mode => true))
end
def test_quirks_object_mode
assert_empty(Oj.load('{}', :quirks_mode => false))
assert_empty(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 test_omit_null_byte
Oj.default_options = { :omit_null_byte => true }
json = Oj.dump({ "fo\x00o" => "b\x00ar" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "foo\x00" => "\x00bar" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "\x00foo" => "bar\x00" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "fo\0o" => "ba\0r" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "foo\0" => "\0bar" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "\0foo" => "bar\0" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "fo\u0000o" => "ba\u0000r" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "foo\u0000" => "\u0000bar" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "\u0000foo" => "bar\u0000" })
assert_equal(%|{"foo":"bar"}|, json)
json = Oj.dump({ "\x00foo" => "bar\x00" }, :omit_null_byte => false)
assert_equal(%|{"\\u0000foo":"bar\\u0000"}|, json)
# no change from default set earlier so :omit_null_byte is still true
json = Oj.dump({ "\x00foo" => "bar\x00" }, :omit_null_byte => nil)
assert_equal(%|{"foo":"bar"}|, json)
Oj.default_options = { :omit_null_byte => false }
json = Oj.dump({ "\x00foo" => "bar\x00" })
assert_equal(%|{"\\u0000foo":"bar\\u0000"}|, json)
json = Oj.dump({ "\x00foo" => "bar\x00" }, :omit_null_byte => true)
assert_equal(%|{"foo":"bar"}|, 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.16.3/test/test_strict.rb 0000755 0000041 0000041 00000023434 14540127115 016130 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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_invalid_float
begin
Oj.load("64ecb72d29191067f91ff95b")
rescue Oj::ParseError => e
assert(e.message == "Invalid float")
return
end
assert(false, "*** expected an exception")
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 }
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_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
skip 'TruffleRuby causes SEGV' if RUBY_ENGINE == 'truffleruby'
begin
n = 10_000
Oj.strict_load(('[' * n) + (']' * n))
rescue Exception => e
refute(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(__dir__, 'open_file_test.json')
File.write(filename, %{{
"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.16.3/test/tests_mimic.rb 0000755 0000041 0000041 00000001223 14540127115 016071 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
$LOAD_PATH << File.join(__dir__, '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'
at_exit do
require 'helper'
if '3.1.0' <= RUBY_VERSION && RbConfig::CONFIG['host_os'] !~ /(mingw|mswin)/
# Oj::debug_odd("teardown before GC.verify_compaction_references")
verify_gc_compaction
# Oj::debug_odd("teardown after GC.verify_compaction_references")
end
end
oj-3.16.3/test/files.rb 0000755 0000041 0000041 00000001071 14540127115 014654 0 ustar www-data www-data #!/usr/bin/env ruby -wW2
# frozen_string_literal: true
if $PROGRAM_NAME == __FILE__
$LOAD_PATH << '.'
$LOAD_PATH << '..'
$LOAD_PATH << '../lib'
$LOAD_PATH << '../ext'
end
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)
d << if File.directory?(filename)
files(filename)
else
Sample::File.new(filename)
end
end
# pp d
d
end
oj-3.16.3/test/json_gem/ 0000755 0000041 0000041 00000000000 14540127115 015024 5 ustar www-data www-data oj-3.16.3/test/json_gem/json_encoding_test.rb 0000755 0000041 0000041 00000010117 14540127115 021232 0 ustar www-data www-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.16.3/test/json_gem/json_generator_test.rb 0000755 0000041 0000041 00000031507 14540127115 021440 0 ustar www-data www-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)
json_nil_opts = JSON.pretty_generate({1=>2}, nil)
assert_equal json, json_nil_opts
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)
actual.delete(:strict)
actual.delete(:script_safe)
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)
actual.delete(:strict)
actual.delete(:script_safe)
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)
actual.delete(:strict)
actual.delete(:script_safe)
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) && Process.respond_to?(:fork)
# forking to avoid modifying core class of a parent process and
# introducing race conditions of tests are run in parallel
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?
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 => 102
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
def test_invalid_to_json
omit if REAL_JSON_GEM
data = Object.new
def data.to_json(*)
nil
end
assert_raises(TypeError) { JSON.generate(data) }
end
end
oj-3.16.3/test/json_gem/json_string_matching_test.rb 0000755 0000041 0000041 00000001712 14540127115 022625 0 ustar www-data www-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.16.3/test/json_gem/json_fixtures_test.rb 0000755 0000041 0000041 00000002006 14540127115 021313 0 ustar www-data www-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.16.3/test/json_gem/json_ext_parser_test.rb 0000755 0000041 0000041 00000001022 14540127115 021613 0 ustar www-data www-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.16.3/test/json_gem/json_addition_test.rb 0000755 0000041 0000041 00000015205 14540127115 021242 0 ustar www-data www-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.16.3/test/json_gem/json_parser_test.rb 0000755 0000041 0000041 00000036242 14540127115 020747 0 ustar www-data www-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) and defined?(JSON::Ext::Parser)
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 = <<~EOT
{
"key1":"value1", // eol comment
"key2":"value2" /* multi line
* comment */,
"key3":"value3" /* multi line
// nested eol comment
* comment */
}
EOT
assert_equal(
{ "key1" => "value1", "key2" => "value2", "key3" => "value3" },
JSON.parse(json))
json = <<~EOT
{
"key1":"value1" /* multi line
// nested eol comment
/* illegal nested multi line comment */
* comment */
}
EOT
assert_raise(JSON::ParserError) { JSON.parse(json) }
json = <<~EOT
{
"key1":"value1" /* multi line
// nested eol comment
closed multi comment */
and again, throw an Error */
}
EOT
assert_raise(JSON::ParserError) { JSON.parse(json) }
json = <<~EOT
{
"key1":"value1" /*/*/
}
EOT
assert_equal({ "key1" => "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
unless ENV['REAL_JSON_GEM']
# max_nesting should be reset to 0 if not included in options
# This behavior is not compatible with Ruby standard JSON gem
ok = JSON.parse too_deep, {}
assert_equal too_deep_ary, ok
end
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.16.3/test/json_gem/json_generic_object_test.rb 0000755 0000041 0000041 00000005463 14540127115 022416 0 ustar www-data www-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.16.3/test/json_gem/json_common_interface_test.rb 0000755 0000041 0000041 00000012033 14540127115 022753 0 ustar www-data www-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))
if RUBY_ENGINE != 'truffleruby'
assert_raise(ArgumentError) { JSON.dump(eval(too_deep), 100) }
assert_raise(ArgumentError) { Marshal.dump(eval(too_deep), 100) }
end
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.16.3/test/json_gem/test_helper.rb 0000644 0000041 0000041 00000001523 14540127115 017670 0 ustar www-data www-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
# 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.
if defined?(GC.verify_compaction_references) == 'method'
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2.0")
GC.verify_compaction_references(expand_heap: true, toward: :empty)
else
GC.verify_compaction_references(double_heap: true, toward: :empty)
end
end
end
NaN = JSON::NaN if defined?(JSON::NaN)
NaN = 0.0/0 unless defined?(NaN)
oj-3.16.3/test/perf_once.rb 0000755 0000041 0000041 00000002255 14540127115 015517 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
require 'English'
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../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 #{$PROCESS_ID}`.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.16.3/test/perf_strict.rb 0000755 0000041 0000041 00000010713 14540127115 016101 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
require 'perf'
require 'oj'
@verbose = false
@indent = 0
@iter = 20_000
@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) }
opts.parse(ARGV)
if @with_nums
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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'] = 12_345_678_901_234_567_890_123_456_789 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
ob = @obj
@obj = []
(4 * @size).times do
@obj << ob
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)
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
# 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.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.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.key?('Yajl')
perf.run(@iter)
puts '-' * 80
puts 'Strict Dump Performance'
perf = Perf.new()
unless @failed.key?('JSON::Ext')
perf.add('JSON::Ext', 'dump') { JSON.generate(@obj) }
perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator }
end
unless @failed.key?('Oj:strict')
perf.add('Oj:strict', 'dump') { Oj.dump(@obj) }
end
perf.add('Yajl', 'encode') { Yajl::Encoder.encode(@obj) } unless @failed.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.16.3/test/test_debian.rb 0000755 0000041 0000041 00000001651 14540127115 016037 0 ustar www-data www-data # frozen_string_literal: true
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 as_json
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
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)
refute_nil(json)
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.16.3/test/_test_active.rb 0000755 0000041 0000041 00000003371 14540127115 016230 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
%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.16.3/test/activesupport6/ 0000755 0000041 0000041 00000000000 14540127115 016221 5 ustar www-data www-data oj-3.16.3/test/activesupport6/encoding_test.rb 0000644 0000041 0000041 00000036377 14540127115 021413 0 ustar www-data www-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.16.3/test/activesupport6/time_zone_test_helpers.rb 0000644 0000041 0000041 00000001707 14540127115 023325 0 ustar www-data www-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.16.3/test/activesupport6/abstract_unit.rb 0000644 0000041 0000041 00000002310 14540127115 021404 0 ustar www-data www-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.16.3/test/activesupport6/encoding_test_cases.rb 0000644 0000041 0000041 00000010123 14540127115 022546 0 ustar www-data www-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.16.3/test/activesupport6/decoding_test.rb 0000644 0000041 0000041 00000013440 14540127115 021363 0 ustar www-data www-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.16.3/test/activesupport6/test_common.rb 0000644 0000041 0000041 00000000761 14540127115 021101 0 ustar www-data www-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.16.3/test/activesupport6/test_helper.rb 0000644 0000041 0000041 00000014045 14540127115 021070 0 ustar www-data www-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.16.3/test/perf_compat.rb 0000755 0000041 0000041 00000007006 14540127115 016055 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
require 'perf'
require 'oj'
$verbose = false
$indent = 0
$iter = 20_000
$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) }
opts.parse(ARGV)
def capture_error(tag, orig, load_key, dump_key, &blk)
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
# 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.parse(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)
new()
end
end # Empty
end # Three
end # Two
end # One
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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.key?('JSON::Ext')
perf.add('JSON::Ext', 'parse') { JSON.parse($json) }
perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
end
unless $failed.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.16.3/test/activesupport7/ 0000755 0000041 0000041 00000000000 14540127115 016222 5 ustar www-data www-data oj-3.16.3/test/activesupport7/encoding_test.rb 0000644 0000041 0000041 00000035013 14540127115 021376 0 ustar www-data www-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"
class TestJSONEncoding < ActiveSupport::TestCase
include TimeZoneTestHelpers
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
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.16.3/test/activesupport7/time_zone_test_helpers.rb 0000644 0000041 0000041 00000002363 14540127115 023325 0 ustar www-data www-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
def with_utc_to_local_returns_utc_offset_times(value)
old_tzinfo2_format = ActiveSupport.utc_to_local_returns_utc_offset_times
ActiveSupport.utc_to_local_returns_utc_offset_times = value
yield
ensure
ActiveSupport.utc_to_local_returns_utc_offset_times = old_tzinfo2_format
end
end
oj-3.16.3/test/activesupport7/abstract_unit.rb 0000644 0000041 0000041 00000002464 14540127115 021417 0 ustar www-data www-data # frozen_string_literal: true
ORIG_ARGV = ARGV.dup
require "bundler/setup"
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
if Process.respond_to?(:fork) && !Gem.win_platform?
parallelize
else
parallelize(with: :threads)
end
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
oj-3.16.3/test/activesupport7/encoding_test_cases.rb 0000644 0000041 0000041 00000010663 14540127115 022560 0 ustar www-data www-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") ]]
ModuleTests = [[ Module, %("Module") ],
[ Class, %("Class") ],
[ ActiveSupport, %("ActiveSupport") ],
[ ActiveSupport::MessageEncryptor, %("ActiveSupport::MessageEncryptor") ]]
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") ]]
IPAddrTests = [[ IPAddr.new("127.0.0.1"), %("127.0.0.1") ]]
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.16.3/test/activesupport7/decoding_test.rb 0000644 0000041 0000041 00000013457 14540127115 021374 0 ustar www-data www-data # frozen_string_literal: true
require_relative "abstract_unit"
require "active_support/json"
require "active_support/time"
require_relative "time_zone_test_helpers"
class TestJSONDecoding < ActiveSupport::TestCase
include TimeZoneTestHelpers
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": "Today is:\\n2020-05-21"}) => { "a" => "Today is:\n2020-05-21" },
%({"a": "2007-01-01 01:12:34 Z\\nwas my birthday"}) => { "a" => "2007-01-01 01:12:34 Z\nwas my 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.16.3/test/_test_active_mimic.rb 0000755 0000041 0000041 00000003473 14540127115 017411 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
%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.16.3/test/test_custom.rb 0000755 0000041 0000041 00000035071 14540127115 016132 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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
skip 'TruffleRuby causes SEGV' if RUBY_ENGINE == 'truffleruby'
begin
n = 10_000
Oj.strict_load(('[' * n) + (']' * n))
rescue Exception => e
refute(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_omit_null_byte
json = Oj.dump({ "fo\x00o" => "b\x00ar" }, :omit_null_byte => true)
assert_equal(%|{"foo":"bar"}|, 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
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
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
skip 'TruffleRuby fails this spec' if RUBY_ENGINE == 'truffleruby'
obj = Time.now()
# These two forms should be able to recreate the time precisely,
# so we check they can load a dumped version and recreate the
# original object correctly.
dump_and_load(obj, false, :time_format => :unix, :create_id => '^o', :create_additions => true)
dump_and_load(obj, false, :time_format => :unix_zone, :create_id => '^o', :create_additions => true)
# These two forms will lose precision while dumping as they don't
# preserve full precision. We check that a dumped version is equal
# to that version loaded and dumped a second time, but don't check
# that the loaded Ruby object is still the same as the original.
dump_load_dump(obj, false, :time_format => :xmlschema, :create_id => '^o', :create_additions => true)
dump_load_dump(obj, false, :time_format => :xmlschema, :create_id => '^o', :create_additions => true, second_precision: 3)
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.16.3/test/test_gc.rb 0000755 0000041 0000041 00000002515 14540127115 015206 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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.to_s, 'x' => x, 'child' => child }
end
def self.json_create(h)
GC.start
new(h['x'], h['child'])
end
end # Goo
def setup
@default_options = Oj.default_options
GC.stress = true
end
def teardown
Oj.default_options = @default_options
GC.stress = false
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
def test_parse_gc
json = '{"a":"Alpha","b":true,"c":12345,"d":[true,[false,[-123456789,null],3.9676,["Something else.",false],null]],"e":{"zero":null,"one":1,"two":2,"three":[3],"four":[0,1,2,3,4]},"f":null,"h":{"a":{"b":{"c":{"d":{"e":{"f":{"g":null}}}}}}},"i":[[[[[[[null]]]]]]]}'
50.times do
data = Oj.load(json)
assert_equal(json, Oj.dump(data))
end
end
end
oj-3.16.3/test/test_integer_range.rb 0000755 0000041 0000041 00000004324 14540127115 017426 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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: 9_007_199_254_740_993}
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: -10_000_000_000_000_000_000, u2: 10_000_000_000_000_000_000}
exp = '{"u":"-10000000000000000000","u2":"10000000000000000000"}'
assert_equal(exp, Oj.dump(test, integer_range: (-1..1)))
end
def test_valid_modes
test = {safe: 0, unsafe: 9_007_199_254_740_993}
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: 10_000_000_000_000_000_000}
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: 10_000_000_000_000_000_000}
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.16.3/test/perf_parser.rb 0000755 0000041 0000041 00000011705 14540127115 016067 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../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) }
opts.parse(ARGV)
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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'] = 12_345_678_901_234_567_890_123_456_789 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}
Oj.default_options = if $cache_keys
{cache_keys: true, cache_str: 6, symbol_keys: $symbol_keys}
else
{cache_keys: false, cache_str: -1, symbol_keys: $symbol_keys}
end
JSON.parser = JSON::Ext::Parser
class AllSaj
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
no_handler = Object.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.parse($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 = 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.parse($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.16.3/test/test_hash.rb 0000755 0000041 0000041 00000001352 14540127115 015536 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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.16.3/test/activerecord/ 0000755 0000041 0000041 00000000000 14540127115 015675 5 ustar www-data www-data oj-3.16.3/test/activerecord/result_test.rb 0000755 0000041 0000041 00000001664 14540127115 020611 0 ustar www-data www-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.16.3/test/perf_saj.rb 0000755 0000041 0000041 00000006147 14540127115 015354 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
# require 'yajl'
require 'perf'
require 'json'
require 'json/ext'
require 'oj'
@verbose = false
@indent = 0
@iter = 10_000
@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) }
opts.parse(ARGV)
class AllSaj < Oj::Saj
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
end # NoSaj
saj_handler = AllSaj.new()
no_saj = NoSaj.new()
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, {'12345' => 12_345, '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' => 12_345_678_901_234_567_890_123_456_789, # 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)
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
# 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.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.16.3/test/test_parser_usual.rb 0000755 0000041 0000041 00000012637 14540127115 017330 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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)
refute(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
def test_empty
p = Oj::Parser.new(:usual)
p.raise_on_empty = false
doc = p.parse(' ')
assert_nil(doc)
p.raise_on_empty = true
assert_raises(Oj::ParseError) { p.parse(' ') }
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.16.3/test/prec.rb 0000644 0000041 0000041 00000001022 14540127115 014474 0 ustar www-data www-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.16.3/test/foo.rb 0000755 0000041 0000041 00000000353 14540127115 014337 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'json'
require 'oj'
require 'oj/json'
Oj.mimic_JSON
JSON.parse("[]")
oj-3.16.3/test/perf_fast.rb 0000755 0000041 0000041 00000014123 14540127115 015525 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
# require 'yajl'
require 'perf'
require 'json'
require 'json/ext'
require 'oj'
@verbose = false
@indent = 0
@iter = 100_000
@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) }
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.each_value { |e| dig(e, &blk) }
else
blk.yield(obj)
end
end
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, {'12345' => 12_345, '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' => 12_345_678_901_234_567_890_123_456_789, # 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)
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
# 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.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.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.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.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.write('json_ext.json', 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.write('json_ext.json', 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.16.3/test/test_parser.rb 0000755 0000041 0000041 00000000370 14540127115 016106 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << File.join(@oj_dir, dir)
end
require 'test_parser_usual'
require 'test_parser_saj'
oj-3.16.3/test/test_parser_saj.rb 0000755 0000041 0000041 00000020373 14540127115 016750 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
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 = []
super
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 LocSaj
attr_accessor :calls
def initialize
@calls = []
end
def hash_start(key, line, column)
@calls << [:hash_start, key, line, column]
end
def hash_end(key, line, column)
@calls << [:hash_end, key, line, column]
end
def array_start(key, line, column)
@calls << [:array_start, key, line, column]
end
def array_end(key, line, column)
@calls << [:array_end, key, line, column]
end
def add_value(value, key, line, column)
@calls << [:add_value, value, key, line, column]
end
end # LocSaj
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, 12_345, 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, 12_345.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((12_345.6789e7 * 10_000).to_i, (handler.calls[0][1] * 10_000).to_i)
end
def test_bignum
handler = AllSaj.new()
json = %{-11.899999999999999}
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(-118_999, (handler.calls[0][1] * 10_000).to_i)
end
def test_bignum_loc
handler = LocSaj.new()
json = <<~JSON
{
"width": 192.33800000000002,
"xaxis": {
"anchor": "y"
}
}
JSON
p = Oj::Parser.new(:saj)
p.handler = handler
p.parse(json)
assert_equal(6, handler.calls.size)
assert_equal(1_923_380, (handler.calls[1][1] * 10_000).to_i)
handler.calls[1][1] = 1_923_380
assert_equal([[:hash_start, nil, 1, 1],
[:add_value, 1_923_380, 'width', 2, 30],
[:hash_start, 'xaxis', 3, 12],
[:add_value, 'y', 'anchor', 4, 17],
[:hash_end, 'xaxis', 5, 3],
[:hash_end, nil, 6, 1]],
handler.calls)
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
def test_loc
handler = LocSaj.new()
Oj::Parser.saj.handler = handler
Oj::Parser.saj.parse($json)
assert_equal([[:hash_start, nil, 1, 1],
[:array_start, 'array', 2, 12],
[:hash_start, nil, 3, 5],
[:add_value, 3, 'num', 4, 18],
[:add_value, 'message', 'string', 5, 25],
[:hash_start, 'hash', 6, 17],
[:hash_start, 'h2', 7, 17],
[:array_start, 'a', 8, 17],
[:add_value, 1, nil, 8, 20],
[:add_value, 2, nil, 8, 23],
[:add_value, 3, nil, 8, 26],
[:array_end, 'a', 8, 27],
[:hash_end, 'h2', 9, 9],
[:hash_end, 'hash', 10, 7],
[:hash_end, nil, 11, 5],
[:array_end, 'array', 12, 3],
[:add_value, true, 'boolean', 13, 18],
[:hash_end, nil, 14, 1]], handler.calls)
end
end
oj-3.16.3/test/test_scp.rb 0000755 0000041 0000041 00000024616 14540127115 015410 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
require 'helper'
require 'socket'
require 'stringio'
$json = %{{
"array": [
{
"num" : 3,
"string": "message",
"hash" : {
"h2" : {
"a" : [ 1, 2, 3 ]
}
}
}
],
"boolean" : true
}}
class NoHandler < Oj::ScHandler
end
class AllHandler < Oj::ScHandler
attr_accessor :calls
def initialize
super
@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, 12_345]], handler.calls)
end
def test_float
handler = AllHandler.new()
json = %{12345.6789}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, 12_345.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((12_345.6789e7 * 10_000).to_i, (handler.calls[0][1] * 10_000).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
skip 'needs fork' unless Process.respond_to?(:fork)
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
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
def test_pipe_close
skip 'needs fork' unless Process.respond_to?(:fork)
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..]
rescue Exception
# 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..]
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.16.3/test/test_parser_debug.rb 0000755 0000041 0000041 00000001015 14540127115 017251 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w[lib ext].each do |dir|
$LOAD_PATH << 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.16.3/test/tests_mimic_addition.rb 0000755 0000041 0000041 00000000662 14540127115 017752 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
$LOAD_PATH << File.join(__dir__, 'json_gem')
require 'json_addition_test'
at_exit do
require 'helper'
if '3.1.0' <= RUBY_VERSION && RbConfig::CONFIG['host_os'] !~ /(mingw|mswin)/
# Oj::debug_odd("teardown before GC.verify_compaction_references")
verify_gc_compaction
# Oj::debug_odd("teardown after GC.verify_compaction_references")
end
end
oj-3.16.3/test/test_compat.rb 0000755 0000041 0000041 00000035400 14540127115 016077 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << __dir__
@oj_dir = File.dirname(File.expand_path(__dir__))
%w(lib ext).each do |dir|
$LOAD_PATH << 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}","x":#{@x},"y":#{@y}}|
end
def self.json_create(h)
new(h['x'], h['y'])
end
end # Jeez
class Argy
def to_json(*a)
%|{"args":"#{a}"}|
end
end # Argy
class Stringy
def to_s
%|[1,2]|
end
end # Stringy
module One
module Two
module Three
class Deep
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)
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(12_345, false)
dump_and_load(-54_321, false)
dump_and_load(1, false)
end
def test_fixnum_array
data = (1..1000).to_a
json = Oj.dump(data, mode: :compat)
assert_equal("[#{data.join(',')}]", json)
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(12_345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54_321.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_405_460_727.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(30_226_801_971_775_055_948_247_051_683_954_096_612_865_741_943, 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(__dir__, 'open_file_test.json')
File.write(filename, %{{
"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_max_nesting
assert_raises() { Oj.to_json([[[[[]]]]], :max_nesting => 3) }
assert_raises() { Oj.dump([[[[[]]]]], :max_nesting => 3, :mode=>:compat) }
assert_raises() { Oj.to_json([[]], :max_nesting => 1) }
assert_equal('[[]]', Oj.to_json([[]], :max_nesting => 2))
assert_raises() { Oj.dump([[]], :max_nesting => 1, :mode=>:compat) }
assert_equal('[[]]', Oj.dump([[]], :max_nesting => 2, :mode=>:compat))
assert_raises() { Oj.to_json([[3]], :max_nesting => 1) }
assert_equal('[[3]]', Oj.to_json([[3]], :max_nesting => 2))
assert_raises() { Oj.dump([[3]], :max_nesting => 1, :mode=>:compat) }
assert_equal('[[3]]', Oj.dump([[3]], :max_nesting => 2, :mode=>:compat))
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 test_parse_large_string
error = assert_raises() { Oj.load(%|{"a":"aaaaaaaaaa\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}|) }
assert_includes(error.message, 'NULL byte in string')
error = assert_raises() { Oj.load(%|{"a":"aaaaaaaaaaaaaaaaaaaa }|) }
assert_includes(error.message, 'quoted string not terminated')
json =<<~JSON
{
"a": "\\u3074\\u30fc\\u305f\\u30fc",
"b": "aaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
JSON
assert_equal('ぴーたー', Oj.load(json)['a'])
end
def test_parse_large_escaped_string
invalid_json = %|{"a":"aaaa\\nbbbb\\rcccc\\tddd\\feee\\bf/\\\\\\u3074\\u30fc\\u305f\\u30fc }|
error = assert_raises() { Oj.load(invalid_json) }
assert_includes(error.message, 'quoted string not terminated')
json = '"aaaa\\nbbbb\\rcccc\\tddd\\feee\\bf/\\\\\\u3074\\u30fc\\u305f\\u30fc "'
assert_equal("aaaa\nbbbb\rcccc\tddd\feee\bf/\\ぴーたー ", Oj.load(json))
end
def test_invalid_to_s
obj = Object.new
def obj.to_s
nil
end
assert_raises(TypeError) { Oj.dump(obj, 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.16.3/test/perf_scp.rb 0000755 0000041 0000041 00000007211 14540127115 015355 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../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) }
opts.parse(ARGV)
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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'] = 12_345_678_901_234_567_890_123_456_789 if @with_bignum
if 0 < @size
ob = @obj
@obj = []
(4 * @size).times do
@obj << ob
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 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
end # NoSaj
class NoHandler < Oj::ScHandler
end # NoHandler
class AllHandler < Oj::ScHandler
def hash_start
nil
end
def hash_end
end
def array_start
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)
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
# 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.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.16.3/test/sample/ 0000755 0000041 0000041 00000000000 14540127115 014504 5 ustar www-data www-data oj-3.16.3/test/sample/group.rb 0000644 0000041 0000041 00000000305 14540127115 016163 0 ustar www-data www-data module Sample
class Group
attr_reader :members
def initialize()
@members = []
end
def <<(member)
@members << member
end
end # Group
end # Sample
oj-3.16.3/test/sample/oval.rb 0000644 0000041 0000041 00000000211 14540127115 015764 0 ustar www-data www-data module Sample
class Oval < Shape
def initialize(left, top, wide, high, color=nil)
super
end
end # Oval
end # Sample
oj-3.16.3/test/sample/shape.rb 0000644 0000041 0000041 00000001104 14540127115 016125 0 ustar www-data www-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.16.3/test/sample/dir.rb 0000644 0000041 0000041 00000000325 14540127115 015607 0 ustar www-data www-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.16.3/test/sample/line.rb 0000644 0000041 0000041 00000000500 14540127115 015753 0 ustar www-data www-data module 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.16.3/test/sample/layer.rb 0000644 0000041 0000041 00000000233 14540127115 016143 0 ustar www-data www-data module Sample
class Layer < Group
attr_accessor :name
def initialize(name)
super()
@name = name
end
end # Layer
end # Sample
oj-3.16.3/test/sample/hasprops.rb 0000644 0000041 0000041 00000000455 14540127115 016674 0 ustar www-data www-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.16.3/test/sample/file.rb 0000644 0000041 0000041 00000003177 14540127115 015760 0 ustar www-data www-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.16.3/test/sample/doc.rb 0000644 0000041 0000041 00000001367 14540127115 015605 0 ustar www-data www-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.16.3/test/sample/change.rb 0000644 0000041 0000041 00000000440 14540127115 016254 0 ustar www-data www-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.16.3/test/sample/rect.rb 0000644 0000041 0000041 00000000210 14540127115 015757 0 ustar www-data www-data module Sample
class Rect < Shape
def initialize(left, top, wide, high, color=nil)
super
end
end # Rect
end # Sample
oj-3.16.3/test/sample/text.rb 0000644 0000041 0000041 00000000643 14540127115 016020 0 ustar www-data www-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.16.3/test/perf_dump.rb 0000755 0000041 0000041 00000003052 14540127115 015534 0 ustar www-data www-data #!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << '.'
$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')
require 'optparse'
require 'oj'
@verbose = false
@indent = 2
@iter = 100_000
@size = 2
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) }
opts.parse(ARGV)
@obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12_345, # number
'd' => [ true, [false, [-123_456_789, 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
}
Oj.default_options = { :indent => @indent, :mode => :strict }
if 0 < @size
o = @obj
@obj = []
(4 * @size).times do
@obj << o
end
end
@json = Oj.dump(@obj)
GC.start
start = Time.now
@iter.times { Oj.dump(@obj) }
duration = Time.now - start
puts "Dumped #{@json.length} byte JSON #{@iter} times in %0.3f seconds or %0.3f iteration/sec." % [duration, @iter / duration]
oj-3.16.3/pages/ 0000755 0000041 0000041 00000000000 14540127115 013343 5 ustar www-data www-data oj-3.16.3/pages/Advanced.md 0000644 0000041 0000041 00000002413 14540127115 015372 0 ustar www-data www-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.16.3/pages/Modes.md 0000644 0000041 0000041 00000024014 14540127115 014735 0 ustar www-data www-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. To universally replace many `JSON` methods with their faster Oj counterparts,
simply run `Oj.mimic_json`. [{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 | 4 | 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 | 5 | 5 | x | | 5 | 5 | |
| :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.
Note: after version 3.11.3 both `Oj.generate` and `JSON.generate`
will not honour this option in Rails Mode, detais on https://github.com/ohler55/oj/pull/716.
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.16.3/pages/WAB.md 0000644 0000041 0000041 00000001131 14540127115 014272 0 ustar www-data www-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.16.3/pages/Custom.md 0000644 0000041 0000041 00000002313 14540127115 015136 0 ustar www-data www-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.16.3/pages/Rails.md 0000644 0000041 0000041 00000013334 14540127115 014743 0 ustar www-data www-data # Rails Quickstart
To universally replace Rails' use of the json gem with Oj, and also
have Oj "take over" many methods on the JSON constant (`load`, `parse`, etc.) with
their faster Oj counterparts, add this to an initializer:
```ruby
Oj.optimize_rails()
```
For more details and options, read on...
# 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.16.3/pages/Security.md 0000644 0000041 0000041 00000002464 14540127115 015502 0 ustar www-data www-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.16.3/pages/InstallOptions.md 0000644 0000041 0000041 00000000773 14540127115 016656 0 ustar www-data www-data # Oj Install Options
### Enable trace log
```
$ gem install oj -- --enable-trace-log
```
To enable Oj trace feature, it uses `--enable-trace-log` option when installing the gem.
Then, the trace logs will be displayed when `:trace` option is set to `true`.
### Enable SIMD instructions
```
$ gem install oj -- --with-sse42
```
To enable the use of SIMD instructions in Oj, it uses the `--with-sse42` option when installing the gem.
This will enable the use of the SSE4.2 instructions in the internal.
oj-3.16.3/pages/Compatibility.md 0000644 0000041 0000041 00000001723 14540127115 016501 0 ustar www-data www-data # Compatibility
**Ruby**
Oj is compatible with Ruby 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.16.3/pages/Encoding.md 0000644 0000041 0000041 00000006561 14540127115 015423 0 ustar www-data www-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.16.3/pages/Parser.md 0000644 0000041 0000041 00000032375 14540127115 015133 0 ustar www-data www-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.16.3/pages/JsonGem.md 0000644 0000041 0000041 00000006340 14540127115 015232 0 ustar www-data www-data # JSON Quickstart
To have Oj universally "take over" many methods on the JSON constant (`load`, `parse`, etc.) with
their faster Oj counterparts, in a mode that is compatible with the json gem:
```ruby
Oj.mimic_JSON()
```
If the project does not already use the json gem, `JSON` will become available.
If the project does require the json gem, `Oj.mimic_JSON()` should be invoked after the
json gem has been required.
For more details and options, read on...
# 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.16.3/pages/Options.md 0000644 0000041 0000041 00000024570 14540127115 015330 0 ustar www-data www-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.
- `:fast` faster conversion to Float.
- `:ruby` convert to Float using the Ruby `to_f` conversion.
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.
- `:slash` escapes `/` characters.
- `: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.
### :skip_null_byte [Boolean]
If true, null bytes in strings will be omitted when dumping.
### :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.16.3/README.md 0000644 0000041 0000041 00000010550 14540127115 013524 0 ustar www-data www-data # [](http://www.ohler.com/oj) gem
[](https://github.com/ohler55/oj/actions/workflows/CI.yml)


[](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'
```
## Rails and json quickstart
See the Quickstart sections of the [Rails](pages/Rails.md) and [json](pages/JsonGem.md) docs.
## multi_json
Code which uses [multi_json](https://github.com/intridea/multi_json)
will automatically prefer Oj if it is installed.
## 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.
- [{file:InstallOptions.md}](pages/InstallOptions.md) for install option.
## 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
- *oj-introspect, an example of creating an Oj parser extension in C*: https://github.com/meinac/oj-introspect
#### 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.16.3/CHANGELOG.md 0000644 0000041 0000041 00000132656 14540127115 014072 0 ustar www-data www-data # CHANGELOG
## 3.16.3 - 2023-12-11
- Fixed the gemspec to allow earlier versions of the bigdecimal gem.
## 3.16.2 - 2023-12-06
- Fixed documentation formatting.
- Added option to the "usual" parser to raise an error on an empty input string.
## 3.16.1 - 2023-09-01
- Fixed exception type on number parsing. (thank you @jasonpenny)
## 3.16.0 - 2023-08-16
- Added the `float_format` option.
- Expanded the `max_nesting` option to allow integer values as well as
the previous boolean (true or nil).
- Skip nesting tests with Truffle Ruby in the json gem tests.
## 3.15.1 - 2023-07-30
- Add protection against some using `require 'oj/json`, an internal file.
- Fixed non-json errors when in compat mode.
## 3.15.0 - 2023-06-02
- Added `omit_null_byte` option when dumping.
## 3.14.3 - 2023-04-07
- Fixed compat parse with optimized Hash when parsing a JSON::GenericObject.
## 3.14.2 - 2023-02-10
- Fixed check for \0 in strings.
## 3.14.1 - 2023-02-01
- Fixed issue with uninitialized handler for Oj::Parser::Saj.
- Fixed hang on unterminated string with a \0 byte in parse.c.
## 3.14.0 - 2023-01-30
- Tracing is now a compile time option giving a 15 to 20% performance boost.
- Some cleanup in the fast parser.
## 3.13.23 - 2022-11-06
- Fixed issue with Oj::Parser extension regarding GC timeing.
## 3.13.22 - 2022-11-01
- Reorganized Oj::Parser code to allow for parser extensions in C.
## 3.13.21 - 2022-08-19
- Bug parsing big numbers fixed in the SAJ parser.
## 3.13.20 - 2022-08-07
- SSE4 made optional with a `--with-sse42` flag to the compile.
## 3.13.19 - 2022-07-29
- TruffleRuby issues resolved.
## 3.13.18 - 2022-07-25
- Fixed SSE detection at run time.
## 3.13.17 - 2022-07-15
- Fixed Oj::Parser to detect unterminated arrays and objects.
## 3.13.16 - 2022-07-06
- Added line and column as optional arguments to the Oj::Parser.saj parser.
## 3.13.15 - 2022-07-03
- Fixed issue dumping NaN value in object mode.
## 3.13.14 - 2022-06-03
- Double fclose() due to bad merger fixed by tonobo.
## 3.13.13 - 2022-05-20
- Fixed flooding stdout with debug output when dumping.
## 3.13.12 - 2022-05-20
- Fixed crash on no arguments to pretty_generate. Now raises an exception.
- Register all classes and globals.
- Fixed memory issue with dumping.
## 3.13.11 - 2022-01-05
- Fixed write blocking failures on writes to a slow stream with larger writes.
## 3.13.10 - 2021-12-12
- Fixed Oj::Doc re-entrant issue with each_child.
- Fixed each_child on empty Oj::Doc.
## 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.16.3/LICENSE 0000644 0000041 0000041 00000002066 14540127115 013255 0 ustar www-data www-data The 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.16.3/RELEASE_NOTES.md 0000644 0000041 0000041 00000004315 14540127115 014621 0 ustar www-data www-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.16.3/lib/ 0000755 0000041 0000041 00000000000 14540127115 013012 5 ustar www-data www-data oj-3.16.3/lib/oj/ 0000755 0000041 0000041 00000000000 14540127115 013422 5 ustar www-data www-data oj-3.16.3/lib/oj/mimic.rb 0000644 0000041 0000041 00000020147 14540127115 015051 0 ustar www-data www-data # frozen_string_literal: false
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]))
super
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.16.3/lib/oj/version.rb 0000644 0000041 0000041 00000000106 14540127115 015431 0 ustar www-data www-data module Oj
# Current version of the module.
VERSION = '3.16.3'
end
oj-3.16.3/lib/oj/schandler.rb 0000644 0000041 0000041 00000007570 14540127115 015723 0 ustar www-data www-data module 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.16.3/lib/oj/error.rb 0000644 0000041 0000041 00000001166 14540127115 015104 0 ustar www-data www-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.16.3/lib/oj/json.rb 0000644 0000041 0000041 00000015214 14540127115 014723 0 ustar www-data www-data require 'ostruct'
require 'oj/state'
if defined?(JSON::PRETTY_STATE_PROTOTYPE)
# There are enough people that try to use both the json gen and oj in mimic
# mode so don't display the warning.
# warn "WARNING: oj/json is a compatability shim used by Oj. Requiring the file explicitly is not recommended."
end
unless defined?(JSON::PRETTY_STATE_PROTOTYPE)
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
# ignore and move on
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
end
oj-3.16.3/lib/oj/saj.rb 0000644 0000041 0000041 00000003660 14540127115 014531 0 ustar www-data www-data module Oj
# A SAX style parse handler for JSON hence the acronym SAJ for Simple API
# for JSON. The Oj::Saj handler class can be subclassed and then used with
# the Oj::Saj key_parse() method or with the more resent
# Oj::Parser.new(:saj). The Saj methods will then be called as the file is
# parsed.
#
# With Oj::Parser.new(:saj) each method can also include a line and column
# argument so hash_start(key) could also be hash_start(key, line,
# column). The error() method is no used with Oj::Parser.new(:saj) so it
# will never be called.
#
# @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
#
# or
#
# p = Oj::Parser.new(:saj)
# p.handler = MySaj.new()
# File.open('any.json', 'r') do |f|
# p.parse(f.read)
# 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.16.3/lib/oj/state.rb 0000644 0000041 0000041 00000010345 14540127115 015072 0 ustar www-data www-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, include_all = false)
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.16.3/lib/oj/bag.rb 0000644 0000041 0000041 00000006620 14540127115 014504 0 ustar www-data www-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.16.3/lib/oj/active_support_helper.rb 0000644 0000041 0000041 00000002060 14540127115 020353 0 ustar www-data www-data # frozen_string_literal: true
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.16.3/lib/oj/easy_hash.rb 0000644 0000041 0000041 00000003717 14540127115 015723 0 ustar www-data www-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
# 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.16.3/lib/oj.rb 0000644 0000041 0000041 00000000370 14540127115 013747 0 ustar www-data www-data # frozen_string_literal: true
# Oj module is defined in oj.c.
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.16.3/oj.gemspec 0000644 0000041 0000041 00000020043 14540127115 014220 0 ustar www-data www-data #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: oj 3.16.3 ruby lib
# stub: ext/oj/extconf.rb
Gem::Specification.new do |s|
s.name = "oj".freeze
s.version = "3.16.3"
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/", "rubygems_mfa_required" => "true", "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 = "2023-12-11"
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/InstallOptions.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/encoder.c".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/mem.c".freeze, "ext/oj/mem.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/saj2.h".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/usual.h".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/InstallOptions.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/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/activesupport7/abstract_unit.rb".freeze, "test/activesupport7/decoding_test.rb".freeze, "test/activesupport7/encoding_test.rb".freeze, "test/activesupport7/encoding_test_cases.rb".freeze, "test/activesupport7/time_zone_test_helpers.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_dump.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_debug.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]
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.7".freeze)
s.rubygems_version = "3.3.15".freeze
s.summary = "A fast JSON parser and serializer.".freeze
if s.respond_to? :specification_version then
s.specification_version = 4
end
if s.respond_to? :add_runtime_dependency then
s.add_runtime_dependency(%q.freeze, [">= 3.0"])
s.add_development_dependency(%q.freeze, ["~> 5"])
s.add_development_dependency(%q.freeze, [">= 0.9", "< 2.0"])
s.add_development_dependency(%q.freeze, ["~> 3.0"])
else
s.add_dependency(%q.freeze, [">= 3.0"])
s.add_dependency(%q.freeze, ["~> 5"])
s.add_dependency(%q.freeze, [">= 0.9", "< 2.0"])
s.add_dependency(%q.freeze, ["~> 3.0"])
end
end
oj-3.16.3/ext/ 0000755 0000041 0000041 00000000000 14540127115 013044 5 ustar www-data www-data oj-3.16.3/ext/oj/ 0000755 0000041 0000041 00000000000 14540127115 013454 5 ustar www-data www-data oj-3.16.3/ext/oj/strict.c 0000644 0000041 0000041 00000013470 14540127115 015135 0 ustar www-data www-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) {
TRACE_PARSE_HASH_END(pi->options.trace, pi);
}
static void array_end(ParseInfo pi) {
TRACE_PARSE_ARRAY_END(pi->options.trace, pi);
}
static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) {
return Qundef;
}
static void add_value(ParseInfo pi, VALUE val) {
TRACE_PARSE_CALL(pi->options.trace, "add_value", pi, 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;
TRACE_PARSE_CALL(pi->options.trace, "add_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "add_number", pi, 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);
}
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
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);
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "set_number", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, value);
}
static VALUE start_array(ParseInfo pi) {
TRACE_PARSE_IN(pi->options.trace, "start_array", pi);
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);
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, v);
}
static void array_append_value(ParseInfo pi, VALUE value) {
rb_ary_push(stack_peek(&pi->stack)->val, value);
TRACE_PARSE_CALL(pi->options.trace, "append_value", pi, 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.16.3/ext/oj/val_stack.c 0000644 0000041 0000041 00000005167 14540127115 015600 0 ustar www-data www-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 stack_mark(void *ptr) {
ValStack stack = (ValStack)ptr;
Val v;
if (NULL == 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
}
static const rb_data_type_t oj_stack_type = {
"Oj/stack",
{
stack_mark,
NULL,
NULL,
},
0,
0,
};
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 TypedData_Wrap_Struct(oj_cstack_class, &oj_stack_type, 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.16.3/ext/oj/code.c 0000644 0000041 0000041 00000014437 14540127115 014543 0 ustar www-data www-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++ = '"';
APPEND_CHARS(out->cur, out->opts->create_id, out->opts->create_id_len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
}
*out->cur++ = '"';
APPEND_CHARS(out->cur, classname, 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++ = '"';
APPEND_CHARS(out->cur, attrs->name, attrs->len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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;
bool neg = false;
long num = attrs->num;
size_t cnt = 0;
if (0 > num) {
neg = true;
num = -num;
}
*b-- = '\0';
if (0 < num) {
b = oj_longlong_to_string(num, neg, b);
} else {
*b = '0';
}
cnt = sizeof(buf) - (b - buf) - 1;
assure_size(out, cnt);
APPEND_CHARS(out->cur, b, cnt);
}
} 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.16.3/ext/oj/trace.c 0000644 0000041 0000041 00000004250 14540127115 014717 0 ustar www-data www-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.16.3/ext/oj/parser.h 0000644 0000041 0000041 00000005226 14540127115 015126 0 ustar www-data www-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 cur; // only set before call to a function
long col;
int ri;
uint32_t ucode;
ojType type; // valType
bool just_one;
} *ojParser;
// Create a new parser without setting the delegate. The parser is
// wrapped. The parser is (ojParser)DATA_PTR(value) where value is the return
// from this function. A delegate must be added before the parser can be
// used. Optionally oj_parser_set_options can be called if the options are not
// set directly.
extern VALUE oj_parser_new();
// Set set the options from a hash (ropts).
extern void oj_parser_set_option(ojParser p, VALUE ropts);
#endif /* OJ_PARSER_H */
oj-3.16.3/ext/oj/dump_object.c 0000644 0000041 0000041 00000053375 14540127115 016130 0 ustar www-data www-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 "mem.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);
APPEND_CHARS(out->cur, "{\"^t\":", 6);
dump_time(obj, out);
*out->cur++ = '}';
*out->cur = '\0';
} else {
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = oj_safe_string_convert(obj);
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 = oj_safe_string_convert(obj);
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);
APPEND_CHARS(out->cur, "{\"^c\":", 6);
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);
APPEND_CHARS(out->cur, "\"^i", 3);
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;
}
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
oj_dump_obj_val(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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 (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);
switch (rb_type(key)) {
case T_STRING:
dump_str_class(key, Qundef, depth, out);
*out->cur++ = ':';
oj_dump_obj_val(value, depth, out);
break;
case T_SYMBOL:
dump_sym(key, 0, out, false);
*out->cur++ = ':';
oj_dump_obj_val(value, depth, out);
break;
default: {
int d2 = depth + 1;
long s2 = size + out->indent + 1;
int i;
int started = 0;
uint8_t b;
assure_size(out, s2 + 15);
APPEND_CHARS(out->cur, "\"^#", 3);
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];
}
}
APPEND_CHARS(out->cur, "\":[", 3);
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) {
APPEND_CHARS(out->cur, "{}", 2);
} 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);
APPEND_CHARS(out->cur, "\"^i\":", 5);
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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
}
*out->cur++ = '}';
}
*out->cur = '\0';
}
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 (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;
}
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);
APPEND_CHARS(out->cur, "\"^O\":", 5);
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++ = '"';
APPEND_CHARS(out->cur, name, nlen);
APPEND_CHARS(out->cur, "\":", 2);
APPEND_CHARS(out->cur, s, 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 (NULL != *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 = OJ_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) {
OJ_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);
APPEND_CHARS(out->cur, "\"^o\":", 5);
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);
APPEND_CHARS(out->cur, "\"^i\":", 5);
dump_ulong(id, out);
}
switch (type) {
case T_STRING:
assure_size(out, d2 * out->indent + 14);
*out->cur++ = ',';
fill_indent(out, d2);
APPEND_CHARS(out->cur, "\"self\":", 7);
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);
APPEND_CHARS(out->cur, "\"self\":", 7);
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);
APPEND_CHARS(out->cur, "\"self\":", 7);
dump_hash_class(obj, Qundef, depth + 1, out);
break;
default: break;
}
{
int cnt = (int)rb_ivar_count(obj);
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;
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, 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);
APPEND_CHARS(out->cur, "\"^u\":[", 6);
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(RARRAY_AREF(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++ = '"';
APPEND_CHARS(out->cur, name, len);
*out->cur++ = '"';
}
*out->cur++ = ']';
} else {
fill_indent(out, d3);
*out->cur++ = '"';
APPEND_CHARS(out->cur, class_name, 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 (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 (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--;
APPEND_CHARS(out->cur, "]}", 2);
*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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, depth, TraceOut);
return;
}
}
oj_dump_nil(Qnil, depth, out, false);
TRACE(out->opts->trace, "dump", Qnil, depth, TraceOut);
}
oj-3.16.3/ext/oj/rails.c 0000644 0000041 0000041 00000131426 14540127115 014741 0 ustar www-data www-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 "mem.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 = OJ_R_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(RARRAY_AREF(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++ = '"';
APPEND_CHARS(out->cur, name, len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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 = oj_safe_string_convert(obj);
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;
if (16 <= sizeof(struct timespec)) {
struct timespec ts = rb_time_timespec(obj);
sec = (long long)ts.tv_sec;
nsec = ts.tv_nsec;
} else {
sec = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
}
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 = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
} else if (rb_respond_to(obj, oj_tv_usec_id)) {
nsec = 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 = oj_safe_string_convert(obj);
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 = OJ_R_ALLOC_N(struct _strLen, cnt);
for (i = 0, cp = cols; i < cnt; i++, cp++) {
v = RARRAY_AREF(rcols, i);
if (T_STRING != rb_type(v)) {
v = oj_safe_string_convert(v);
}
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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
dump_row(RARRAY_AREF(rows, i), cols, ccnt, d2, out);
if (i < rcnt - 1) {
*out->cur++ = ',';
}
}
OJ_R_FREE(cols);
size = depth * out->indent + 1;
assure_size(out, size);
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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;
TRACE(out->opts->trace, "as_json", obj, 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);
}
TRACE(out->opts->trace, "as_json", obj, 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 = OJ_R_ALLOC_N(struct _rOpt, rot->alen);
memset(rot->table, 0, sizeof(struct _rOpt) * rot->alen);
} else if (rot->alen <= rot->len) {
rot->alen *= 2;
OJ_R_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) {
OJ_R_FREE(e->ropts.table);
}
OJ_R_FREE(ptr);
}
}
static void encoder_mark(void *ptr) {
if (NULL != ptr) {
Encoder e = (Encoder)ptr;
if (Qnil != e->arg) {
rb_gc_mark(e->arg);
}
}
}
static const rb_data_type_t oj_encoder_type = {
"Oj/encoder",
{
encoder_mark,
encoder_free,
NULL,
},
0,
0,
};
/* 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 = OJ_R_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 TypedData_Wrap_Struct(encoder_class, &oj_encoder_type, 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;
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
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;
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
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;
ROpt ro;
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
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) {
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;
}
oj_out_init(&out);
out.omit_nil = copts.dump_opts.omit_nil;
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);
}
oj_out_free(&out);
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;
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
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(void) {
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_gc_register_address(&encoder_class);
rb_undef_alloc_func(encoder_class);
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 = oj_safe_string_convert(obj);
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;
}
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
dump_rails_val(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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 = oj_safe_string_convert(key);
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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, depth, TraceOut);
return;
}
}
oj_dump_nil(Qnil, depth, out, false);
TRACE(out->opts->trace, "dump", Qnil, 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.16.3/ext/oj/sparse.c 0000644 0000041 0000041 00000075172 14540127115 015131 0 ustar www-data www-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 "mem.h"
#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) {
OJ_R_FREE((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) {
OJ_R_FREE((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) {
OJ_R_FREE((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) {
OJ_R_FREE((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) {
#ifdef _WIN32
rb_w32_close(fd);
#else
close(fd);
#endif
}
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.16.3/ext/oj/reader.c 0000644 0000041 0000041 00000015515 14540127115 015071 0 ustar www-data www-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
#ifdef NEEDS_UIO
#if NEEDS_UIO
#include
#endif
#endif
#include
#include
#include "mem.h"
#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 = oj_safe_string_convert(io);
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 = OJ_R_ALLOC_N(char, size * 2);
memcpy((char *)reader->head, old, size);
} else {
OJ_R_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);
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);
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.16.3/ext/oj/validate.c 0000644 0000041 0000041 00000001776 14540127115 015424 0 ustar www-data www-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) {
Funcs end = p->funcs + 3;
Funcs f;
p->ctx = NULL;
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.16.3/ext/oj/dump.c 0000644 0000041 0000041 00000110340 14540127115 014564 0 ustar www-data www-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
#if !IS_WINDOWS
#include
#endif
#include "cache8.h"
#include "mem.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 slash_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";
// JSON standard but escape forward slashes `/`
static char slash_friendly_chars[256] = "\
66666666222622666666666666666666\
11211111111111121111111111111111\
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 calculate_string_size(const uint8_t *str, size_t len, const char *table) {
size_t size = 0;
size_t i = len;
for (; 3 < i; i -= 4) {
size += table[*str++];
size += table[*str++];
size += table[*str++];
size += table[*str++];
}
for (; 0 < i; i--) {
size += table[*str++];
}
return size - len * (size_t)'0';
}
inline static size_t newline_friendly_size(const uint8_t *str, size_t len) {
return calculate_string_size(str, len, newline_friendly_chars);
}
inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) {
return calculate_string_size(str, len, hibit_friendly_chars);
}
inline static size_t slash_friendly_size(const uint8_t *str, size_t len) {
return calculate_string_size(str, len, slash_friendly_chars);
}
inline static size_t ascii_friendly_size(const uint8_t *str, size_t len) {
return calculate_string_size(str, len, ascii_friendly_chars);
}
inline static size_t xss_friendly_size(const uint8_t *str, size_t len) {
return calculate_string_size(str, len, xss_friendly_chars);
}
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) {
return calculate_string_size(str, len, rails_friendly_chars);
}
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;
APPEND_CHARS(out->cur, "\\u", 2);
for (i = 3; 0 <= i; i--) {
*out->cur++ = hex_chars[(uint8_t)(c1 >> (i * 4)) & 0x0F];
}
}
APPEND_CHARS(out->cur, "\\u", 2);
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);
APPEND_CHARS(out->cur, "\"^r", 3);
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;
// 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 = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
}
*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);
APPEND_CHARS(out->cur, b, size);
*out->cur = '\0';
}
void oj_dump_ruby_time(VALUE obj, Out out) {
volatile VALUE rstr = oj_safe_string_convert(obj);
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 = '+';
if (16 <= sizeof(struct timespec)) {
struct timespec ts = rb_time_timespec(obj);
sec = ts.tv_sec;
nsec = ts.tv_nsec;
} else {
sec = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
}
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) {
oj_out_init(out);
}
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) {
struct _out out;
size_t size;
FILE *f;
int ok;
oj_out_init(&out);
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"))) {
oj_out_free(&out);
rb_raise(rb_eIOError, "%s", strerror(errno));
}
ok = (size == fwrite(out.buf, 1, size, f));
oj_out_free(&out);
if (!ok) {
int err = ferror(f);
fclose(f);
rb_raise(rb_eIOError, "Write failed. [%d:%s]", err, strerror(err));
}
fclose(f);
}
#if !IS_WINDOWS
static void write_ready(int fd) {
struct pollfd pp;
int i;
pp.fd = fd;
pp.events = POLLERR | POLLOUT;
pp.revents = 0;
if (0 >= (i = poll(&pp, 1, 5000))) {
if (0 == i || EAGAIN == errno) {
rb_raise(rb_eIOError, "write timed out");
}
rb_raise(rb_eIOError, "write failed. %d %s.", errno, strerror(errno));
}
}
#endif
void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts) {
struct _out out;
ssize_t size;
VALUE clas = rb_obj_class(stream);
#if !IS_WINDOWS
int fd;
VALUE s;
#endif
oj_out_init(&out);
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))) {
ssize_t cnt;
ssize_t total = 0;
while (true) {
if (0 > (cnt = write(fd, out.buf + total, size - total))) {
if (EAGAIN != errno) {
rb_raise(rb_eIOError, "write failed. %d %s.", errno, strerror(errno));
break;
}
}
total += cnt;
if (size <= total) {
// Completed
break;
}
write_ready(fd);
}
#endif
} else if (rb_respond_to(stream, oj_write_id)) {
rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size));
} else {
oj_out_free(&out);
rb_raise(rb_eArgError, "to_stream() expected an IO Object.");
}
oj_out_free(&out);
}
void oj_dump_str(VALUE obj, int depth, Out out, bool as_ok) {
int idx = RB_ENCODING_GET(obj);
if (oj_utf8_encoding_index != idx) {
rb_encoding *enc = rb_enc_from_index(idx);
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;
size_t len;
sw = oj_str_writer_unwrap(obj);
len = sw->out.cur - sw->out.buf;
if (0 < len) {
len--;
}
oj_dump_raw(sw->out.buf, len, out);
} else {
volatile VALUE jv;
TRACE(out->opts->trace, "raw_json", obj, depth + 1, TraceRubyIn);
jv = rb_funcall(obj, oj_raw_json_id, 2, RB_INT2NUM(depth), RB_INT2NUM(out->indent));
TRACE(out->opts->trace, "raw_json", obj, 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 SlashEsc:
has_hi = true;
cmap = slash_friendly_chars;
size = slash_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) {
APPEND_CHARS(out->cur, "\\u00", 4);
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++ = ':';
}
APPEND_CHARS(out->cur, str, 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) {
if (0 == (uint8_t)*str && out->opts->dump_opts.omit_null_byte) {
break;
}
APPEND_CHARS(out->cur, "\\u00", 4);
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 = oj_safe_string_convert(obj);
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);
APPEND_CHARS(out->cur, str, cnt);
*out->cur = '\0';
}
void oj_out_init(Out out) {
out->buf = out->stack_buffer;
out->cur = out->buf;
out->end = out->buf + sizeof(out->stack_buffer) - BUFFER_EXTRA;
out->allocated = false;
}
void oj_out_free(Out out) {
if (out->allocated) {
OJ_R_FREE(out->buf); // TBD
}
}
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) {
OJ_R_REALLOC_N(buf, char, (size + BUFFER_EXTRA));
} else {
buf = OJ_R_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);
APPEND_CHARS(out->cur, "null", 4);
*out->cur = '\0';
}
void oj_dump_true(VALUE obj, int depth, Out out, bool as_ok) {
assure_size(out, 4);
APPEND_CHARS(out->cur, "true", 4);
*out->cur = '\0';
}
void oj_dump_false(VALUE obj, int depth, Out out, bool as_ok) {
assure_size(out, 5);
APPEND_CHARS(out->cur, "false", 5);
*out->cur = '\0';
}
static const char digits_table[] = "\
00010203040506070809\
10111213141516171819\
20212223242526272829\
30313233343536373839\
40414243444546474849\
50515253545556575859\
60616263646566676869\
70717273747576777879\
80818283848586878889\
90919293949596979899";
char *oj_longlong_to_string(long long num, bool negative, char *buf) {
while (100 <= num) {
unsigned idx = num % 100 * 2;
*buf-- = digits_table[idx + 1];
*buf-- = digits_table[idx];
num /= 100;
}
if (num < 10) {
*buf-- = num + '0';
} else {
*buf-- = digits_table[num * 2 + 1];
*buf-- = digits_table[num * 2];
}
if (negative) {
*buf = '-';
} else {
buf++;
}
return buf;
}
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 = NUM2LL(obj);
bool neg = false;
size_t cnt = 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 = true;
num = -num;
}
*b-- = '\0';
if (dump_as_string) {
*b-- = '"';
}
if (0 < num) {
b = oj_longlong_to_string(num, neg, b);
} else {
*b = '0';
}
if (dump_as_string) {
*--b = '"';
}
cnt = sizeof(buf) - (b - buf) - 1;
assure_size(out, cnt);
APPEND_CHARS(out->cur, b, cnt);
*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);
}
APPEND_CHARS(out->cur, RSTRING_PTR(rs), 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(nan_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 = oj_safe_string_convert(obj);
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);
APPEND_CHARS(out->cur, buf, cnt);
*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 = oj_safe_string_convert(obj);
strcpy(buf, RSTRING_PTR(rstr));
cnt = (int)RSTRING_LEN(rstr);
}
return cnt;
}
oj-3.16.3/ext/oj/util.c 0000644 0000041 0000041 00000010020 14540127115 014566 0 ustar www-data www-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.16.3/ext/oj/usual.c 0000644 0000041 0000041 00000105274 14540127115 014762 0 ustar www-data www-data // Copyright (c) 2021, Peter Ohler, All rights reserved.
#include "usual.h"
#include "cache.h"
#include "mem.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
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 = OJ_R_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 = OJ_R_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);
OJ_R_FREE(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(Usual d) {
if (d->cend <= d->ctail + 1) {
size_t cap = d->cend - d->chead;
long pos = d->ctail - d->chead;
cap *= 2;
OJ_R_REALLOC_N(d->chead, struct _col, cap);
d->ctail = d->chead + pos;
d->cend = d->chead + cap;
}
}
static void push(ojParser p, VALUE v) {
Usual d = (Usual)p->ctx;
if (d->vend <= d->vtail) {
size_t cap = d->vend - d->vhead;
long pos = d->vtail - d->vhead;
cap *= 2;
OJ_R_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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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;
OJ_R_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) {
Usual d = (Usual)p->ctx;
if (d->vend <= d->vtail + 1) {
size_t cap = d->vend - d->vhead;
long pos = d->vtail - d->vhead;
cap *= 2;
OJ_R_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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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;
Usual d = (Usual)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) {
OJ_R_FREE(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) {
OJ_R_FREE(kp->key);
}
}
#endif
d->ktail = d->khead + c->ki;
d->vtail = head;
head--;
*head = obj;
}
static void close_object_class(ojParser p) {
VALUE *vp;
Usual d = (Usual)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) {
OJ_R_FREE(kp->key);
}
}
d->ktail = d->khead + c->ki;
d->vtail = head;
head--;
*head = obj;
}
static void close_object_create(ojParser p) {
VALUE *vp;
Usual d = (Usual)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) {
OJ_R_FREE(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) {
OJ_R_FREE(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) {
OJ_R_FREE(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) {
OJ_R_FREE(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) {
OJ_R_FREE(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) {
OJ_R_FREE(kp->key);
}
}
}
}
d->ktail = d->khead + c->ki;
d->vtail = head;
head--;
*head = obj;
}
static void close_array(ojParser p) {
Usual d = (Usual)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;
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
if (d->vhead < d->vtail) {
return *d->vhead;
}
if (d->raise_on_empty) {
rb_raise(oj_parse_error_class, "empty string");
}
return Qnil;
}
static void start(ojParser p) {
Usual d = (Usual)p->ctx;
d->vtail = d->vhead;
d->ctail = d->chead;
d->ktail = d->khead;
}
static void dfree(ojParser p) {
Usual d = (Usual)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);
}
OJ_R_FREE(d->vhead);
OJ_R_FREE(d->chead);
OJ_R_FREE(d->khead);
OJ_R_FREE(d->create_id);
OJ_R_FREE(p->ctx);
p->ctx = NULL;
}
static void mark(ojParser p) {
if (NULL == p || NULL == p->ctx) {
return;
}
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return d->array_class;
}
static VALUE opt_array_class_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return d->cache_keys ? Qtrue : Qfalse;
}
static VALUE opt_cache_keys_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return INT2NUM((int)d->cache_str);
}
static VALUE opt_cache_strings_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return INT2NUM((int)d->cache_xrate);
}
static VALUE opt_cache_expunge_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return ULONG2NUM(d->vend - d->vhead);
}
static VALUE opt_capacity_set(ojParser p, VALUE value) {
Usual d = (Usual)p->ctx;
long cap = NUM2LONG(value);
if (d->vend - d->vhead < cap) {
long pos = d->vtail - d->vhead;
OJ_R_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;
OJ_R_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) {
Usual d = (Usual)p->ctx;
return (NULL != d->class_cache) ? Qtrue : Qfalse;
}
static VALUE opt_class_cache_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return d->hash_class;
}
static VALUE opt_hash_class_set(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return d->ignore_json_create ? Qtrue : Qfalse;
}
static VALUE opt_ignore_json_create_set(ojParser p, VALUE value) {
Usual d = (Usual)p->ctx;
d->ignore_json_create = (Qtrue == value);
return d->ignore_json_create ? Qtrue : Qfalse;
}
static VALUE opt_missing_class(ojParser p, VALUE value) {
Usual d = (Usual)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) {
Usual d = (Usual)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) {
Usual d = (Usual)p->ctx;
return (NULL != d->sym_cache) ? Qtrue : Qfalse;
}
static VALUE opt_symbol_keys_set(ojParser p, VALUE value) {
Usual d = (Usual)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 opt_raise_on_empty(ojParser p, VALUE value) {
Usual d = (Usual)p->ctx;
return d->raise_on_empty ? Qtrue : Qfalse;
}
static VALUE opt_raise_on_empty_set(ojParser p, VALUE value) {
Usual d = (Usual)p->ctx;
d->raise_on_empty = (Qtrue == value);
return d->raise_on_empty ? 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 = "raise_on_empty", .func = opt_raise_on_empty},
{.name = "raise_on_empty=", .func = opt_raise_on_empty_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_init_usual(ojParser p, Usual d) {
int cap = 4096;
d->vhead = OJ_R_ALLOC_N(VALUE, cap);
d->vend = d->vhead + cap;
d->vtail = d->vhead;
d->khead = OJ_R_ALLOC_N(union _key, cap);
d->kend = d->khead + cap;
d->ktail = d->khead;
cap = 256;
d->chead = OJ_R_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->raise_on_empty = 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;
// The parser fields are set but the functions can be replaced by a
// delegate that wraps the usual delegate.
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("[]=");
}
}
void oj_set_parser_usual(ojParser p) {
Usual d = OJ_R_ALLOC(struct _usual);
oj_init_usual(p, d);
}
oj-3.16.3/ext/oj/dump_leaf.c 0000644 0000041 0000041 00000011311 14540127115 015551 0 ustar www-data www-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);
inline static void dump_chars(const char *s, size_t size, Out out) {
assure_size(out, size);
APPEND_CHARS(out->cur, s, 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(StringValueCStr(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;
assure_size(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 {
assure_size(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;
assure_size(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;
assure_size(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 {
assure_size(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;
assure_size(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) {
oj_out_init(out);
}
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) {
struct _out out;
size_t size;
FILE *f;
oj_out_init(&out);
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));
}
oj_out_free(&out);
fclose(f);
}
oj-3.16.3/ext/oj/resolve.h 0000644 0000041 0000041 00000000650 14540127115 015305 0 ustar www-data www-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.16.3/ext/oj/dump_strict.c 0000644 0000041 0000041 00000031450 14540127115 016160 0 ustar www-data www-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 = oj_safe_string_convert(obj);
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);
APPEND_CHARS(out->cur, buf, cnt);
*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;
}
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
if (NullMode == out->opts->mode) {
oj_dump_null_val(RARRAY_AREF(a, i), d2, out);
} else {
oj_dump_strict_val(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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 = oj_safe_string_convert(obj);
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 = oj_safe_string_convert(obj);
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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, depth, TraceOut);
return;
}
}
oj_dump_nil(Qnil, depth, out, false);
TRACE(out->opts->trace, "dump", Qnil, depth, TraceOut);
}
oj-3.16.3/ext/oj/scp.c 0000644 0000041 0000041 00000013326 14540127115 014412 0 ustar www-data www-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.16.3/ext/oj/cache8.h 0000644 0000041 0000041 00000001050 14540127115 014754 0 ustar www-data www-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.16.3/ext/oj/circarray.c 0000644 0000041 0000041 00000003064 14540127115 015602 0 ustar www-data www-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"
#include "mem.h"
CircArray oj_circ_array_new(void) {
CircArray ca;
if (0 == (ca = OJ_R_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) {
OJ_R_FREE(ca->objs);
}
OJ_R_FREE(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 = OJ_R_ALLOC_N(VALUE, cnt))) {
rb_raise(rb_eNoMemError, "not enough memory\n");
}
memcpy(ca->objs, ca->obj_array, sizeof(VALUE) * ca->cnt);
} else {
OJ_R_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.16.3/ext/oj/wab.c 0000644 0000041 0000041 00000040613 14540127115 014375 0 ustar www-data www-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(void) {
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(void) {
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(void) {
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;
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
fill_indent(out, d2);
oj_dump_wab_val(RARRAY_AREF(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;
if (16 <= sizeof(struct timespec)) {
struct timespec ts = rb_time_timespec(obj);
sec = ts.tv_sec;
nsec = ts.tv_nsec;
} else {
sec = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
}
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 = oj_safe_string_convert(obj);
oj_dump_raw(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), out);
} else if (resolve_wab_uuid_class() == clas) {
oj_dump_str(oj_safe_string_convert(obj), depth, out, false);
} else if (resolve_uri_http_class() == clas) {
oj_dump_str(oj_safe_string_convert(obj), 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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, 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) {
TRACE_PARSE_HASH_END(pi->options.trace, pi);
}
static void array_end(ParseInfo pi) {
TRACE_PARSE_ARRAY_END(pi->options.trace, pi);
}
static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) {
return Qundef;
}
static void add_value(ParseInfo pi, VALUE val) {
TRACE_PARSE_CALL(pi->options.trace, "add_value", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "add_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "add_number", pi, pi->stack.head->val);
}
static VALUE start_hash(ParseInfo pi) {
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
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);
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "set_number", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, value);
}
static VALUE start_array(ParseInfo pi) {
TRACE_PARSE_IN(pi->options.trace, "start_array", pi);
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);
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, rval);
}
static void array_append_value(ParseInfo pi, VALUE value) {
rb_ary_push(stack_peek(&pi->stack)->val, value);
TRACE_PARSE_CALL(pi->options.trace, "append_value", pi, 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.16.3/ext/oj/encode.h 0000644 0000041 0000041 00000000604 14540127115 015062 0 ustar www-data www-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.16.3/ext/oj/stream_writer.c 0000644 0000041 0000041 00000026045 14540127115 016516 0 ustar www-data www-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"
#include "mem.h"
extern VALUE Oj;
static void stream_writer_free(void *ptr) {
StreamWriter sw;
if (0 == ptr) {
return;
}
sw = (StreamWriter)ptr;
OJ_R_FREE(sw->sw.out.buf);
OJ_R_FREE(sw->sw.types);
OJ_R_FREE(ptr);
}
static const rb_data_type_t oj_stream_writer_type = {
"Oj/stream_writer",
{
NULL,
stream_writer_free,
NULL,
},
0,
0,
};
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 = OJ_R_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))) {
if (rb_cInteger != rb_obj_class(v)) {
OJ_R_FREE(sw);
rb_raise(rb_eArgError, ":buffer size must be a Integer.");
}
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 TypedData_Wrap_Struct(oj_stream_writer_class, &oj_stream_writer_type, 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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
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 {
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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
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 {
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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
switch (argc) {
case 1: oj_str_writer_push_value((StrWriter)sw, *argv, 0); break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_value((StrWriter)sw, *argv, 0);
} else {
oj_str_writer_push_value((StrWriter)sw, *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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
switch (argc) {
case 1: oj_str_writer_push_json((StrWriter)sw, StringValuePtr(*argv), 0); break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_json((StrWriter)sw, StringValuePtr(*argv), 0);
} else {
oj_str_writer_push_json((StrWriter)sw, 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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
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;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
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) {
StreamWriter sw;
TypedData_Get_Struct(self, struct _streamWriter, &oj_stream_writer_type, sw);
stream_writer_write(sw);
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(void) {
oj_stream_writer_class = rb_define_class_under(Oj, "StreamWriter", rb_cObject);
rb_gc_register_address(&oj_stream_writer_class);
rb_undef_alloc_func(oj_stream_writer_class);
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.16.3/ext/oj/trace.h 0000644 0000041 0000041 00000004133 14540127115 014724 0 ustar www-data www-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);
#ifdef OJ_ENABLE_TRACE_LOG
#define TRACE(option, func, obj, depth, where) \
if (RB_UNLIKELY(Yes == option)) { \
oj_trace(func, obj, __FILE__, __LINE__, depth, where); \
}
#define TRACE_PARSE_IN(option, func, pi) \
if (RB_UNLIKELY(Yes == option)) { \
oj_trace_parse_in(func, pi, __FILE__, __LINE__); \
}
#define TRACE_PARSE_CALL(option, func, pi, obj) \
if (RB_UNLIKELY(Yes == option)) { \
oj_trace_parse_call(func, pi, __FILE__, __LINE__, obj); \
}
#define TRACE_PARSE_HASH_END(option, pi) \
if (RB_UNLIKELY(Yes == option)) { \
oj_trace_parse_hash_end(pi, __FILE__, __LINE__); \
}
#define TRACE_PARSE_ARRAY_END(option, pi) \
if (RB_UNLIKELY(Yes == option)) { \
oj_trace_parse_array_end(pi, __FILE__, __LINE__); \
}
#else
#define TRACE(option, func, obj, depth, where)
#define TRACE_PARSE_IN(option, func, pi)
#define TRACE_PARSE_CALL(option, func, pi, obj)
#define TRACE_PARSE_HASH_END(option, pi)
#define TRACE_PARSE_ARRAY_END(option, pi)
#endif
#endif /* OJ_TRACE_H */
oj-3.16.3/ext/oj/oj.h 0000644 0000041 0000041 00000027643 14540127115 014251 0 ustar www-data www-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"
#if __STDC_VERSION__ >= 199901L
// To avoid using ruby_snprintf with C99.
#undef snprintf
#include
#endif
// To avoid using ruby_nonempty_memcpy().
#undef memcpy
#include
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',
SlashEsc = 's',
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 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;
bool omit_null_byte;
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 stack_buffer[4096];
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;
bool omit_null_byte;
int argc;
VALUE *argv;
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 char *oj_longlong_to_string(long long num, bool negative, char *buf);
extern StrWriter oj_str_writer_unwrap(VALUE writer);
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(void);
extern void oj_stream_writer_init(void);
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 int oj_utf8_encoding_index;
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_skip_null_byte_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_json_create_id;
extern ID oj_length_id;
extern ID oj_new_id;
extern ID oj_parse_id;
extern ID oj_plus_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;
static inline VALUE oj_safe_string_convert(VALUE obj) {
VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
StringValue(rstr);
return rstr;
}
#define APPEND_CHARS(buffer, chars, size) \
{ \
memcpy(buffer, chars, size); \
buffer += size; \
}
#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.16.3/ext/oj/saj2.c 0000644 0000041 0000041 00000043335 14540127115 014467 0 ustar www-data www-data // Copyright (c) 2021, Peter Ohler, All rights reserved.
#include "saj2.h"
#include "cache.h"
#include "mem.h"
#include "oj.h"
#include "parser.h"
static VALUE get_key(ojParser p) {
Saj d = (Saj)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(Saj d, VALUE key) {
if (d->klen <= (size_t)(d->tail - d->keys)) {
size_t off = d->tail - d->keys;
d->klen += d->klen / 2;
OJ_R_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(((Saj)p->ctx)->handler, oj_hash_start_id, 1, Qnil);
}
static void open_object_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_hash_start_id, 3, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void open_object_key(ojParser p) {
Saj d = (Saj)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_object_loc_key(ojParser p) {
Saj d = (Saj)p->ctx;
volatile VALUE key = get_key(p);
push_key(d, key);
rb_funcall(d->handler, oj_hash_start_id, 3, key, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void open_array(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_array_start_id, 1, Qnil);
}
static void open_array_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_array_start_id, 3, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void open_array_key(ojParser p) {
Saj d = (Saj)p->ctx;
volatile VALUE key = get_key(p);
push_key(d, key);
rb_funcall(d->handler, oj_array_start_id, 1, key);
}
static void open_array_loc_key(ojParser p) {
Saj d = (Saj)p->ctx;
volatile VALUE key = get_key(p);
push_key(d, key);
rb_funcall(d->handler, oj_array_start_id, 3, key, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void close_object(ojParser p) {
Saj d = (Saj)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_object_loc(ojParser p) {
Saj d = (Saj)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, 3, key, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void close_array(ojParser p) {
Saj d = (Saj)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 close_array_loc(ojParser p) {
Saj d = (Saj)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, 3, key, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void add_null(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qnil, Qnil);
}
static void add_null_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 4, Qnil, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void add_null_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qnil, get_key(p));
}
static void add_null_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
Qnil,
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_true(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qtrue, Qnil);
}
static void add_true_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 4, Qtrue, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void add_true_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qtrue, get_key(p));
}
static void add_true_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
Qtrue,
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_false(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qfalse, Qnil);
}
static void add_false_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 4, Qfalse, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void add_false_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, Qfalse, get_key(p));
}
static void add_false_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
Qfalse,
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_int(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, LONG2NUM(p->num.fixnum), Qnil);
}
static void add_int_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
LONG2NUM(p->num.fixnum),
Qnil,
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_int_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, LONG2NUM(p->num.fixnum), get_key(p));
}
static void add_int_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
LONG2NUM(p->num.fixnum),
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_float(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, rb_float_new(p->num.dub), Qnil);
}
static void add_float_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
rb_float_new(p->num.dub),
Qnil,
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_float_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler, oj_add_value_id, 2, rb_float_new(p->num.dub), get_key(p));
}
static void add_float_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
rb_float_new(p->num.dub),
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_big(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
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_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))),
Qnil,
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_big_key(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
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_big_key_loc(ojParser p) {
rb_funcall(((Saj)p->ctx)->handler,
oj_add_value_id,
4,
rb_funcall(rb_cObject, oj_bigdecimal_id, 1, rb_str_new(buf_str(&p->buf), buf_len(&p->buf))),
get_key(p),
LONG2FIX(p->line),
LONG2FIX(p->cur - p->col));
}
static void add_str(ojParser p) {
Saj d = (Saj)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_loc(ojParser p) {
Saj d = (Saj)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, 4, rstr, Qnil, LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
static void add_str_key(ojParser p) {
Saj d = (Saj)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 add_str_key_loc(ojParser p) {
Saj d = (Saj)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, 4, rstr, get_key(p), LONG2FIX(p->line), LONG2FIX(p->cur - p->col));
}
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) {
Saj d = (Saj)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)) {
if (1 == rb_obj_method_arity(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;
} else {
p->funcs[TOP_FUN].open_object = open_object_loc;
p->funcs[ARRAY_FUN].open_object = open_object_loc;
p->funcs[OBJECT_FUN].open_object = open_object_loc_key;
}
}
if (rb_respond_to(value, oj_array_start_id)) {
if (1 == rb_obj_method_arity(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;
} else {
p->funcs[TOP_FUN].open_array = open_array_loc;
p->funcs[ARRAY_FUN].open_array = open_array_loc;
p->funcs[OBJECT_FUN].open_array = open_array_loc_key;
}
}
if (rb_respond_to(value, oj_hash_end_id)) {
if (1 == rb_obj_method_arity(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;
} else {
p->funcs[TOP_FUN].close_object = close_object_loc;
p->funcs[ARRAY_FUN].close_object = close_object_loc;
p->funcs[OBJECT_FUN].close_object = close_object_loc;
}
}
if (rb_respond_to(value, oj_array_end_id)) {
if (1 == rb_obj_method_arity(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;
} else {
p->funcs[TOP_FUN].close_array = close_array_loc;
p->funcs[ARRAY_FUN].close_array = close_array_loc;
p->funcs[OBJECT_FUN].close_array = close_array_loc;
}
}
if (rb_respond_to(value, oj_add_value_id)) {
if (2 == rb_obj_method_arity(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;
} else {
p->funcs[TOP_FUN].add_null = add_null_loc;
p->funcs[ARRAY_FUN].add_null = add_null_loc;
p->funcs[OBJECT_FUN].add_null = add_null_key_loc;
p->funcs[TOP_FUN].add_true = add_true_loc;
p->funcs[ARRAY_FUN].add_true = add_true_loc;
p->funcs[OBJECT_FUN].add_true = add_true_key_loc;
p->funcs[TOP_FUN].add_false = add_false_loc;
p->funcs[ARRAY_FUN].add_false = add_false_loc;
p->funcs[OBJECT_FUN].add_false = add_false_key_loc;
p->funcs[TOP_FUN].add_int = add_int_loc;
p->funcs[ARRAY_FUN].add_int = add_int_loc;
p->funcs[OBJECT_FUN].add_int = add_int_key_loc;
p->funcs[TOP_FUN].add_float = add_float_loc;
p->funcs[ARRAY_FUN].add_float = add_float_loc;
p->funcs[OBJECT_FUN].add_float = add_float_key_loc;
p->funcs[TOP_FUN].add_big = add_big_loc;
p->funcs[ARRAY_FUN].add_big = add_big_loc;
p->funcs[OBJECT_FUN].add_big = add_big_key_loc;
p->funcs[TOP_FUN].add_str = add_str_loc;
p->funcs[ARRAY_FUN].add_str = add_str_loc;
p->funcs[OBJECT_FUN].add_str = add_str_key_loc;
}
}
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) saj", 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) {
Saj d = (Saj)p->ctx;
d->tail = d->keys;
}
static void dfree(ojParser p) {
Saj d = (Saj)p->ctx;
if (NULL != d->keys) {
OJ_R_FREE(d->keys);
}
cache_free(d->str_cache);
OJ_R_FREE(p->ctx);
}
static void mark(ojParser p) {
if (NULL == p || NULL == p->ctx) {
return;
}
Saj d = (Saj)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_init_saj(ojParser p, Saj d) {
d->klen = 256;
d->keys = OJ_R_ALLOC_N(VALUE, d->klen);
d->tail = d->keys;
d->handler = Qnil;
d->str_cache = cache_create(0, form_str, true, false);
d->cache_str = 16;
d->cache_keys = true;
d->thread_safe = false;
p->ctx = (void *)d;
reset(p);
p->option = option;
p->result = result;
p->free = dfree;
p->mark = mark;
p->start = start;
}
void oj_set_parser_saj(ojParser p) {
Saj d = OJ_R_ALLOC(struct _saj);
oj_init_saj(p, d);
}
oj-3.16.3/ext/oj/util.h 0000644 0000041 0000041 00000000625 14540127115 014605 0 ustar www-data www-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.16.3/ext/oj/err.h 0000644 0000041 0000041 00000003304 14540127115 014415 0 ustar www-data www-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.16.3/ext/oj/cache.h 0000644 0000041 0000041 00000001420 14540127115 014665 0 ustar www-data www-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;
typedef struct _cache *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(void *data);
extern void cache_mark(void *data);
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.16.3/ext/oj/err.c 0000644 0000041 0000041 00000003132 14540127115 014407 0 ustar www-data www-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.16.3/ext/oj/rxclass.c 0000644 0000041 0000041 00000006657 14540127115 015315 0 ustar www-data www-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 "mem.h"
#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);
}
OJ_R_FREE(rxc);
#endif
}
}
void oj_rxclass_rappend(RxClass rc, VALUE rx, VALUE clas) {
RxC rxc = OJ_R_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 = OJ_R_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));
OJ_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.16.3/ext/oj/intern.c 0000644 0000041 0000041 00000020561 14540127115 015123 0 ustar www-data www-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 "mem.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 VALUE str_cache_obj;
static VALUE sym_cache_obj;
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 = OJ_R_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);
OJ_R_FREE(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);
}
static const rb_data_type_t oj_cache_type = {
"Oj/cache",
{
cache_mark,
cache_free,
NULL,
},
0,
0,
};
void oj_hash_init(void) {
VALUE cache_class = rb_define_class_under(Oj, "Cache", rb_cObject);
rb_undef_alloc_func(cache_class);
struct _cache *str_cache = cache_create(0, form_str, true, true);
str_cache_obj = TypedData_Wrap_Struct(cache_class, &oj_cache_type, str_cache);
rb_gc_register_address(&str_cache_obj);
struct _cache *sym_cache = cache_create(0, form_sym, true, true);
sym_cache_obj = TypedData_Wrap_Struct(cache_class, &oj_cache_type, sym_cache);
rb_gc_register_address(&sym_cache_obj);
struct _cache *attr_cache = cache_create(0, form_attr, false, true);
attr_cache_obj = TypedData_Wrap_Struct(cache_class, &oj_cache_type, 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
Cache c;
TypedData_Get_Struct(str_cache_obj, struct _cache, &oj_cache_type, c);
return cache_intern(c, key, len);
#endif
}
VALUE
oj_sym_intern(const char *key, size_t len) {
Cache c;
TypedData_Get_Struct(sym_cache_obj, struct _cache, &oj_cache_type, c);
return cache_intern(c, key, len);
}
ID oj_attr_intern(const char *key, size_t len) {
Cache c;
TypedData_Get_Struct(attr_cache_obj, struct _cache, &oj_cache_type, c);
return cache_intern(c, 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 = OJ_R_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 = OJ_R_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);
}
rb_gc_register_mark_object(bucket->val);
return bucket->val;
}
char *oj_strndup(const char *s, size_t len) {
char *d = OJ_R_ALLOC_N(char, len + 1);
memcpy(d, s, len);
d[len] = '\0';
return d;
}
/*
void intern_cleanup(void) {
cache_free(str_cache);
cache_free(sym_cache);
cache_free(attr_cache);
}
*/
oj-3.16.3/ext/oj/mem.c 0000644 0000041 0000041 00000020547 14540127115 014406 0 ustar www-data www-data // Copyright (c) 2018, Peter Ohler, All rights reserved.
#include "mem.h"
#include
#include
#include
#include
#include
#include
#include
typedef struct _rec {
struct _rec *next;
const void *ptr;
size_t size;
const char *file;
int line;
bool ruby;
} *Rec;
typedef struct _rep {
struct _rep *next;
size_t size;
const char *file;
int line;
int cnt;
} *Rep;
#ifdef MEM_DEBUG
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static Rec recs = NULL;
static const char mem_pad[] = "--- This is a memory pad and should not change until being freed. ---";
void *oj_malloc(size_t size, const char *file, int line) {
void *ptr = malloc(size + sizeof(mem_pad));
if (NULL != ptr) {
Rec r = (Rec)malloc(sizeof(struct _rec));
if (NULL != r) {
strcpy(((char *)ptr) + size, mem_pad);
r->ptr = ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = false;
pthread_mutex_lock(&lock);
r->next = recs;
recs = r;
pthread_mutex_unlock(&lock);
} else {
free(ptr);
ptr = NULL;
}
}
return ptr;
}
void *oj_realloc(void *orig, size_t size, const char *file, int line) {
void *ptr = realloc(orig, size + sizeof(mem_pad));
Rec r;
if (NULL != ptr) {
strcpy(((char *)ptr) + size, mem_pad);
pthread_mutex_lock(&lock);
for (r = recs; NULL != r; r = r->next) {
if (orig == r->ptr) {
r->ptr = ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = false;
break;
}
}
pthread_mutex_unlock(&lock);
if (NULL == r) {
printf("Realloc at %s:%d (%p) not allocated.\n", file, line, orig);
}
}
return ptr;
}
void *oj_calloc(size_t count, size_t size, const char *file, int line) {
void *ptr;
size *= count;
if (NULL != (ptr = malloc(size + sizeof(mem_pad)))) {
Rec r = (Rec)malloc(sizeof(struct _rec));
if (NULL != r) {
memset(ptr, 0, size);
strcpy(((char *)ptr) + size, mem_pad);
r->ptr = ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = false;
pthread_mutex_lock(&lock);
r->next = recs;
recs = r;
pthread_mutex_unlock(&lock);
} else {
free(ptr);
ptr = NULL;
}
}
return ptr;
}
void *oj_r_alloc(size_t size, const char *file, int line) {
void *ptr = ruby_xmalloc(size + sizeof(mem_pad));
if (NULL != ptr) {
Rec r = (Rec)malloc(sizeof(struct _rec));
if (NULL != r) {
strcpy(((char *)ptr) + size, mem_pad);
r->ptr = ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = true;
pthread_mutex_lock(&lock);
r->next = recs;
recs = r;
pthread_mutex_unlock(&lock);
} else {
free(ptr);
ptr = NULL;
}
}
return ptr;
}
void *oj_r_realloc(void *orig, size_t size, const char *file, int line) {
void *ptr = ruby_xrealloc2(orig, 1, size + sizeof(mem_pad));
Rec r;
if (NULL != ptr) {
strcpy(((char *)ptr) + size, mem_pad);
pthread_mutex_lock(&lock);
for (r = recs; NULL != r; r = r->next) {
if (orig == r->ptr) {
r->ptr = ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = true;
break;
}
}
pthread_mutex_unlock(&lock);
if (NULL == r) {
printf("Realloc at %s:%d (%p) not allocated.\n", file, line, orig);
}
}
return ptr;
}
void oj_freed(void *ptr, const char *file, int line, bool ruby) {
if (NULL != ptr) {
Rec r = NULL;
Rec prev = NULL;
pthread_mutex_lock(&lock);
for (r = recs; NULL != r; r = r->next) {
if (ptr == r->ptr) {
if (NULL == prev) {
recs = r->next;
} else {
prev->next = r->next;
}
break;
}
prev = r;
}
pthread_mutex_unlock(&lock);
if (NULL == r) {
printf("Free at %s:%d (%p) not allocated or already freed.\n", file, line, ptr);
} else {
char *pad = (char *)r->ptr + r->size;
if (r->ruby != ruby) {
if (r->ruby) {
printf("Memory at %s:%d (%p) allocated with Ruby allocator and freed with stdlib free.\n",
file,
line,
ptr);
} else {
printf("Memory at %s:%d (%p) allocated with stdlib allocator and freed with Ruby free.\n",
file,
line,
ptr);
}
}
if (0 != strcmp(mem_pad, pad)) {
uint8_t *p;
uint8_t *end = (uint8_t *)pad + sizeof(mem_pad);
printf("Memory at %s:%d (%p) write outside allocated.\n", file, line, ptr);
for (p = (uint8_t *)pad; p < end; p++) {
if (0x20 < *p && *p < 0x7f) {
printf("%c ", *p);
} else {
printf("%02x ", *(uint8_t *)p);
}
}
printf("\n");
}
free(r);
}
}
}
void oj_r_free(void *ptr, const char *file, int line) {
oj_freed(ptr, file, line, true);
xfree(ptr);
}
void oj_free(void *ptr, const char *file, int line) {
oj_freed(ptr, file, line, false);
free(ptr);
}
char *oj_mem_strdup(const char *str, const char *file, int line) {
size_t size = strlen(str) + 1;
char *ptr = (char *)malloc(size + sizeof(mem_pad));
if (NULL != ptr) {
Rec r = (Rec)malloc(sizeof(struct _rec));
if (NULL != r) {
strcpy(ptr, str);
strcpy(((char *)ptr) + size, mem_pad);
r->ptr = (void *)ptr;
r->size = size;
r->file = file;
r->line = line;
r->ruby = false;
pthread_mutex_lock(&lock);
r->next = recs;
recs = r;
pthread_mutex_unlock(&lock);
} else {
free(ptr);
ptr = NULL;
}
}
return ptr;
}
#endif
#ifdef MEM_DEBUG
static Rep update_reps(Rep reps, Rec r) {
Rep rp = reps;
for (; NULL != rp; rp = rp->next) {
if (rp->line == r->line && (rp->file == r->file || 0 == strcmp(rp->file, r->file))) {
rp->size += r->size;
rp->cnt++;
break;
}
}
if (NULL == rp && NULL != (rp = (Rep)malloc(sizeof(struct _rep)))) {
rp->size = r->size;
rp->file = r->file;
rp->line = r->line;
rp->cnt = 1;
rp->next = reps;
reps = rp;
}
return reps;
}
static void print_stats() {
printf("\n--- Memory Usage Report --------------------------------------------------------\n");
pthread_mutex_lock(&lock);
if (NULL == recs) {
printf("No memory leaks\n");
} else {
Rep reps = NULL;
Rep rp;
Rec r;
size_t leaked = 0;
for (r = recs; NULL != r; r = r->next) {
reps = update_reps(reps, r);
}
while (NULL != (rp = reps)) {
reps = rp->next;
printf("%16s:%3d %8lu bytes over %d occurances allocated and not freed.\n",
rp->file,
rp->line,
rp->size,
rp->cnt);
leaked += rp->size;
free(rp);
}
printf("%lu bytes leaked\n", leaked);
}
pthread_mutex_unlock(&lock);
printf("--------------------------------------------------------------------------------\n");
}
#endif
void oj_mem_report(void) {
#ifdef MEM_DEBUG
rb_gc();
print_stats();
#endif
}
oj-3.16.3/ext/oj/rxclass.h 0000644 0000041 0000041 00000001345 14540127115 015307 0 ustar www-data www-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.16.3/ext/oj/extconf.rb 0000644 0000041 0000041 00000004042 14540127115 015447 0 ustar www-data www-data # frozen_string_literal: true
require 'mkmf'
require 'rbconfig'
extension_name = 'oj'
dir_config(extension_name)
parts = RUBY_DESCRIPTION.split(' ')
type = parts[0]
type = type[4..] 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,
}
# Support for compaction.
have_func('rb_gc_mark_movable')
have_func('stpcpy')
have_func('pthread_mutex_init')
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?
if with_config('--with-sse42')
if try_cflags('-msse4.2')
$CPPFLAGS += ' -msse4.2'
dflags['OJ_USE_SSE4_2'] = 1
else
warn 'SSE 4.2 is not supported on this platform.'
end
end
if enable_config('trace-log', false)
dflags['OJ_ENABLE_TRACE_LOG'] = 1
end
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.16.3/ext/oj/code.h 0000644 0000041 0000041 00000002107 14540127115 014537 0 ustar www-data www-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.16.3/ext/oj/fast.c 0000644 0000041 0000041 00000137630 14540127115 014567 0 ustar www-data www-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 "dump.h"
#include "encode.h"
#include "mem.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);
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';
b = oj_longlong_to_string((long long)num, false, 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 = OJ_R_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) {
OJ_R_FREE(b);
}
}
OJ_R_FREE(doc->json);
OJ_R_FREE(doc);
}
}
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) {
doc_free(doc);
}
}
static void mark_leaf(Leaf leaf) {
if (NULL != 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) {
struct _parseInfo pi;
volatile VALUE result = Qnil;
Doc doc;
int ex = 0;
volatile VALUE self;
doc = OJ_R_ALLOC_N(struct _doc, 1);
// 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 - (512L * 1024L));
#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
doc->json = json;
self = TypedData_Wrap_Struct(clas, &oj_doc_type, doc);
doc->self = self;
result = rb_protect(protect_open_proc, (VALUE)&pi, &ex);
if (given || 0 != ex) {
DATA_PTR(doc->self) = NULL;
// TBD is this needed?
/*
doc_free(pi.doc);
if (0 != ex) { // will jump so caller will not free
OJ_R_FREE(json);
}
*/
} 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();
Check_Type(str, T_STRING);
len = (int)RSTRING_LEN(str) + 1;
json = OJ_R_ALLOC_N(char, len);
memcpy(json, StringValuePtr(str), len);
obj = parse_json(clas, json, given);
// TBD is this needed
/*
if (given) {
OJ_R_FREE(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();
path = StringValuePtr(filename);
if (0 == (f = fopen(path, "r"))) {
rb_raise(rb_eIOError, "%s", strerror(errno));
}
fseek(f, 0, SEEK_END);
len = ftell(f);
json = OJ_R_ALLOC_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';
obj = parse_json(clas, json, given);
// TBD is this needed
/*
if (given) {
OJ_R_FREE(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) {
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;
case T_FIXNUM: type = rb_cInteger; break;
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) {
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);
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) {
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;
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;
Leaf *where_orig = doc->where;
wlen = doc->where - doc->where_path;
if (0 < wlen) {
memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
}
if (1 <= argc) {
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));
}
doc->where = where_orig;
return Qnil;
}
}
if (NULL == doc->where || NULL == *doc->where) {
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));
}
doc->where = where_orig;
}
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) {
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) {
path = StringValuePtr(*argv);
}
if (2 <= argc) {
filename = StringValuePtr(argv[1]);
}
}
if (0 != (leaf = get_doc_leaf(doc, path))) {
volatile VALUE rjson;
if (0 == filename) {
struct _out out;
oj_out_init(&out);
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);
oj_out_free(&out);
} 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) {
Doc d;
TypedData_Get_Struct(self, struct _doc, &oj_doc_type, d);
return ULONG2NUM(d->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) = NULL;
if (0 != doc) {
doc_free(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(void) {
oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject);
rb_gc_register_address(&oj_doc_class);
rb_undef_alloc_func(oj_doc_class);
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.16.3/ext/oj/parser.c 0000644 0000041 0000041 00000144344 14540127115 015126 0 ustar www-data www-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 which is the limit for double
#define MAX_POW 308
#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[309] = {
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};
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;
}
p->type = OJ_NONE;
}
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;
p->line = 1;
p->col = -1;
#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->cur = b - json;
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->cur = b - json;
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:
p->cur = b - json;
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->cur = b - json;
p->funcs[p->stack[p->depth]].close_object(p);
break;
case OPEN_ARRAY:
p->cur = b - json;
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:
p->cur = b - json;
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->cur = b - json;
p->funcs[p->stack[p->depth]].close_array(p);
break;
case NUM_COMMA:
p->cur = b - json;
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:
p->cur = b - json;
calc_num(p);
break;
case NUM_NEWLINE:
p->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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->cur = b - json;
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) {
parse_error(p, "parse error, not closed");
}
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':
p->cur = b - json;
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);
if (NULL != p->free) {
p->free(p);
}
OJ_R_FREE(ptr);
}
static void parser_mark(void *ptr) {
if (NULL != ptr) {
ojParser p = (ojParser)ptr;
if (0 != p->reader) {
rb_gc_mark(p->reader);
}
if (NULL != p->mark) {
p->mark(p);
}
}
}
static const rb_data_type_t oj_parser_type = {
"Oj/parser",
{
parser_mark,
parser_free,
NULL,
},
0,
0,
};
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 = StringValuePtr(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 = OJ_R_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 TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
}
// Create a new parser without setting the delegate. The parser is
// wrapped. The parser is (ojParser)DATA_PTR(value) where value is the return
// from this function. A delegate must be added before the parser can be
// used. Optionally oj_parser_set_options can be called if the options are not
// set directly.
VALUE oj_parser_new(void) {
ojParser p = OJ_R_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;
return TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
}
// Set set the options from a hash (ropts).
void oj_parser_set_option(ojParser p, VALUE ropts) {
Check_Type(ropts, T_HASH);
rb_hash_foreach(ropts, opt_cb, (VALUE)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_ is a flag indicating hash keys should be cached.
* - _cache_strings_ is a positive integer less than 35. Strings shorter than that length are cached.
* - _handler_ is the SAJ handler
*
* - *:usual*
* - _cache_keys_ is a flag indicating hash keys should be cached.
* - _cache_strings_ is a positive integer less than 35. Strings shorter than that length are cached.
* - _cache_expunge_ dictates when the cache will be expunged where 0 never expunges,
* 1 expunges slowly, 2 expunges faster, and 3 or higher expunges agressively.
* - _capacity_ is the capacity of the parser's internal stack. The parser grows automatically
* but can be updated directly with this call.
* - _create_id_ if non-nil is the key that is used to specify the type of object to create
* when parsing. 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_ is the approach to how decimals are parsed. 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.
* - _ignore_json_create_ is a flag that when set the class json_create method is
* ignored on parsing in favor of creating an instance and populating directly.
* - _missing_class_ is an indicator that determines how unknown class names are handled.
* 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_ is a flag that if true then null values in a map or object are omitted
* from the resulting Hash or Object.
* - _symbol_keys_ is a flag that indicates Hash keys should be parsed to Symbols versus Strings.
*/
static VALUE parser_missing(int argc, VALUE *argv, VALUE self) {
ojParser p;
const char *key = NULL;
volatile VALUE rkey = *argv;
volatile VALUE rv = Qnil;
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
#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 = StringValuePtr(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;
const byte *ptr = (const byte *)StringValuePtr(json);
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
parser_reset(p);
p->start(p);
parse(p, ptr);
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;
volatile VALUE rbuf = rb_str_new2("");
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
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;
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
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;
const char *path;
int fd;
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
path = StringValuePtr(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;
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
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;
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
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 = OJ_R_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 = TypedData_Wrap_Struct(parser_class, &oj_parser_type, 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 = OJ_R_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 = TypedData_Wrap_Struct(parser_class, &oj_parser_type, 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 = OJ_R_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 = TypedData_Wrap_Struct(parser_class, &oj_parser_type, 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(void) {
parser_class = rb_define_class_under(Oj, "Parser", rb_cObject);
rb_gc_register_address(&parser_class);
rb_undef_alloc_func(parser_class);
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.16.3/ext/oj/saj2.h 0000644 0000041 0000041 00000001141 14540127115 014461 0 ustar www-data www-data // Copyright (c) 2021, Peter Ohler, All rights reserved.
#include
#include
struct _cache;
struct _ojParser;
typedef struct _saj {
VALUE handler;
VALUE *keys;
VALUE *tail;
size_t klen;
struct _cache *str_cache;
uint8_t cache_str;
bool cache_keys;
bool thread_safe;
} *Saj;
// Initialize the parser with the SAJ delegate. If the SAJ delegate is wrapped
// then this function is called first and then the parser functions can be
// replaced.
extern void oj_init_saj(struct _ojParser *p, Saj d);
oj-3.16.3/ext/oj/dump.h 0000644 0000041 0000041 00000007013 14540127115 014573 0 ustar www-data www-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);
// initialize an out buffer with the provided stack allocated memory
extern void oj_out_init(Out out);
// clean up the out buffer if it uses heap allocated memory
extern void oj_out_free(Out out);
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 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';
memset(out->cur, ' ', cnt);
out->cur += cnt;
}
}
inline static bool 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;
}
inline static void dump_ulong(unsigned long num, Out out) {
char buf[32];
char *b = buf + sizeof(buf) - 1;
size_t cnt = 0;
*b-- = '\0';
if (0 < num) {
b = oj_longlong_to_string((long long)num, false, b);
} else {
*b = '0';
}
cnt = sizeof(buf) - (b - buf) - 1;
APPEND_CHARS(out->cur, b, cnt);
*out->cur = '\0';
}
#endif /* OJ_DUMP_H */
oj-3.16.3/ext/oj/cache8.c 0000644 0000041 0000041 00000004766 14540127115 014770 0 ustar www-data www-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 "mem.h"
#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 = OJ_R_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);
}
}
}
OJ_R_FREE(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.16.3/ext/oj/dump_compat.c 0000644 0000041 0000041 00000071640 14540127115 016140 0 ustar www-data www-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++ = '"';
APPEND_CHARS(out->cur, out->opts->create_id, out->opts->create_id_len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
}
*out->cur++ = '"';
APPEND_CHARS(out->cur, classname, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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;
TRACE(out->opts->trace, "to_json", obj, 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);
}
TRACE(out->opts->trace, "to_json", obj, 0, TraceRubyOut);
StringValue(rs);
s = RSTRING_PTR(rs);
len = (int)RSTRING_LEN(rs);
assure_size(out, len + 1);
APPEND_CHARS(out->cur, s, 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_array_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;
}
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
oj_dump_compat_val(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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 = oj_safe_string_convert(rb_funcall(obj, offset_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);
APPEND_CHARS(out->cur, "\"m\"", 3);
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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);
APPEND_CHARS(out->cur, "\"b\"", 3);
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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);
APPEND_CHARS(out->cur, "\"a\"", 3);
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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;
if (16 <= sizeof(struct timespec)) {
struct timespec ts = rb_time_timespec(obj);
sec = (long long)ts.tv_sec;
nsec = ts.tv_nsec;
} else {
sec = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
}
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");
cnt = 8;
} 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");
cnt = 9;
} 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");
cnt = 3;
} 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 = oj_safe_string_convert(obj);
strcpy(buf, RSTRING_PTR(rstr));
cnt = (int)RSTRING_LEN(rstr);
}
assure_size(out, cnt);
APPEND_CHARS(out->cur, buf, cnt);
*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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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(oj_safe_string_convert(key), 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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) {
APPEND_CHARS(out->cur, "{}", 2);
} 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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);
APPEND_CHARS(out->cur, "..", 2);
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);
APPEND_CHARS(out->cur, "\"v\"", 3);
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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 = oj_safe_string_convert(obj);
}
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);
}
APPEND_CHARS(out->cur, RSTRING_PTR(rs), 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) {
if (0 == rb_const_defined(rb_cObject, rb_intern("JSON"))) {
rb_require("oj/json");
}
{
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);
TRACE(out->opts->trace, "dump", obj, depth, TraceIn);
// The max_nesting logic is that an empty Array or Hash is assumed to have
// content so the max_nesting should fail but a non-collection value is
// okay. That means a check for a collectable value is needed before
// raising.
if (out->opts->dump_opts.max_depth <= depth) {
if (RUBY_T_ARRAY == type || RUBY_T_HASH == type) {
if (0 < out->argc) {
set_state_depth(*out->argv, depth);
}
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);
TRACE(out->opts->trace, "dump", obj, depth, TraceOut);
return;
}
}
oj_dump_nil(Qnil, depth, out, false);
TRACE(out->opts->trace, "dump", Qnil, depth, TraceOut);
}
oj-3.16.3/ext/oj/cache.c 0000644 0000041 0000041 00000020367 14540127115 014673 0 ustar www-data www-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"
#include "mem.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 = OJ_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;
OJ_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 = OJ_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;
OJ_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 = OJ_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 = OJ_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 = OJ_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(void *data) {
Cache c = (Cache)data;
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;
OJ_FREE(s);
}
}
OJ_FREE((void *)c->slots);
OJ_FREE(c);
}
void cache_mark(void *data) {
Cache c = (Cache)data;
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.16.3/ext/oj/buf.h 0000644 0000041 0000041 00000004112 14540127115 014377 0 ustar www-data www-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 "mem.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) {
OJ_R_FREE(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 (0 == slen) {
return;
}
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 = OJ_R_ALLOC_N(char, new_len);
memcpy(buf->head, buf->base, len);
} else {
OJ_R_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 = OJ_R_ALLOC_N(char, new_len);
memcpy(buf->head, buf->base, len);
} else {
OJ_R_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.16.3/ext/oj/parse.c 0000644 0000041 0000041 00000113033 14540127115 014733 0 ustar www-data www-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 "mem.h"
#include "oj.h"
#include "rxclass.h"
#include "val_stack.h"
#ifdef OJ_USE_SSE4_2
#include
#endif
// 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)) {
OJ_R_FREE((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");
}
}
static inline const char *scan_string_noSIMD(const char *str, const char *end) {
for (; '"' != *str; str++) {
if (end <= str || '\0' == *str || '\\' == *str) {
break;
}
}
return str;
}
#ifdef OJ_USE_SSE4_2
static inline const char *scan_string_SIMD(const char *str, const char *end) {
static const char chars[16] = "\x00\\\"";
const __m128i terminate = _mm_loadu_si128((const __m128i *)&chars[0]);
const char *_end = (const char *)(end - 16);
for (; str <= _end; str += 16) {
const __m128i string = _mm_loadu_si128((const __m128i *)str);
const int r = _mm_cmpestri(terminate,
3,
string,
16,
_SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT);
if (r != 16) {
str = (char *)(str + r);
return str;
}
}
return scan_string_noSIMD(str, end);
}
#endif
static const char *(*scan_func)(const char *str, const char *end) = scan_string_noSIMD;
void oj_scanner_init(void) {
#ifdef OJ_USE_SSE4_2
scan_func = scan_string_SIMD;
#endif
}
// 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);
buf_append_string(&buf, start, cnt);
for (s = pi->cur; '"' != *s;) {
const char *scanned = scan_func(s, pi->end);
if (scanned >= pi->end || '\0' == *scanned) {
// if (scanned >= pi->end) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated");
buf_cleanup(&buf);
return;
}
buf_append_string(&buf, s, (size_t)(scanned - s));
s = scanned;
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;
}
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 = OJ_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)) {
OJ_R_FREE((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);
pi->cur = scan_func(pi->cur, pi->end);
if (RB_UNLIKELY(pi->end <= pi->cur)) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated");
return;
}
if (RB_UNLIKELY('\0' == *pi->cur)) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "NULL byte in string");
return;
}
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)) {
OJ_R_FREE((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.pi = pi;
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;
// Skip leading zeros.
for (; '0' == *pi->cur; pi->cur++) {
zero1 = true;
}
for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) {
int d = (*pi->cur - '0');
if (RB_LIKELY(0 != ni.i)) {
dec_cnt++;
}
ni.i = ni.i * 10 + d;
}
if (RB_UNLIKELY(0 != ni.i && zero1 && CompatMode == pi->options.mode)) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number");
return;
}
if (INT64_MAX <= ni.i || DEC_MAX < dec_cnt) {
ni.big = true;
}
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 (RB_LIKELY(0 != ni.num || 0 != ni.i)) {
dec_cnt++;
}
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)) {
OJ_R_FREE((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) {
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) {
VALUE v = pi->start_hash(pi);
stack_push(&pi->stack, v, NEXT_HASH_NEW);
}
static void hash_end(ParseInfo pi) {
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 '+':
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) {
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 = OJ_R_ALLOC_N(char, ni->len + 1);
memcpy(buf, ni->str, ni->len);
buf[ni->len] = '\0';
rnum = rb_cstr_to_inum(buf, 10, 0);
OJ_R_FREE(buf);
}
} else {
if (ni->neg) {
rnum = rb_ll2inum(-ni->i);
} else {
rnum = rb_ll2inum(ni->i);
}
}
} else { // decimal
if (ni->big) {
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) {
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)) {
if (Qnil == ni->pi->err_class) {
rb_raise(oj_parse_error_class, "Invalid float");
} else {
rb_raise(ni->pi->err_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, VALUE *inputp) {
int idx = RB_ENCODING_GET(*inputp);
if (oj_utf8_encoding_index != idx) {
rb_encoding *enc = rb_enc_from_index(idx);
*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;
VALUE input;
VALUE wrapped_stack;
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);
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 = OJ_R_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) {
OJ_R_FREE(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) {
OJ_R_FREE(buf);
} else if (free_json) {
OJ_R_FREE(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.16.3/ext/oj/compat.c 0000644 0000041 0000041 00000020463 14540127115 015110 0 ustar www-data www-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 "mem.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);
if (Qundef == kval->key_val && 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);
volatile VALUE rkey = oj_calc_hash_key(pi, kval);
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);
}
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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();
}
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
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) {
OJ_R_FREE((char *)parent->classname);
parent->classname = 0;
}
}
TRACE_PARSE_HASH_END(pi->options.trace, pi);
}
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;
TRACE_PARSE_CALL(pi->options.trace, "add_string", pi, rstr);
}
static void add_num(ParseInfo pi, NumInfo ni) {
pi->stack.head->val = oj_num_as_value(ni);
TRACE_PARSE_CALL(pi->options.trace, "add_number", pi, 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 (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);
}
TRACE_PARSE_CALL(pi->options.trace, "set_number", pi, 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);
}
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, value);
}
static VALUE start_array(ParseInfo pi) {
if (Qnil != pi->options.array_class) {
return rb_class_new_instance(0, NULL, pi->options.array_class);
}
TRACE_PARSE_IN(pi->options.trace, "start_array", pi);
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);
}
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, 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.16.3/ext/oj/custom.c 0000644 0000041 0000041 00000107040 14540127115 015134 0 ustar www-data www-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 "mem.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 = oj_safe_string_convert(obj);
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 = oj_safe_string_convert(obj);
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 = oj_safe_string_convert(obj);
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 (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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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(oj_safe_string_convert(key), 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, 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) {
APPEND_CHARS(out->cur, "{}", 2);
} 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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.hash_nl, out->opts->dump_opts.hash_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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++ = '"';
APPEND_CHARS(out->cur, out->opts->create_id, out->opts->create_id_len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
}
*out->cur++ = '"';
APPEND_CHARS(out->cur, classname, clen);
APPEND_CHARS(out->cur, "\",", 2);
}
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++ = '"';
APPEND_CHARS(out->cur, name, nlen);
APPEND_CHARS(out->cur, "\":", 2);
APPEND_CHARS(out->cur, s, 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 = OJ_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) {
OJ_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;
TRACE(out->opts->trace, "to_json", obj, 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);
}
TRACE(out->opts->trace, "to_json", obj, depth + 1, TraceRubyOut);
s = RSTRING_PTR(rs);
len = (int)RSTRING_LEN(rs);
assure_size(out, len + 1);
APPEND_CHARS(out->cur, s, len);
*out->cur = '\0';
} else if (Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) {
volatile VALUE aj;
TRACE(out->opts->trace, "as_json", obj, 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);
}
TRACE(out->opts->trace, "as_json", obj, depth + 1, TraceRubyOut);
// Catch the obvious brain damaged recursive dumping.
if (aj == obj) {
volatile VALUE rstr = oj_safe_string_convert(obj);
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 (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++ = '"';
APPEND_CHARS(out->cur, out->opts->create_id, out->opts->create_id_len);
*out->cur++ = '"';
if (0 < out->opts->dump_opts.before_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.before_sep, out->opts->dump_opts.before_size);
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts.after_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
}
*out->cur++ = '"';
APPEND_CHARS(out->cur, classname, 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;
}
assure_size(out, size * cnt);
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->opts->dump_opts.use) {
if (0 < out->opts->dump_opts.array_size) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = d2; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
}
}
} else {
fill_indent(out, d2);
}
oj_dump_custom_val(RARRAY_AREF(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) {
APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
}
if (0 < out->opts->dump_opts.indent_size) {
int i;
for (i = depth; 0 < i; i--) {
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, 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);
APPEND_CHARS(out->cur, "..", 2);
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(RARRAY_AREF(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++ = '"';
APPEND_CHARS(out->cur, name, len);
APPEND_CHARS(out->cur, "\":", 2);
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);
TRACE(out->opts->trace, "dump", obj, 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);
TRACE(out->opts->trace, "dump", obj, depth, TraceOut);
return;
}
}
oj_dump_nil(Qnil, depth, out, false);
TRACE(out->opts->trace, "dump", Qnil, 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);
if (Qundef == kval->key_val && 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 rkey = oj_calc_hash_key(pi, kval);
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;
}
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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;
}
TRACE_PARSE_HASH_END(pi->options.trace, pi);
}
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) {
struct timespec ts;
ts.tv_sec = ni->i;
ts.tv_nsec = nsec;
parent->val = rb_time_timespec_new(&ts, (int)ni->exp);
} 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;
}
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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;
}
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, 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.16.3/ext/oj/mem.h 0000644 0000041 0000041 00000003477 14540127115 014416 0 ustar www-data www-data // Copyright (c) 2018, Peter Ohler, All rights reserved.
#ifndef OJ_MEM_H
#define OJ_MEM_H
#include
#include
#include
#ifdef MEM_DEBUG
#define OJ_MALLOC(size) oj_malloc(size, __FILE__, __LINE__)
#define OJ_REALLOC(ptr, size) oj_realloc(ptr, size, __FILE__, __LINE__)
#define OJ_CALLOC(count, size) oj_calloc(count, size, __FILE__, __LINE__)
#define OJ_FREE(ptr) oj_free(ptr, __FILE__, __LINE__)
#define OJ_R_ALLOC(type) oj_r_alloc(sizeof(type), __FILE__, __LINE__)
#define OJ_R_ALLOC_N(type, n) (type *)oj_r_alloc(sizeof(type) * (n), __FILE__, __LINE__)
#define OJ_R_REALLOC_N(ptr, type, n) ((ptr) = (type *)oj_r_realloc(ptr, (sizeof(type) * (n)), __FILE__, __LINE__))
#define OJ_R_FREE(ptr) oj_r_free(ptr, __FILE__, __LINE__)
#define OJ_STRDUP(str) oj_mem_strdup(str, __FILE__, __LINE__)
extern void *oj_malloc(size_t size, const char *file, int line);
extern void *oj_realloc(void *ptr, size_t size, const char *file, int line);
extern void *oj_calloc(size_t count, size_t size, const char *file, int line);
extern void oj_free(void *ptr, const char *file, int line);
extern void *oj_r_alloc(size_t size, const char *file, int line);
extern void *oj_r_realloc(void *ptr, size_t size, const char *file, int line);
extern void oj_r_free(void *ptr, const char *file, int line);
extern char *oj_mem_strdup(const char *str, const char *file, int line);
#else
#define OJ_MALLOC(size) malloc(size)
#define OJ_REALLOC(ptr, size) realloc(ptr, size)
#define OJ_CALLOC(count, size) calloc(count, size)
#define OJ_FREE(ptr) free(ptr)
#define OJ_R_ALLOC(type) RB_ALLOC(type)
#define OJ_R_ALLOC_N(type, n) RB_ALLOC_N(type, n)
#define OJ_R_REALLOC_N(ptr, type, n) RB_REALLOC_N(ptr, type, n)
#define OJ_R_FREE(ptr) xfree(ptr)
#define OJ_STRDUP(str) strdup(str)
#endif
extern void oj_mem_report();
#endif /* OJ_MEM_H */
oj-3.16.3/ext/oj/encoder.c 0000644 0000041 0000041 00000003636 14540127115 015247 0 ustar www-data www-data // Copyright (c) 2011, 2022 Peter Ohler. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for license details.
#include "oj.h"
typedef struct _encoder {
int indent; // indention for dump, default 2
char circular; // YesNo
char escape_mode; // Escape_Mode
char mode; // Mode
char time_format; // TimeFormat
char bigdec_as_num; // YesNo
char to_hash; // YesNo
char to_json; // YesNo
char as_json; // YesNo
char raw_json; // YesNo
char trace; // YesNo
char sec_prec_set; // boolean (0 or 1)
char ignore_under; // YesNo - ignore attrs starting with _ if true in object and custom modes
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
struct _dumpOpts dump_opts;
struct _rxClass str_rx;
VALUE* ignore; // Qnil terminated array of classes or NULL
}* Encoder;
/*
rb_define_module_function(Oj, "encode", encode, -1);
rb_define_module_function(Oj, "to_file", to_file, -1); // or maybe just write
rb_define_module_function(Oj, "to_stream", to_stream, -1);
*/
// write(to, obj)
// if to is a string then open file
// else if stream then write to stream
// handle non-blocking
// should each mode have a different encoder or use delegates like the parser?
oj-3.16.3/ext/oj/val_stack.h 0000644 0000041 0000041 00000007433 14540127115 015603 0 ustar www-data www-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 "mem.h"
#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) {
OJ_R_FREE(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 = OJ_R_ALLOC_N(struct _val, len + STACK_INC);
memcpy(head, stack->base, sizeof(struct _val) * len);
} else {
OJ_R_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.16.3/ext/oj/odd.h 0000644 0000041 0000041 00000002422 14540127115 014373 0 ustar www-data www-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 {
struct _odd *next;
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.16.3/ext/oj/object.c 0000644 0000041 0000041 00000057621 14540127115 015101 0 ustar www-data www-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[7];
const char *end = str + len;
const char *orig = str;
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) {
unsigned long long num = 0;
unsigned long long den = 1;
const unsigned long long last_den_limit = ULLONG_MAX / 10;
for (; str < end; str++) {
c = *str;
if (c < '0' || '9' < c) {
str++;
break;
}
if (den > last_den_limit) {
// bail to Time.parse if there are more fractional digits than a ULLONG rational can hold
return rb_funcall(rb_cTime, oj_parse_id, 1, rb_str_new(orig, len));
}
num = num * 10 + (c - '0');
den *= 10;
}
args[5] = rb_funcall(INT2NUM(n), oj_plus_id, 1, rb_rational_new(ULL2NUM(num), ULL2NUM(den)));
} 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) {
struct timespec ts;
ts.tv_sec = ni->i;
ts.tv_nsec = nsec;
parent->val = rb_time_timespec_new(&ts, (int)ni->exp);
} 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_CONST_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 = RARRAY_AREF(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_CONST_PTR(value), rb_eArgError);
}
if (sc == rb_cRange) {
parent->val = rb_class_new_instance(len - 1, RARRAY_CONST_PTR(value) + 1, rb_cRange);
} else {
// 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_CONST_PTR(value)[i + 1]);
}
}
}
return 1;
} else if (3 <= klen && '#' == key[1]) {
volatile const 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_CONST_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) {
if (kval->klen == 5 && strncmp("~mesg", kval->key, 5) == 0 && rb_obj_is_kind_of(parent->val, rb_eException)) {
parent->val = rb_funcall(parent->val, rb_intern("exception"), 1, value);
} else if (kval->klen == 3 && strncmp("~bt", kval->key, 3) == 0 && rb_obj_is_kind_of(parent->val, rb_eException)) {
rb_funcall(parent->val, rb_intern("set_backtrace"), 1, value);
} else {
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;
}
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, 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;
}
TRACE_PARSE_CALL(pi->options.trace, "add_number", pi, 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 const VALUE *a = RARRAY_CONST_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;
}
TRACE_PARSE_CALL(pi->options.trace, "add_value", pi, value);
}
static VALUE start_hash(ParseInfo pi) {
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
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;
}
TRACE_PARSE_HASH_END(pi->options.trace, pi);
}
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);
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, 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);
TRACE_PARSE_CALL(pi->options.trace, "add_string", pi, pi->stack.head->val);
}
static void add_num(ParseInfo pi, NumInfo ni) {
pi->stack.head->val = oj_num_as_value(ni);
TRACE_PARSE_CALL(pi->options.trace, "add_num", pi, 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.16.3/ext/oj/oj.c 0000644 0000041 0000041 00000247451 14540127115 014245 0 ustar www-data www-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 "mem.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_json_create_id;
ID oj_length_id;
ID oj_new_id;
ID oj_parse_id;
ID oj_plus_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_format_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_null_byte_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 slash_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;
int oj_utf8_encoding_index = 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
false, // omit_null_byte
MAX_DEPTH, // max_depth
},
{
// str_rx
NULL, // head
NULL, // tail
{'\0'}, // err
},
NULL,
};
/* 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_|_:slash_|_: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_|_:ruby_] 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
* - *:float_format* [_String_] the C printf format string for printing floats.
* Default follows the float_precision and will be changed if float_precision is
* changed. The string can be no more than 6 bytes.
* - *: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
* - *:omit_null_byte* [_true_|_false_] if true null bytes in strings will be
* omitted when dumping
* - *: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, float_format_sym, rb_str_new_cstr(oj_default_options.float_fmt));
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 SlashEsc: rb_hash_aset(opts, escape_mode_sym, slash_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, omit_null_byte_sym, oj_default_options.dump_opts.omit_null_byte ? 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_format* [_String_] the C printf format string for printing floats.
* Default follows the float_precision and will be changed if float_precision
* is changed. The string can be no more than 6 bytes.
* - *: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;
if (rb_cInteger != rb_obj_class(v)) {
rb_raise(rb_eArgError, ":float_precision must be a Integer.");
}
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;
if (rb_cInteger != rb_obj_class(v)) {
rb_raise(rb_eArgError, ":cache_str must be a Integer.");
}
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;
if (rb_cInteger != rb_obj_class(v)) {
rb_raise(rb_eArgError, ":second_precision must be a Integer.");
}
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 (slash_sym == v) {
copts->escape_mode = SlashEsc;
} 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) {
OJ_R_FREE((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 = OJ_R_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 (omit_null_byte_sym == k) {
if (Qnil == v) {
return ST_CONTINUE;
}
if (Qtrue == v) {
copts->dump_opts.omit_null_byte = true;
} else if (Qfalse == v) {
copts->dump_opts.omit_null_byte = false;
} else {
rb_raise(rb_eArgError, ":omit_null_byte 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) {
OJ_R_FREE(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 = OJ_R_ALLOC_N(VALUE, cnt + 1);
for (i = 0; i < cnt; i++) {
copts->ignore[i] = RARRAY_AREF(v, i);
}
copts->ignore[i] = Qnil;
}
}
} else if (integer_range_sym == k) {
if (Qnil == v) {
return ST_CONTINUE;
}
if (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;
} else if (oj_max_nesting_sym == k) {
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;
}
}
} else if (float_format_sym == k) {
rb_check_type(v, T_STRING);
if (6 < (int)RSTRING_LEN(v)) {
rb_raise(rb_eArgError, ":float_format must be 6 bytes or less.");
}
strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
copts->float_fmt[RSTRING_LEN(v)] = '\0';
}
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().");
}
path = StringValuePtr(*argv);
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.");
}
}
}
#ifdef _WIN32
{
WCHAR *wide_path;
wide_path = rb_w32_mbstr_to_wstr(CP_UTF8, path, -1, NULL);
fd = rb_w32_wopen(wide_path, O_RDONLY);
OJ_FREE(wide_path);
}
#else
fd = open(path, O_RDONLY);
#endif
if (0 == fd) {
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;
oj_out_free(arg->out);
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) {
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;
oj_out_init(arg.out);
arg.out->omit_nil = copts.dump_opts.omit_nil;
arg.out->omit_null_byte = copts.dump_opts.omit_null_byte;
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* [_Fixnum_|_boolean_] It true nesting is limited to 100.
* If a Fixnum nesting is set to the provided value. The option to detect
* circular references is available but is not compatible with the json gem.,
* default is false or unlimited.
* - *: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) {
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;
oj_out_init(&out);
out.omit_nil = copts.dump_opts.omit_nil;
out.omit_null_byte = copts.dump_opts.omit_null_byte;
// 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);
oj_out_free(&out);
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);
}
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;
}
extern void print_all_odds(const char *label);
static VALUE debug_odd(VALUE self, VALUE label) {
print_all_odds(RSTRING_PTR(label));
return Qnil;
}
static VALUE mem_report(VALUE self) {
oj_mem_report();
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(void) {
int err = 0;
#if HAVE_RB_EXT_RACTOR_SAFE
rb_ext_ractor_safe(true);
#endif
Oj = rb_define_module("Oj");
rb_gc_register_address(&Oj);
oj_cstack_class = rb_define_class_under(Oj, "CStack", rb_cObject);
rb_gc_register_address(&oj_cstack_class);
rb_undef_alloc_func(oj_cstack_class);
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_index = rb_enc_find_index("UTF-8");
oj_utf8_encoding = rb_enc_from_index(oj_utf8_encoding_index);
// rb_define_module_function(Oj, "hash_test", hash_test, 0);
rb_define_module_function(Oj, "debug_odd", debug_odd, 1);
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);
rb_define_module_function(Oj, "mem_report", mem_report, 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_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_plus_id = rb_intern("+");
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_format_sym = ID2SYM(rb_intern("float_format"));
rb_gc_register_address(&float_format_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);
omit_null_byte_sym = ID2SYM(rb_intern("omit_null_byte"));
rb_gc_register_address(&omit_null_byte_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);
slash_sym = ID2SYM(rb_intern("slash"));
rb_gc_register_address(&slash_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_scanner_init();
}
oj-3.16.3/ext/oj/odd.c 0000644 0000041 0000041 00000015160 14540127115 014371 0 ustar www-data www-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
#include "mem.h"
static Odd odds = NULL;
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 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));
rb_gc_register_mark_object(odd->clas);
odd->create_obj = odd->clas;
rb_gc_register_mark_object(odd->create_obj);
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 = NUM2LL(rb_funcall(rfrac, numerator_id, 0));
long long den = NUM2LL(rb_funcall(rfrac, denominator_id, 0));
num += sec * den;
return rb_funcall(rb_cObject, rational_id, 2, rb_ll2inum(num), rb_ll2inum(den));
}
static void print_odd(Odd odd) {
const char **np;
int i;
printf(" %s {\n", odd->classname);
printf(" attr_cnt: %d %p\n", odd->attr_cnt, (void *)odd->attr_names);
printf(" attr_names: %p\n", (void *)*odd->attr_names);
printf(" attr_names: %c\n", **odd->attr_names);
for (i = odd->attr_cnt, np = odd->attr_names; 0 < i; i--, np++) {
printf(" %d %s\n", i, *np);
}
printf(" }\n");
}
void print_all_odds(const char *label) {
Odd odd;
printf("@ %s {\n", label);
for (odd = odds; NULL != odd; odd = odd->next) {
print_odd(odd);
}
printf("}\n");
}
static Odd odd_create(void) {
Odd odd = OJ_R_ALLOC(struct _odd);
memset(odd, 0, sizeof(struct _odd));
odd->next = odds;
odds = odd;
return odd;
}
void oj_odd_init(void) {
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
odd = odd_create();
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 = odd_create();
np = odd->attr_names;
*np++ = "year";
*np++ = "month";
*np++ = "day";
*np++ = "start";
*np++ = 0;
set_class(odd, "Date");
odd->attr_cnt = 4;
// DateTime
odd = odd_create();
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 = odd_create();
np = odd->attr_names;
*np++ = "begin";
*np++ = "end";
*np++ = "exclude_end?";
*np++ = 0;
set_class(odd, "Range");
odd->attr_cnt = 3;
}
Odd oj_get_odd(VALUE clas) {
Odd odd;
const char *classname = NULL;
for (odd = odds; NULL != odd; odd = odd->next) {
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; NULL != odd; odd = odd->next) {
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 NULL;
}
OddArgs oj_odd_alloc_args(Odd odd) {
OddArgs oa = OJ_R_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) {
OJ_R_FREE(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;
odd = odd_create();
odd->clas = clas;
rb_gc_register_mark_object(odd->clas);
if (NULL == (odd->classname = OJ_STRDUP(rb_class2name(clas)))) {
rb_raise(rb_eNoMemError, "for class name.");
}
odd->clen = strlen(odd->classname);
odd->create_obj = create_object;
rb_gc_register_mark_object(odd->create_obj);
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 = OJ_STRDUP(RSTRING_PTR(*members)))) {
rb_raise(rb_eNoMemError, "for attribute name.");
}
break;
case T_SYMBOL:
// The symbol can move and invalidate the name so make a copy.
if (NULL == (*np = OJ_STRDUP(rb_id2name(SYM2ID(*members))))) {
rb_raise(rb_eNoMemError, "for attribute name.");
}
break;
default: rb_raise(rb_eArgError, "registered member identifiers must be Strings or Symbols."); break;
}
*ap = rb_intern(*np);
}
*np = 0;
*ap = 0;
}
oj-3.16.3/ext/oj/resolve.c 0000644 0000041 0000041 00000004435 14540127115 015305 0 ustar www-data www-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.16.3/ext/oj/saj.c 0000644 0000041 0000041 00000050300 14540127115 014373 0 ustar www-data www-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 "mem.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 - (512L * 1024L)); /* 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 = OJ_R_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 = OJ_R_ALLOC_N(char, len);
strcpy(json, StringValueCStr(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 = OJ_R_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 = OJ_R_ALLOC_N(char, len);
strcpy(json, StringValueCStr(s));
} else {
rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object.");
}
}
saj_parse(*argv, json);
OJ_R_FREE(json);
return Qnil;
}
oj-3.16.3/ext/oj/string_writer.c 0000644 0000041 0000041 00000037376 14540127115 016542 0 ustar www-data www-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 "mem.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;
OJ_R_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 = OJ_R_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;
}
// Must be allocated. Using the out.stack_buffer results in double frees
// and I haven't figured out why yet.
sw->out.buf = OJ_R_ALLOC_N(char, buf_size);
sw->out.cur = sw->out.buf;
sw->out.end = sw->out.buf + buf_size - BUFFER_EXTRA;
sw->out.allocated = true;
*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.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 string_writer_free(void *ptr) {
StrWriter sw;
if (0 == ptr) {
return;
}
sw = (StrWriter)ptr;
oj_out_free(&sw->out);
OJ_R_FREE(sw->types);
OJ_R_FREE(ptr);
}
static const rb_data_type_t oj_string_writer_type = {
"Oj/string_writer",
{
NULL,
string_writer_free,
NULL,
},
0,
0,
};
StrWriter oj_str_writer_unwrap(VALUE writer) {
StrWriter sw;
TypedData_Get_Struct(writer, struct _strWriter, &oj_string_writer_type, sw);
return sw;
}
/* 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 = OJ_R_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 TypedData_Wrap_Struct(oj_string_writer_class, &oj_string_writer_type, 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;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
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;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
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 {
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;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
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 {
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) {
StrWriter sw;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
switch (argc) {
case 1: oj_str_writer_push_value(sw, *argv, 0); break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_value(sw, *argv, 0);
} else {
oj_str_writer_push_value(sw, *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) {
StrWriter sw;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
switch (argc) {
case 1: oj_str_writer_push_json(sw, StringValuePtr(*argv), 0); break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_json(sw, StringValuePtr(*argv), 0);
} else {
oj_str_writer_push_json(sw, 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) {
StrWriter sw;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
oj_str_writer_pop(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 str_writer_pop_all(VALUE self) {
StrWriter sw;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
oj_str_writer_pop_all(sw);
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;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
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;
TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
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 strict 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(void) {
oj_string_writer_class = rb_define_class_under(Oj, "StringWriter", rb_cObject);
rb_gc_register_address(&oj_string_writer_class);
rb_undef_alloc_func(oj_string_writer_class);
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.16.3/ext/oj/debug.c 0000644 0000041 0000041 00000010116 14540127115 014705 0 ustar www-data www-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.16.3/ext/oj/usual.h 0000644 0000041 0000041 00000003110 14540127115 014751 0 ustar www-data www-data // Copyright (c) 2022, Peter Ohler, All rights reserved.
#include
#include
#include
struct _cache;
struct _ojParser;
// 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 _usual {
VALUE *vhead;
VALUE *vtail;
VALUE *vend;
Col chead;
Col ctail;
Col cend;
Key khead;
Key ktail;
Key kend;
VALUE (*get_key)(struct _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;
bool raise_on_empty;
} *Usual;
// Initialize the parser with the usual delegate. If the usual delegate is
// wrapped then this function is called first and then the parser functions
// can be replaced.
extern void oj_init_usual(struct _ojParser *p, Usual d);
oj-3.16.3/ext/oj/parse.h 0000644 0000041 0000041 00000007102 14540127115 014737 0 ustar www-data www-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;
struct _parseInfo;
typedef struct _numInfo {
int64_t i;
int64_t num;
int64_t div;
int64_t di;
const char *str;
size_t len;
long exp;
struct _parseInfo *pi;
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));
}
extern void oj_scanner_init(void);
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.16.3/ext/oj/mimic_json.c 0000644 0000041 0000041 00000101341 14540127115 015747 0 ustar www-data www-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 "mem.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) {
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;
oj_out_init(&out);
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;
}
oj_out_free(&out);
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, RARRAY_AREF(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)");
}
if (T_STRING == rb_type(*argv)) {
return mimic_load(argc, argv, self);
}
return mimic_dump(argc, argv, self);
}
static VALUE mimic_generate_core(int argc, VALUE *argv, Options copts) {
struct _out out;
VALUE rstr;
if (0 == argc) {
rb_raise(rb_eArgError, "wrong number of arguments (0))");
}
memset(out.stack_buffer, 0, sizeof(out.stack_buffer));
oj_out_init(&out);
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.
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) {
rb_warn("Oj::Rails.mimic_JSON was called implicitly. "
"Call it explicitly beforehand if you want to remove this warning.");
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);
oj_out_free(&out);
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 (0 == argc) {
rb_raise(rb_eArgError, "wrong number of arguments (0))");
}
if (1 == argc || Qnil == argv[1]) {
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) {
rb_warn("Oj::Rails.mimic_JSON was called implicitly. "
"Call it explicitly beforehand if you want to remove this warning.");
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 int parse_options_cb(VALUE k, VALUE v, VALUE info) {
struct _parseInfo *pi = (struct _parseInfo *)info;
if (oj_symbolize_names_sym == k) {
pi->options.sym_key = (Qtrue == v) ? Yes : No;
} else if (oj_quirks_mode_sym == k) {
pi->options.quirks_mode = (Qtrue == v) ? Yes : No;
} else if (oj_create_additions_sym == k) {
pi->options.create_ok = (Qtrue == v) ? Yes : No;
} else if (oj_allow_nan_sym == k) {
pi->options.allow_nan = (Qtrue == v) ? Yes : No;
} else if (oj_hash_class_sym == k) {
if (Qnil == v) {
pi->options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.hash_class = v;
}
} else if (oj_object_class_sym == k) {
if (Qnil == v) {
pi->options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.hash_class = v;
}
} else if (oj_array_class_sym == k) {
if (Qnil == v) {
pi->options.array_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.array_class = v;
}
} else if (oj_decimal_class_sym == k) {
pi->options.compat_bigdec = (oj_bigdecimal_class == v);
}
return ST_CONTINUE;
}
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.");
}
rb_hash_foreach(ropts, parse_options_cb, (VALUE)&pi);
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 [_nil_|_String_] the id.
*/
static VALUE mimic_set_create_id(VALUE self, VALUE id) {
if (NULL != oj_default_options.create_id) {
if (oj_json_class != oj_default_options.create_id) {
OJ_R_FREE((char *)oj_default_options.create_id);
}
oj_default_options.create_id = NULL;
oj_default_options.create_id_len = 0;
}
if (Qnil != id) {
const char *ptr = StringValueCStr(id);
size_t len = RSTRING_LEN(id) + 1;
oj_default_options.create_id = OJ_R_ALLOC_N(char, len);
strcpy((char *)oj_default_options.create_id, ptr);
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
false, // omit_null_byte
100, // max_depth
},
{
// str_rx
NULL, // head
NULL, // tail
{'\0'}, // err
}};
static VALUE mimic_object_to_json(int argc, VALUE *argv, VALUE self) {
struct _out out;
VALUE rstr;
struct _options copts = oj_default_options;
copts.str_rx.head = NULL;
copts.str_rx.tail = NULL;
oj_out_init(&out);
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);
oj_out_free(&out);
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;
VALUE verbose;
// 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, "create_id=");
rb_define_module_function(json, "create_id=", mimic_set_create_id, 1);
rb_undef_method(json, "create_id");
rb_define_module_function(json, "create_id", mimic_create_id, 0);
rb_undef_method(json, "dump");
rb_define_module_function(json, "dump", mimic_dump, -1);
rb_undef_method(json, "load");
rb_define_module_function(json, "load", mimic_load, -1);
rb_define_module_function(json, "restore", mimic_load, -1);
rb_undef_method(json, "recurse_proc");
rb_define_module_function(json, "recurse_proc", mimic_recurse_proc, 1);
rb_undef_method(json, "[]");
rb_define_module_function(json, "[]", mimic_dump_load, -1);
rb_undef_method(json, "generate");
rb_define_module_function(json, "generate", oj_mimic_generate, -1);
rb_undef_method(json, "fast_generate");
rb_define_module_function(json, "fast_generate", oj_mimic_generate, -1);
rb_undef_method(json, "pretty_generate");
rb_define_module_function(json, "pretty_generate", oj_mimic_pretty_generate, -1);
// For older versions of JSON, the deprecated unparse methods.
rb_undef_method(json, "unparse");
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_undef_method(json, "parse");
rb_define_module_function(json, "parse", oj_mimic_parse, -1);
rb_undef_method(json, "parse!");
rb_define_module_function(json, "parse!", mimic_parse_bang, -1);
rb_undef_method(json, "state");
rb_define_module_function(json, "state", mimic_state, 0);
rb_gv_set("$VERBOSE", verbose);
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.16.3/ext/oj/reader.h 0000644 0000041 0000041 00000006260 14540127115 015073 0 ustar www-data www-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
#include "mem.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) {
OJ_R_FREE((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.16.3/ext/oj/intern.h 0000644 0000041 0000041 00000001236 14540127115 015126 0 ustar www-data www-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(void);
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.16.3/ext/oj/rails.h 0000644 0000041 0000041 00000000726 14540127115 014744 0 ustar www-data www-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(void);
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.16.3/ext/oj/circarray.h 0000644 0000041 0000041 00000001246 14540127115 015607 0 ustar www-data www-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 */