oj-2.5.3/ 0000755 0000041 0000041 00000000000 12263716750 012172 5 ustar www-data www-data oj-2.5.3/lib/ 0000755 0000041 0000041 00000000000 12263716750 012740 5 ustar www-data www-data oj-2.5.3/lib/oj.rb 0000644 0000041 0000041 00000002536 12263716750 013703 0 ustar www-data www-data # Optimized JSON (Oj), as the name implies was written to provide speed
# optimized JSON handling.
#
# Oj has several dump or serialization modes which control how Objects 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.
#
# - :strict mode will only allow the 7 basic JSON types to be serialized. Any other Object
# will raise and Exception.
#
# - :null mode replaces any Object that is not one of the JSON 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 mode is is the compatible with other systems. It will serialize
# any Object but will check to see if the Object implements a to_hash() or
# to_json() method. If either exists that method is used for serializing the
# Object. The to_hash() is more flexible and produces more consistent output
# so it has a preference over the to_json() method. If neither the to_json()
# or to_hash() methods exist then the Oj internal Object variable encoding
# is used.
module Oj
end
require 'oj/version'
require 'oj/bag'
require 'oj/error'
require 'oj/mimic'
require 'oj/saj'
require 'oj/schandler'
require 'oj/oj' # C extension
oj-2.5.3/lib/oj/ 0000755 0000041 0000041 00000000000 12263716750 013350 5 ustar www-data www-data oj-2.5.3/lib/oj/schandler.rb 0000644 0000041 0000041 00000003135 12263716750 015642 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 initialize()
# @hash_cnt = 0
# end
#
# def start_hash()
# @hash_cnt += 1
# end
# end
#
# cnt = MyHandler.new()
# File.open('any.json', 'r') do |f|
# Oj.sc_parse(cnt, f)
# end
#
# To make the desired methods active while parsing the desired method should
# be made public in the subclasses. If the methods remain private they will
# not be called during parsing.
#
# def hash_start(); end
# def hash_end(); end
# def array_start(); end
# def array_end(); end
# def add_value(value); end
# def error(message, line, column); end
#
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 array_start()
end
def array_end()
end
def add_value(value)
end
def hash_set(h, key, value)
end
def array_append(a, value)
end
end # ScHandler
end # Oj
oj-2.5.3/lib/oj/saj.rb 0000644 0000041 0000041 00000002771 12263716750 014461 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 should be subclassed and then used with
# the Oj.sajkey_parse() method. The Saj methods will then be called as the
# file is parsed.
#
# @example
#
# require 'oj'
#
# class MySaj < ::Oj::Saj
# def initialize()
# @hash_cnt = 0
# end
#
# def start_hash(key)
# @hash_cnt += 1
# end
# end
#
# cnt = MySaj.new()
# File.open('any.xml', 'r') do |f|
# Oj.saj_parse(cnt, f)
# end
#
# To make the desired methods active while parsing the desired method should
# be made public in the subclasses. If the methods remain private they will
# not be called during parsing.
#
# def hash_start(key); end
# def hash_end(key); end
# def array_start(key); end
# def array_end(key); end
# def add_value(value, key); end
# def error(message, line, column); end
#
class Saj
# Create a new instance of the Saj handler class.
def initialize()
end
# To make the desired methods active while parsing the desired method should
# be made public in the subclasses. If the methods remain private they will
# not be called during parsing.
private
def hash_start(key)
end
def hash_end(key)
end
def array_start(key)
end
def array_end(key)
end
def add_value(value, key)
end
def error(message, line, column)
end
end # Saj
end # Oj
oj-2.5.3/lib/oj/version.rb 0000644 0000041 0000041 00000000107 12263716750 015360 0 ustar www-data www-data
module Oj
# Current version of the module.
VERSION = '2.5.3'
end
oj-2.5.3/lib/oj/bag.rb 0000644 0000041 0000041 00000006715 12263716750 014437 0 ustar www-data www-data
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
at_m = ('@' + m.to_s).to_sym
instance_variables.include?(at_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.to_s).to_sym
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.each do |vid|
return false if instance_variable_get(vid) != other.instance_variable_get(vid)
end
true
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 { |n| n.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-2.5.3/lib/oj/mimic.rb 0000644 0000041 0000041 00000000740 12263716750 014774 0 ustar www-data www-data
module Oj
def self.mimic_loaded(mimic_paths=[])
$LOAD_PATH.each do |d|
next unless File.exist?(d)
offset = d.size() + 1
Dir.glob(File.join(d, '**', '*.rb')).each do |file|
next unless file[offset..-1].start_with?('json')
$LOADED_FEATURES << file unless $LOADED_FEATURES.include?(file)
end
end
mimic_paths.each { |p| $LOADED_FEATURES << p }
$LOADED_FEATURES << 'json' unless $LOADED_FEATURES.include?('json')
end
end
oj-2.5.3/lib/oj/error.rb 0000644 0000041 0000041 00000001041 12263716750 015022 0 ustar www-data www-data
module Oj
class Error < StandardError
end # Error
# An Exception that is raised as a result of a parse error while parsing a JSON document.
class ParseError < Error
end # ParseError
# An Exception that is raised as a result of a path being too deep.
class DepthError < Error
end # DepthError
# An Exception that is raised if a file fails to load.
class LoadError < Error
end # LoadError
# An Exception that is raised if there is a conflict with mimicing JSON
class MimicError < Error
end # MimicError
end # Oj
oj-2.5.3/metadata.yml 0000644 0000041 0000041 00000004700 12263716750 014476 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: oj
version: !ruby/object:Gem::Version
version: 2.5.3
platform: ruby
authors:
- Peter Ohler
autorequire:
bindir: bin
cert_chain: []
date: 2014-01-03 00:00:00.000000000 Z
dependencies: []
description: 'The fastest JSON parser and object serializer. '
email: peter@ohler.com
executables: []
extensions:
- ext/oj/extconf.rb
extra_rdoc_files:
- README.md
files:
- LICENSE
- README.md
- ext/oj/buf.h
- ext/oj/cache8.c
- ext/oj/cache8.h
- ext/oj/circarray.c
- ext/oj/circarray.h
- ext/oj/compat.c
- ext/oj/dump.c
- ext/oj/encode.h
- ext/oj/err.c
- ext/oj/err.h
- ext/oj/extconf.rb
- ext/oj/fast.c
- ext/oj/hash.c
- ext/oj/hash.h
- ext/oj/hash_test.c
- ext/oj/object.c
- ext/oj/odd.c
- ext/oj/odd.h
- ext/oj/oj.c
- ext/oj/oj.h
- ext/oj/parse.c
- ext/oj/parse.h
- ext/oj/resolve.c
- ext/oj/resolve.h
- ext/oj/saj.c
- ext/oj/scp.c
- ext/oj/strict.c
- ext/oj/val_stack.c
- ext/oj/val_stack.h
- lib/oj.rb
- lib/oj/bag.rb
- lib/oj/error.rb
- lib/oj/mimic.rb
- lib/oj/saj.rb
- lib/oj/schandler.rb
- lib/oj/version.rb
- test/a.rb
- test/bug.rb
- test/debian_test.rb
- test/e.rb
- test/files.rb
- test/foo.rb
- test/lots.rb
- test/mj.rb
- test/perf.rb
- test/perf_compat.rb
- test/perf_fast.rb
- test/perf_object.rb
- test/perf_saj.rb
- test/perf_scp.rb
- test/perf_simple.rb
- test/perf_strict.rb
- test/sample.rb
- test/sample/change.rb
- test/sample/dir.rb
- test/sample/doc.rb
- test/sample/file.rb
- test/sample/group.rb
- test/sample/hasprops.rb
- test/sample/layer.rb
- test/sample/line.rb
- test/sample/oval.rb
- test/sample/rect.rb
- test/sample/shape.rb
- test/sample/text.rb
- test/sample_json.rb
- test/struct.rb
- test/test_compat.rb
- test/test_fast.rb
- test/test_gc.rb
- test/test_mimic.rb
- test/test_mimic_after.rb
- test/test_object.rb
- test/test_saj.rb
- test/test_scp.rb
- test/test_strict.rb
- test/test_writer.rb
- test/tests.rb
- test/x.rb
homepage: http://www.ohler.com/oj
licenses:
- MIT
- GPL-3.0
metadata: {}
post_install_message:
rdoc_options:
- "--main"
- README.md
require_paths:
- lib
- ext
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project: oj
rubygems_version: 2.2.0
signing_key:
specification_version: 4
summary: A fast JSON parser and serializer.
test_files: []
has_rdoc: true
oj-2.5.3/test/ 0000755 0000041 0000041 00000000000 12263716750 013151 5 ustar www-data www-data oj-2.5.3/test/perf_simple.rb 0000644 0000041 0000041 00000016026 12263716750 016010 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'yajl'
require 'json'
require 'json/pure'
require 'json/ext'
require 'msgpack'
require 'oj'
require 'ox'
class Jazz
def initialize()
@boolean = true
@number = 58
@string = "A string"
@array = [true, false, nil]
@hash = { 'one' => 1, 'two' => 2 }
end
def to_json()
%{
{ "boolean":#{@boolean},
"number":#{@number},
"string":#{@string},
"array":#{@array},
"hash":#{@hash},
}
}
end
def to_hash()
{ 'boolean' => @boolean,
'number' => @number,
'string' => @string,
'array' => @array,
'hash' => @hash,
}
end
def to_msgpack(out='')
to_hash().to_msgpack(out)
end
end
$indent = 2
$iter = 10000
$with_object = true
$with_bignum = true
$with_nums = true
opts = OptionParser.new
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-o", "without objects") { $with_object = false }
opts.on("-b", "without bignum") { $with_bignum = false }
opts.on("-n", "without numbers") { $with_nums = false }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
if $with_nums
obj = {
'a' => 'Alpha',
'b' => true,
'c' => 12345,
'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]],
'e' => { 'one' => 1, 'two' => 2 },
'f' => nil,
}
obj['g'] = Jazz.new() if $with_object
obj['h'] = 12345678901234567890123456789 if $with_bignum
else
obj = {
'a' => 'Alpha',
'b' => true,
'c' => '12345',
'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]],
'e' => { 'one' => '1', 'two' => '2' },
'f' => nil,
}
end
Oj.default_options = { :indent => $indent, :mode => :object }
s = Oj.dump(obj)
xml = Ox.dump(obj, :indent => $indent)
puts
# Put Oj in strict mode so it only create JSON native types instead of the
# original Ruby Objects. None of the other packages other than Ox support
# Object recreation so no need for Oj to do it in the performance tests.
Oj.default_options = { :mode => :strict }
parse_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 }
start = Time.now
$iter.times do
Oj.load(s)
end
dt = Time.now - start
base_dt = dt
parse_results[:oj] = dt
puts "%d Oj.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0]
start = Time.now
$iter.times do
Yajl::Parser.parse(s)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'Yajl'
end
parse_results[:yajl] = dt
puts "%d Yajl::Parser.parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0]
begin
JSON.parser = JSON::Ext::Parser
start = Time.now
$iter.times do
JSON.parse(s)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'JSON::Ext'
end
parse_results[:ext] = dt
puts "%d JSON::Ext::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
puts "JSON::Ext failed: #{e.class}: #{e.message}"
end
begin
JSON.parser = JSON::Pure::Parser
start = Time.now
$iter.times do
JSON.parse(s)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'JSON::Pure'
end
parse_results[:pure] = dt
puts "%d JSON::Pure::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
puts "JSON::Pure failed: #{e.class}: #{e.message}"
end
begin
mp = MessagePack.pack(obj)
start = Time.now
$iter.times do
MessagePack.unpack(mp)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'MessagePack'
end
parse_results[:msgpack] = dt
puts "%d MessagePack.unpack()s in %0.3f seconds or %0.1f packs/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
puts "MessagePack failed: #{e.class}: #{e.message}"
end
start = Time.now
$iter.times do
Ox.load(xml)
end
dt = Time.now - start
parse_results[:ox] = dt
puts "%d Ox.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0]
puts "Parser results:"
puts "gem seconds parses/msec X faster than #{base_name} (higher is better)"
parse_results.each do |name,dt|
if 0.0 == dt
puts "#{name} failed to generate JSON"
next
end
puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt]
end
puts
# Back to object mode for best performance when dumping.
Oj.default_options = { :indent => $indent, :mode => :object }
dump_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 }
start = Time.now
$iter.times do
Oj.dump(obj)
end
dt = Time.now - start
base_dt = dt
base_name = 'Oj'
parse_results[:oj] = dt
puts "%d Oj.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0]
start = Time.now
$iter.times do
Yajl::Encoder.encode(obj)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'Yajl'
end
parse_results[:yajl] = dt
puts "%d Yajl::Encoder.encode()s in %0.3f seconds or %0.1f encodes/msec" % [$iter, dt, $iter/dt/1000.0]
begin
JSON.parser = JSON::Ext::Parser
start = Time.now
$iter.times do
JSON.generate(obj)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'JSON::Ext'
end
parse_results[:pure] = dt
puts "%d JSON::Ext generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
parse_results[:ext] = 0.0
puts "JSON::Ext failed: #{e.class}: #{e.message}"
end
begin
JSON.parser = JSON::Pure::Parser
start = Time.now
$iter.times do
JSON.generate(obj)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'JSON::Pure'
end
parse_results[:pure] = dt
puts "%d JSON::Pure generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
parse_results[:pure] = 0.0
puts "JSON::Pure failed: #{e.class}: #{e.message}"
end
begin
start = Time.now
$iter.times do
MessagePack.pack(obj)
end
dt = Time.now - start
if base_dt < dt
base_dt = dt
base_name = 'MessagePack'
end
parse_results[:msgpack] = dt
puts "%d Msgpack()s in %0.3f seconds or %0.1f unpacks/msec" % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
parse_results[:msgpack] = 0.0
puts "MessagePack failed: #{e.class}: #{e.message}"
end
start = Time.now
$iter.times do
Ox.dump(obj)
end
dt = Time.now - start
parse_results[:ox] = dt
puts "%d Ox.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0]
puts "Parser results:"
puts "gem seconds dumps/msec X faster than #{base_name} (higher is better)"
parse_results.each do |name,dt|
if 0.0 == dt
puts "#{name} failed to generate JSON"
next
end
puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt]
end
puts
oj-2.5.3/test/tests.rb 0000755 0000041 0000041 00000071010 12263716750 014642 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
def hash_eql(h1, h2)
return false if h1.size != h2.size
h1.keys.each do |k|
return false unless h1[k] == h2[k]
end
true
end
class Jam
attr_accessor :x, :y
def initialize(x, y)
@x = x
@y = y
end
def eql?(o)
self.class == o.class && @x == o.x && @y == o.y
end
alias == eql?
end# Jam
class Jeez < Jam
def initialize(x, y)
super
end
def to_json()
%{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}}
end
def self.json_create(h)
self.new(h['x'], h['y'])
end
end# Jeez
# contributed by sauliusg to fix as_json
class Orange < Jam
def initialize(x, y)
super
end
def as_json()
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
self.new(h['x'], h['y'])
end
end
class Melon < Jam
def initialize(x, y)
super
end
def as_json()
"#{x} #{y}"
end
def self.json_create(h)
self.new(h['x'], h['y'])
end
end
class Jazz < Jam
def initialize(x, y)
super
end
def to_hash()
{ 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y }
end
def self.json_create(h)
self.new(h['x'], h['y'])
end
end# Jazz
class Range
def to_hash()
{ 'begin' => self.begin, 'end' => self.end, 'exclude_end' => self.exclude_end? }
end
end # Range
# define the symbol
class ActiveSupport
end
class RailsLike
attr_accessor :x
def initialize(x)
@x = x
end
def to_json(options = nil)
Oj.dump(self, :mode => :compat)
end
end # RailsLike
class Juice < ::Test::Unit::TestCase
def test0_get_options
opts = Oj.default_options()
assert_equal({ :indent=>0,
:second_precision=>9,
:circular=>false,
:auto_define=>false,
:symbol_keys=>false,
:class_cache=>true,
:escape_mode=>:json,
:mode=>:object,
:time_format=>:unix,
:bigdecimal_as_decimal=>true,
:bigdecimal_load=>:auto,
:allow_gc=>true,
:create_id=>'json_class'}, opts)
end
def test0_set_options
orig = {
:indent=>0,
:second_precision=>9,
:circular=>false,
:auto_define=>false,
:symbol_keys=>false,
:class_cache=>true,
:escape_mode=>:ascii,
:mode=>:object,
:time_format=>:unix,
:bigdecimal_as_decimal=>true,
:bigdecimal_load=>:auto,
:allow_gc=>true,
:create_id=>'json_class'}
o2 = {
:indent=>4,
:second_precision=>7,
:circular=>true,
:auto_define=>true,
:symbol_keys=>true,
:class_cache=>false,
:escape_mode=>:json,
:mode=>:compat,
:time_format=>:ruby,
:bigdecimal_as_decimal=>false,
:bigdecimal_load=>:bigdecimal,
:allow_gc=>false,
:create_id=>nil}
o3 = { :indent => 4 }
Oj.default_options = o2
opts = Oj.default_options()
assert_equal(o2, opts);
Oj.default_options = o3 # see if it throws an exception
Oj.default_options = orig # return to original
end
def test_nil
dump_and_load(nil, false)
end
def test_true
dump_and_load(true, false)
end
def test_false
dump_and_load(false, false)
end
def test_fixnum
dump_and_load(0, false)
dump_and_load(12345, false)
dump_and_load(-54321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54321.012, false)
dump_and_load(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)
assert_equal('NaN', json)
loaded = Oj.load(json);
assert_equal(true, loaded.nan?)
end
def test_string
dump_and_load('', false)
dump_and_load('abc', false)
dump_and_load("abc\ndef", false)
dump_and_load("a\u0041", false)
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_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_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
# rails encoding tests
def test_does_not_escape_entities_by_default
# use Oj to create the hash since some Rubies don't deal nicely with unicode.
json = %{{"key":"I <3 this\\u2028space"}}
hash = Oj.load(json)
out = Oj.dump(hash)
assert_equal(json, out)
end
def test_escapes_entities_by_default_when_configured_to_do_so
hash = {'key' => "I <3 this"}
Oj.default_options = {:escape_mode => :xss_safe}
out = Oj.dump hash
assert_equal(%{{"key":"I \\u003c3 this"}}, out)
end
def test_escapes_entities_when_asked_to
hash = {'key' => "I <3 this"}
out = Oj.dump(hash, :escape_mode => :xss_safe)
assert_equal(%{{"key":"I \\u003c3 this"}}, out)
end
def test_does_not_escape_entities_when_not_asked_to
hash = {'key' => "I <3 this"}
out = Oj.dump(hash, :escape_mode => :json)
assert_equal(%{{"key":"I <3 this"}}, out)
end
def test_escapes_common_xss_vectors
hash = {'key' => ""}
out = Oj.dump(hash, :escape_mode => :xss_safe)
assert_equal(%{{"key":"\\u003cscript\\u003ealert(123) \\u0026\\u0026 formatHD()\\u003c\\/script\\u003e"}}, out)
end
# Symbol
def test_symbol_strict
begin
Oj.dump(:abc, :mode => :strict)
rescue Exception
assert(true)
return
end
assert(false, "*** expected an exception")
end
def test_symbol_null
json = Oj.dump(:abc, :mode => :null)
assert_equal('null', json)
end
def test_symbol_compat
json = Oj.dump(:abc, :mode => :compat)
assert_equal('"abc"', json)
end
def test_symbol_object
Oj.default_options = { :mode => :object }
#dump_and_load(''.to_sym, false)
dump_and_load(:abc, false)
dump_and_load(':xyz'.to_sym, false)
end
# Time
def test_time_strict
t = Time.local(2012, 1, 5, 23, 58, 7)
begin
Oj.dump(t, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
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_unix_time_compat
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(%{1325775487.123456000}, json)
end
def test_unix_time_compat_precision
t = Time.xmlschema("2012-01-05T23:58:07.123456789+09:00")
#t = Time.local(2012, 1, 5, 23, 58, 7, 123456)
json = Oj.dump(t, :mode => :compat, :second_precision => 5)
assert_equal(%{1325775487.12346}, json)
t = Time.xmlschema("2012-01-05T23:58:07.999600+09:00")
json = Oj.dump(t, :mode => :compat, :second_precision => 3)
assert_equal(%{1325775488.000}, json)
end
def test_unix_time_compat_early
t = Time.xmlschema("1954-01-05T00:00:00.123456789+00:00")
json = Oj.dump(t, :mode => :compat, :second_precision => 5)
assert_equal(%{-504575999.87654}, json)
end
def test_unix_time_compat_1970
t = Time.xmlschema("1970-01-01T00:00:00.123456789+00:00")
json = Oj.dump(t, :mode => :compat, :second_precision => 5)
assert_equal(%{0.12346}, json)
end
def test_ruby_time_compat
t = Time.xmlschema("2012-01-05T23:58:07.123456000+09:00")
json = Oj.dump(t, :mode => :compat, :time_format => :ruby)
#assert_equal(%{"2012-01-05 23:58:07 +0900"}, json)
assert_equal(%{"#{t.to_s}"}, json)
end
def test_xml_time_compat
begin
t = Time.new(2012, 1, 5, 23, 58, 7.123456000, 34200)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
assert_equal(%{"2012-01-05T23:58:07.123456000+09:30"}, json)
rescue Exception
# some Rubies (1.8.7) do not allow the timezome to be set
t = Time.local(2012, 1, 5, 23, 58, 7, 123456)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
tz = t.utc_offset
# Ruby does not handle a %+02d properly so...
sign = '+'
if 0 > tz
sign = '-'
tz = -tz
end
assert_equal(%{"2012-01-05T23:58:07.123456000%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_no_secs
begin
t = Time.new(2012, 1, 5, 23, 58, 7.0, 34200)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
assert_equal(%{"2012-01-05T23:58:07+09:30"}, json)
rescue Exception
# some Rubies (1.8.7) do not allow the timezome to be set
t = Time.local(2012, 1, 5, 23, 58, 7, 0)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
tz = t.utc_offset
# Ruby does not handle a %+02d properly so...
sign = '+'
if 0 > tz
sign = '-'
tz = -tz
end
assert_equal(%{"2012-01-05T23:58:07%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_precision
begin
t = Time.new(2012, 1, 5, 23, 58, 7.123456789, 32400)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 5)
assert_equal(%{"2012-01-05T23:58:07.12346+09:00"}, json)
rescue Exception
# some Rubies (1.8.7) do not allow the timezome to be set
t = Time.local(2012, 1, 5, 23, 58, 7, 123456)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 5)
tz = t.utc_offset
# Ruby does not handle a %+02d properly so...
sign = '+'
if 0 > tz
sign = '-'
tz = -tz
end
assert_equal(%{"2012-01-05T23:58:07.12346%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_precision_round
begin
t = Time.new(2012, 1, 5, 23, 58, 7.9996, 32400)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 3)
assert_equal(%{"2012-01-05T23:58:08+09:00"}, json)
rescue Exception
# some Rubies (1.8.7) do not allow the timezome to be set
t = Time.local(2012, 1, 5, 23, 58, 7, 999600)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 3)
tz = t.utc_offset
# Ruby does not handle a %+02d properly so...
sign = '+'
if 0 > tz
sign = '-'
tz = -tz
end
assert_equal(%{"2012-01-05T23:58:08%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_zulu
begin
t = Time.new(2012, 1, 5, 23, 58, 7.0, 0)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
assert_equal(%{"2012-01-05T23:58:07Z"}, json)
rescue Exception
# some Rubies (1.8.7) do not allow the timezome to be set
t = Time.utc(2012, 1, 5, 23, 58, 7, 0)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
#tz = t.utc_offset
assert_equal(%{"2012-01-05T23:58:07Z"}, json)
end
end
def test_time_object
t = Time.now()
Oj.default_options = { :mode => :object }
dump_and_load(t, false)
end
def test_time_object_early
t = Time.xmlschema("1954-01-05T00:00:00.123456")
Oj.default_options = { :mode => :object }
dump_and_load(t, false)
end
# Class
def test_class_strict
begin
Oj.dump(Juice, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_class_null
json = Oj.dump(Juice, :mode => :null)
assert_equal('null', json)
end
def test_class_compat
json = Oj.dump(Juice, :mode => :compat)
assert_equal(%{"Juice"}, json)
end
def test_class_object
Oj.default_options = { :mode => :object }
dump_and_load(Juice, 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
def test_non_str_hash_strict
begin
Oj.dump({ 1 => true, 0 => false }, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_non_str_hash_null
begin
Oj.dump({ 1 => true, 0 => false }, :mode => :null)
assert(false)
rescue Exception
assert(true)
end
end
def test_non_str_hash_compat
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_non_str_hash_object
Oj.default_options = { :mode => :object }
json = Oj.dump({ 1 => true, :sim => nil })
h = Oj.load(json, :mode => :strict)
assert_equal({"^#1" => [1, true], ":sim" => nil}, h)
h = Oj.load(json)
assert_equal({ 1 => true, :sim => nil }, h)
end
def test_mixed_hash_object
Oj.default_options = { :mode => :object }
json = Oj.dump({ 1 => true, 'nil' => nil, :sim => 4 })
h = Oj.load(json, :mode => :strict)
assert_equal({"^#1" => [1, true], "nil" => nil, ":sim" => 4}, h)
h = Oj.load(json)
assert_equal({ 1 => true, 'nil' => nil, :sim => 4 }, h)
end
# Object with to_json()
def test_json_object_strict
obj = Jeez.new(true, 58)
begin
Oj.dump(obj, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_json_object_null
obj = Jeez.new(true, 58)
json = Oj.dump(obj, :mode => :null)
assert_equal('null', json)
end
def test_json_object_compat
Oj.default_options = { :mode => :compat }
obj = Jeez.new(true, 58)
json = Oj.dump(obj, :indent => 2)
assert(%{{"json_class":"Jeez","x":true,"y":58}} == json ||
%{{"json_class":"Jeez","y":58,"x":true}} == json)
dump_and_load(obj, false)
end
def test_json_object_create_id
Oj.default_options = { :mode => :compat, :create_id => 'kson_class' }
expected = Jeez.new(true, 58)
json = %{{"kson_class":"Jeez","x":true,"y":58}}
obj = Oj.load(json)
assert_equal(expected, obj)
Oj.default_options = { :create_id => 'json_class' }
end
def test_json_object_object
obj = Jeez.new(true, 58)
json = Oj.dump(obj, :mode => :object, :indent => 2)
assert(%{{
"^o":"Jeez",
"x":true,
"y":58
}} == json ||
%{{
"^o":"Jeez",
"y":58,
"x":true
}} == json)
obj2 = Oj.load(json, :mode => :object)
assert_equal(obj, obj2)
end
# Object with to_hash()
def test_to_hash_object_strict
obj = Jazz.new(true, 58)
begin
Oj.dump(obj, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_to_hash_object_null
obj = Jazz.new(true, 58)
json = Oj.dump(obj, :mode => :null)
assert_equal('null', json)
end
def test_to_hash_object_compat
obj = Jazz.new(true, 58)
json = Oj.dump(obj, :mode => :compat, :indent => 2)
h = Oj.load(json, :mode => :strict)
assert_equal(obj.to_hash, h)
end
def test_to_hash_object_object
obj = Jazz.new(true, 58)
json = Oj.dump(obj, :mode => :object, :indent => 2)
assert(%{{
"^o":"Jazz",
"x":true,
"y":58
}} == json ||
%{{
"^o":"Jazz",
"y":58,
"x":true
}} == json)
obj2 = Oj.load(json, :mode => :object)
assert_equal(obj, obj2)
end
# Object with as_json() # contributed by sauliusg
def test_as_json_object_strict
obj = Orange.new(true, 58)
begin
Oj.dump(obj, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_as_json_object_null
obj = Orange.new(true, 58)
json = Oj.dump(obj, :mode => :null)
assert_equal('null', json)
end
def test_as_json_object_compat_hash
Oj.default_options = { :mode => :compat }
obj = Orange.new(true, 58)
json = Oj.dump(obj, :indent => 2)
assert(!json.nil?)
dump_and_load(obj, false)
end
def test_as_json_object_compat_non_hash
Oj.default_options = { :mode => :compat }
obj = Melon.new(true, 58)
json = Oj.dump(obj, :indent => 2)
assert_equal(%{"true 58"}, json)
end
def test_as_json_object_object
obj = Orange.new(true, 58)
json = Oj.dump(obj, :mode => :object, :indent => 2)
assert(%{{
"^o":"Orange",
"x":true,
"y":58
}} == json ||
%{{
"^o":"Orange",
"y":58,
"x":true
}} == json)
obj2 = Oj.load(json, :mode => :object)
assert_equal(obj, obj2)
end
# Object without to_json() or to_hash()
def test_object_strict
obj = Jam.new(true, 58)
begin
Oj.dump(obj, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_object_null
obj = Jam.new(true, 58)
json = Oj.dump(obj, :mode => :null)
assert_equal('null', json)
end
def test_object_compat
obj = Jam.new(true, 58)
json = Oj.dump(obj, :mode => :compat, :indent => 2)
assert(%{{
"x":true,
"y":58
}} == json ||
%{{
"y":58,
"x":true
}} == json)
end
def test_object_object
obj = Jam.new(true, 58)
json = Oj.dump(obj, :mode => :object, :indent => 2)
assert(%{{
"^o":"Jam",
"x":true,
"y":58
}} == json ||
%{{
"^o":"Jam",
"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":"Jam",
"x":true,
"y":58
}} == json ||
%{{
"^o":"Jam",
"y":58,
"x":true
}} == json)
obj2 = Oj.load(json, :mode => :object, :class_cache => false)
assert_equal(obj, obj2)
end
# Exception
def test_exception
err = nil
begin
raise StandardError.new('A Message')
assert(false)
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 RUBY_VERSION.start_with?('1.8') || '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
# Range
def test_range_strict
begin
Oj.dump(1..7, :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_range_null
json = Oj.dump(1..7, :mode => :null)
assert_equal('null', json)
end
def test_range_compat
json = Oj.dump(1..7, :mode => :compat)
h = Oj.load(json, :mode => :strict)
assert_equal({'begin' => 1, 'end' => 7, 'exclude_end' => false}, h)
json = Oj.dump(1...7, :mode => :compat)
h = Oj.load(json, :mode => :strict)
assert_equal({'begin' => 1, 'end' => 7, 'exclude_end' => true}, h)
end
def test_range_object
unless RUBY_VERSION.start_with?('1.8')
Oj.default_options = { :mode => :object }
json = Oj.dump(1..7, :mode => :object, :indent => 0)
if 'rubinius' == $ruby
assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json)
elsif 'jruby' == $ruby
assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json)
else
assert_equal(%{{"^u":["Range",1,7,false]}}, json)
end
dump_and_load(1..7, false)
dump_and_load(1..1, false)
dump_and_load(1...7, false)
end
end
# BigNum
def test_bignum_strict
json = Oj.dump(7 ** 55, :mode => :strict)
assert_equal('30226801971775055948247051683954096612865741943', json)
end
def test_bignum_null
json = Oj.dump(7 ** 55, :mode => :null)
assert_equal('30226801971775055948247051683954096612865741943', json)
end
def test_bignum_compat
json = Oj.dump(7 ** 55, :mode => :compat)
b = Oj.load(json, :mode => :strict)
assert_equal(30226801971775055948247051683954096612865741943, b)
end
def test_bignum_object
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.new('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.new('3.14159265358979323846'), false)
Oj.default_options = {:mode => mode}
end
def test_bigdecimal_compat
orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => false)
bg = Oj.load(json, :mode => :compat)
assert_equal(orig.to_s, bg)
orig = BigDecimal.new('3.14159265358979323846')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => false)
bg = Oj.load(json, :mode => :compat)
assert_equal(orig.to_s, bg)
end
def test_bigdecimal_load
orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true)
bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true)
assert_equal(BigDecimal, bg.class)
assert_equal(orig, bg)
end
def test_float_load
orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true)
bg = Oj.load(json, :mode => :compat, :bigdecimal_load => :float)
assert_equal(Float, bg.class)
assert_equal(orig.to_f, bg)
end
def test_bigdecimal_compat_to_json
orig = BigDecimal.new('80.51')
BigDecimal.send(:define_method, :to_json) do
%{"this is big"}
end
json = Oj.dump(orig, :mode => :compat)
bg = Oj.load(json, :mode => :compat)
assert_equal("this is big", bg)
BigDecimal.send(:remove_method, :to_json) # cleanup
end
def test_bigdecimal_object
mode = Oj.default_options[:mode]
Oj.default_options = {:mode => :object}
dump_and_load(BigDecimal.new('3.14159265358979323846'), false)
Oj.default_options = {:mode => mode}
# Infinity is the same for Float and BigDecimal
json = Oj.dump(BigDecimal.new('Infinity'), :mode => :object)
assert_equal('Infinity', json)
json = Oj.dump(BigDecimal.new('-Infinity'), :mode => :object)
assert_equal('-Infinity', json)
end
# Date
def test_date_strict
begin
Oj.dump(Date.new(2012, 6, 19), :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_date_null
json = Oj.dump(Date.new(2012, 6, 19), :mode => :null)
assert_equal('null', 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_date_object
dump_and_load(Date.new(2012, 6, 19), false)
end
# DateTime
def test_datetime_strict
begin
Oj.dump(DateTime.new(2012, 6, 19, 20, 19, 27), :mode => :strict)
assert(false)
rescue Exception
assert(true)
end
end
def test_datetime_null
json = Oj.dump(DateTime.new(2012, 6, 19, 20, 19, 27), :mode => :null)
assert_equal('null', json)
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
def test_datetime_object
dump_and_load(DateTime.new(2012, 6, 19), false)
end
# autodefine Oj::Bag
def test_bag
json = %{{
"^o":"Jem",
"x":true,
"y":58 }}
obj = Oj.load(json, :mode => :object, :auto_define => true)
assert_equal('Jem', obj.class.name)
assert_equal(true, obj.x)
assert_equal(58, obj.y)
end
# Circular
def test_circular_object
obj = Jam.new(nil, 58)
obj.x = obj
json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true)
assert(%{{
"^o":"Jam",
"^i":1,
"x":"^r1",
"y":58
}} == json ||
%{{
"^o":"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_hash
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_array
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
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' => '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
# Stream Deeply Nested
def test_deep_nest
#unless 'jruby' == RUBY_DESCRIPTION.split(' ')[0]
begin
n = 10000
Oj.strict_load('[' * n + ']' * n)
rescue Exception => e
assert(false, e.message)
end
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 = '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
# symbol_keys option
def test_symbol_keys
json = %{{
"x":true,
"y":58,
"z": [1,2,3]
}
}
obj = Oj.load(json, :mode => :strict, :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.load(json, :mode => :strict)
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.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
# Rails re-call test. Active support recalls the json dumper when the to_json
# method is called. This mimics that and verifies Oj detects it.
def test_rails_like
obj = RailsLike.new(3)
json = Oj.dump(obj, :mode => :compat)
assert_equal('{"x":3}', json)
end
def dump_and_load(obj, trace=false)
json = Oj.dump(obj, :indent => 2)
puts json if trace
loaded = Oj.load(json);
assert_equal(obj, loaded)
loaded
end
end
oj-2.5.3/test/test_scp.rb 0000755 0000041 0000041 00000012303 12263716750 015324 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'bigdecimal'
require 'oj'
require 'pp'
$json = %{{
"array": [
{
"num" : 3,
"string": "message",
"hash" : {
"h2" : {
"a" : [ 1, 2, 3 ]
}
}
}
],
"boolean" : true
}}
class NoHandler < Oj::ScHandler
def initialize()
end
end
class AllHandler < Oj::ScHandler
attr_accessor :calls
def initialize()
@calls = []
end
def hash_start()
@calls << [:hash_start]
{}
end
def hash_end()
@calls << [:hash_end]
end
def 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 ScpTest < ::Test::Unit::TestCase
def test_nil
handler = AllHandler.new()
json = %{null}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, nil]], handler.calls)
end
def test_true
handler = AllHandler.new()
json = %{true}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, true]], handler.calls)
end
def test_false
handler = AllHandler.new()
json = %{false}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, false]], handler.calls)
end
def test_string
handler = AllHandler.new()
json = %{"a string"}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, 'a string']], handler.calls)
end
def test_fixnum
handler = AllHandler.new()
json = %{12345}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, 12345]], handler.calls)
end
def test_float
handler = AllHandler.new()
json = %{12345.6789}
Oj.sc_parse(handler, json)
assert_equal([[:add_value, 12345.6789]], handler.calls)
end
def test_float_exp
handler = AllHandler.new()
json = %{12345.6789e7}
Oj.sc_parse(handler, json)
assert_equal(1, handler.calls.size)
assert_equal(:add_value, handler.calls[0][0])
assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i)
end
def test_array_empty
handler = AllHandler.new()
json = %{[]}
Oj.sc_parse(handler, json)
assert_equal([[:array_start],
[:array_end],
[:add_value, []]], handler.calls)
end
def test_array
handler = AllHandler.new()
json = %{[true,false]}
Oj.sc_parse(handler, json)
assert_equal([[:array_start],
[:array_append, true],
[:array_append, false],
[:array_end],
[:add_value, []]], handler.calls)
end
def test_hash_empty
handler = AllHandler.new()
json = %{{}}
Oj.sc_parse(handler, json)
assert_equal([[:hash_start],
[:hash_end],
[:add_value, {}]], handler.calls)
end
def test_hash
handler = AllHandler.new()
json = %{{"one":true,"two":false}}
Oj.sc_parse(handler, json)
assert_equal([[:hash_start],
[:hash_set, 'one', true],
[:hash_set, 'two', 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_set, :one, true],
[:hash_set, :two, false],
[:hash_end],
[:add_value, {}]], handler.calls)
end
def test_full
handler = AllHandler.new()
Oj.sc_parse(handler, $json)
assert_equal([[:hash_start],
[:array_start],
[:hash_start],
[:hash_set, "num", 3],
[:hash_set, "string", "message"],
[:hash_start],
[:hash_start],
[: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_set, "boolean", true],
[: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}
begin
Oj.sc_parse(handler, json)
rescue Exception => e
assert_equal("unexpected character at line 1, column 6 [parse.c:637]", e.message)
end
end
end
oj-2.5.3/test/files.rb 0000755 0000041 0000041 00000000760 12263716750 014606 0 ustar www-data www-data #!/usr/bin/env ruby -wW2
if $0 == __FILE__
$: << '.'
$: << '..'
$: << '../lib'
$: << '../ext'
end
require 'pp'
require 'sample/file'
require 'sample/dir'
def files(dir)
d = ::Sample::Dir.new(dir)
Dir.new(dir).each do |fn|
next if fn.start_with?('.')
filename = File.join(dir, fn)
#filename = '.' == dir ? fn : File.join(dir, fn)
if File.directory?(filename)
d << files(filename)
else
d << ::Sample::File.new(filename)
end
end
#pp d
d
end
oj-2.5.3/test/perf_strict.rb 0000755 0000041 0000041 00000010261 12263716750 016025 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << '.'
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'perf'
require 'oj'
$verbose = false
$indent = 0
$iter = 20000
$with_bignum = false
$with_nums = true
$size = 0
opts = OptionParser.new
opts.on("-v", "verbose") { $verbose = true }
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
opts.on("-b", "with bignum") { $with_bignum = true }
opts.on("-n", "without numbers") { $with_nums = false }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
if $with_nums
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
'f' => nil, # nil
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
$obj['g'] = 12345678901234567890123456789 if $with_bignum
else
$obj = {
'a' => 'Alpha',
'b' => true,
'c' => '12345',
'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]],
'e' => { 'zero' => '0', 'one' => '1', 'two' => '2' },
'f' => nil,
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
end
Oj.default_options = { :indent => $indent, :mode => :strict }
if 0 < $size
o = $obj
$obj = []
(4 * $size).times do
$obj << o
end
end
$json = Oj.dump($obj)
$obj_json = Oj.dump($obj, :mode => :object)
#puts "*** size: #{$obj_json.size}"
$failed = {} # key is same as String used in tests later
def capture_error(tag, orig, load_key, dump_key, &blk)
begin
obj = blk.call(orig)
raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
rescue Exception => e
$failed[tag] = "#{e.class}: #{e.message}"
end
end
# Verify that all packages dump and load correctly and return the same Object as the original.
capture_error('Oj:strict', $obj, 'load', 'dump') { |o| Oj.strict_load(Oj.dump(o, :mode => :strict)) }
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.strict_load($json)}\n"
puts "Yajl loaded object:\n#{Yajl::Parser.parse($json)}\n"
puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n"
end
puts '-' * 80
puts "Strict Parse Performance"
perf = Perf.new()
unless $failed.has_key?('JSON::Ext')
perf.add('JSON::Ext', 'parse') { JSON.parse($json) }
perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
end
unless $failed.has_key?('JSON::Pure')
perf.add('JSON::Pure', 'parse') { JSON.parse($json) }
perf.before('JSON::Pure') { JSON.parser = JSON::Pure::Parser }
end
unless $failed.has_key?('Oj:strict')
perf.add('Oj:strict', 'strict_load') { Oj.strict_load($json) }
end
perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl')
perf.run($iter)
puts
puts '-' * 80
puts
unless $failed.empty?
puts "The following packages were not included for the reason listed"
$failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
end
oj-2.5.3/test/lots.rb 0000755 0000041 0000041 00000003100 12263716750 014454 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
module One
module Two
module Three
class Empty
def initialize()
end
def eql?(o)
self.class == o.class
end
alias == eql?
def to_hash()
{'json_class' => "#{self.class.name}"}
end
def to_json(*a)
%{{"json_class":"#{self.class.name}"}}
end
def self.json_create(h)
self.new()
end
end # Empty
end # Three
end # Two
end # One
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
'f' => nil, # nil
'g' => One::Two::Three::Empty.new(),
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
$obj = [$obj]*10000
Oj.default_options = { :indent => 2, :mode => :compat }
$json = Oj.dump($obj, :mode => :compat)
$result = nil
100.times { |i|
print(".") if (0 == i % 10)
$result = Oj.compat_load($json)
}
oj-2.5.3/test/perf_fast.rb 0000755 0000041 0000041 00000014202 12263716750 015451 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << '.'
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'yajl'
require 'perf'
require 'json'
require 'json/ext'
require 'oj'
$verbose = false
$indent = 0
$iter = 10000
$gets = 0
$fetch = false
$write = false
$read = false
opts = OptionParser.new
opts.on("-v", "verbose") { $verbose = true }
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i }
opts.on("-f", "fetch") { $fetch = true }
opts.on("-w", "write") { $write = true }
opts.on("-r", "read") { $read = true }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
# This just navigates to each leaf of the JSON structure.
def dig(obj, &blk)
case obj
when Array
obj.each { |e| dig(e, &blk) }
when Hash
obj.values.each { |e| dig(e, &blk) }
else
blk.yield(obj)
end
end
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array
'e' => { 'one' => 1, 'two' => 2 }, # hash
'f' => nil, # nil
'g' => 12345678901234567890123456789, # big number
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
Oj.default_options = { :indent => $indent, :mode => :compat }
$json = Oj.dump($obj)
$failed = {} # key is same as String used in tests later
def capture_error(tag, orig, load_key, dump_key, &blk)
begin
obj = blk.call(orig)
raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
rescue Exception => e
$failed[tag] = "#{e.class}: #{e.message}"
end
end
# Verify that all packages dump and load correctly and return the same Object as the original.
capture_error('Oj::Doc', $obj, 'load', 'dump') { |o| Oj::Doc.open(Oj.dump(o, :mode => :strict)) { |f| f.fetch() } }
capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) }
capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse }
if $verbose
puts "json:\n#{$json}\n"
end
puts '-' * 80
puts "Parse Performance"
perf = Perf.new()
perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| } } unless $failed.has_key?('Oj::Doc')
perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl')
perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext')
perf.run($iter)
puts '-' * 80
puts "JSON generation Performance"
Oj::Doc.open($json) do |doc|
perf = Perf.new()
perf.add('Oj::Doc', 'dump') { doc.dump() }
perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) }
perf.add('JSON::Ext', 'fast_generate') { JSON.fast_generate($obj) }
perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator }
perf.run($iter)
end
if 0 < $gets
puts '-' * 80
puts "Parse and get all values Performance"
perf = Perf.new()
perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| $gets.times { f.each_value() {} } } } unless $failed.has_key?('Oj::Doc')
perf.add('Yajl', 'parse') { $gets.times { dig(Yajl::Parser.parse($json)) {} } } unless $failed.has_key?('Yajl')
perf.add('JSON::Ext', 'parse') { $gets.times { dig(JSON::Ext::Parser.new($json).parse) {} } } unless $failed.has_key?('JSON::Ext')
perf.run($iter)
end
if $fetch
puts '-' * 80
puts "fetch nested Performance"
json_hash = Oj.load($json, :mode => :strict)
Oj::Doc.open($json) do |fast|
#puts "*** C fetch: #{fast.fetch('/d/2/4/y')}"
#puts "*** Ruby fetch: #{json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil)}"
perf = Perf.new()
perf.add('Oj::Doc', 'fetch') { fast.fetch('/d/2/4/x'); fast.fetch('/h/a/b/c/d/e/f/g'); fast.fetch('/i/1/1/1/1/1/1/1') }
# version that fails gracefully
perf.add('Ruby', 'fetch') do
json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil)
json_hash.fetch('h', {}).fetch('a', {}).fetch('b', {}).fetch('c', {}).fetch('d', {}).fetch('e', {}).fetch('f', {}).fetch('g', {})
json_hash.fetch('i', []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, nil)
end
# version that raises if the path is incorrect
# perf.add('Ruby', 'fetch') { $fetch.times { json_hash['d'][1][3][1] } }
perf.run($iter)
end
end
if $write
puts '-' * 80
puts "JSON write to file Performance"
Oj::Doc.open($json) do |doc|
perf = Perf.new()
perf.add('Oj::Doc', 'dump') { doc.dump(nil, 'oj.json') }
perf.add('Yajl', 'encode') { File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) } }
perf.add('JSON::Ext', 'fast_generate') { File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) } }
perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator }
perf.run($iter)
end
end
if $read
puts '-' * 80
puts "JSON read from file Performance"
Oj::Doc.open($json) { |doc| doc.dump(nil, 'oj.json') }
File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) }
JSON.generator = JSON::Ext::Generator
File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) }
Oj::Doc.open($json) do |doc|
perf = Perf.new()
perf.add('Oj::Doc', 'open_file') { ::Oj::Doc.open_file('oj.json') }
perf.add('Yajl', 'decode') { Yajl::decoder.decode(File.read('yajl.json')) }
perf.add('JSON::Ext', '') { JSON.parse(File.read('json_ext.json')) }
perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
perf.run($iter)
end
end
unless $failed.empty?
puts "The following packages were not included for the reason listed"
$failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
end
oj-2.5.3/test/struct.rb 0000755 0000041 0000041 00000001221 12263716750 015021 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
A = Struct.new(:a,:b,:c,:d)
B = Struct.new(:e,:f)
obj = [A.new(55, B.new(1, 'X'), B.new(2, 'Y'), 3)]
s = Oj.dump(obj, :mode => :object)
100000.times do
Oj.load(s, :mode => :object)
# ds = Oj.dump(o, :mode => :object)
# if ds != s
# puts ds
# raise "holy crap"
# end
end
oj-2.5.3/test/foo.rb 0000644 0000041 0000041 00000001203 12263716750 014255 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
reltypes={}
Oj::Doc.open_file('foo.json') do |doc|
doc.each_child do |target|
puts "#{target.local_key} is #{target.local_key.class}"
target.each_leaf do |score|
reltype=score.local_key
reltypes[reltype] = (reltypes[reltype] || 0) + 1
end
end
end
oj-2.5.3/test/perf.rb 0000644 0000041 0000041 00000004636 12263716750 014443 0 ustar www-data www-data
class Perf
def initialize()
@items = []
end
def add(title, op, &blk)
@items << Item.new(title, op, &blk)
end
def before(title, &blk)
@items.each do |i|
if title == i.title
i.set_before(&blk)
break
end
end
end
def run(iter)
base = Item.new(nil, nil) { }
base.run(iter, 0.0)
@items.each do |i|
i.run(iter, base.duration)
if i.error.nil?
puts "#{i.title}.#{i.op} #{iter} times in %0.3f seconds or %0.3f #{i.op}/sec." % [i.duration, iter / i.duration]
else
puts "***** #{i.title}.#{i.op} failed! #{i.error}"
end
end
summary()
end
def summary()
fastest = nil
slowest = nil
width = 6
@items.each do |i|
next if i.duration.nil?
width = i.title.size if width < i.title.size
end
iva = @items.clone
iva.delete_if { |i| i.duration.nil? }
iva = iva.sort_by { |i| i.duration }
puts
puts "Summary:"
puts "%*s time (secs) rate (ops/sec)" % [width, 'System']
puts "#{'-' * width} ----------- --------------"
iva.each do |i|
if i.duration.nil?
else
puts "%*s %11.3f %14.3f" % [width, i.title, i.duration, i.rate ]
end
end
puts
puts "Comparison Matrix\n(performance factor, 2.0 means row is twice as fast as column)"
puts ([' ' * width] + iva.map { |i| "%*s" % [width, i.title] }).join(' ')
puts (['-' * width] + iva.map { |i| '-' * width }).join(' ')
iva.each do |i|
line = ["%*s" % [width, i.title]]
iva.each do |o|
line << "%*.2f" % [width, o.duration / i.duration]
end
puts line.join(' ')
end
puts
end
class Item
attr_accessor :title
attr_accessor :op
attr_accessor :blk
attr_accessor :duration
attr_accessor :rate
attr_accessor :error
def initialize(title, op, &blk)
@title = title
@blk = blk
@op = op
@duration = nil
@rate = nil
@error = nil
@before = nil
end
def set_before(&blk)
@before = blk
end
def run(iter, base)
begin
GC.start
@before.call unless @before.nil?
start = Time.now
iter.times { @blk.call }
@duration = Time.now - start - base
@duration = 0.0 if @duration < 0.0
@rate = iter / @duration
rescue Exception => e
@error = "#{e.class}: #{e.message}"
end
end
end # Item
end # Perf
oj-2.5.3/test/e.rb 0000755 0000041 0000041 00000000347 12263716750 013731 0 ustar www-data www-data #!/usr/bin/env ruby
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
json = %{"\xc2\xa9\xc3\x98"}
puts "original:\n#{json}"
str = Oj.load(json)
puts "out: #{str}"
oj-2.5.3/test/perf_saj.rb 0000755 0000041 0000041 00000006244 12263716750 015300 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << '.'
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'yajl'
require 'perf'
require 'json'
require 'json/ext'
require 'oj'
$verbose = false
$indent = 0
$iter = 10000
$gets = 0
$fetch = false
$write = false
$read = false
opts = OptionParser.new
opts.on("-v", "verbose") { $verbose = true }
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i }
opts.on("-f", "fetch") { $fetch = true }
opts.on("-w", "write") { $write = true }
opts.on("-r", "read") { $read = true }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
class AllSaj < Oj::Saj
def initialize()
end
def hash_start(key)
end
def hash_end(key)
end
def array_start(key)
end
def array_end(key)
end
def add_value(value, key)
end
end # AllSaj
class NoSaj < Oj::Saj
def initialize()
end
end # NoSaj
saj_handler = AllSaj.new()
no_saj = NoSaj.new()
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array
'e' => { 'one' => 1, 'two' => 2 }, # hash
'f' => nil, # nil
'g' => 12345678901234567890123456789, # big number
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
Oj.default_options = { :indent => $indent, :mode => :compat }
$json = Oj.dump($obj)
$failed = {} # key is same as String used in tests later
def capture_error(tag, orig, load_key, dump_key, &blk)
begin
obj = blk.call(orig)
raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
rescue Exception => e
$failed[tag] = "#{e.class}: #{e.message}"
end
end
# Verify that all packages dump and load correctly and return the same Object as the original.
capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) }
capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse }
if $verbose
puts "json:\n#{$json}\n"
end
puts '-' * 80
puts "Parse Performance"
perf = Perf.new()
perf.add('Oj::Saj', 'all') { Oj.saj_parse(saj_handler, $json) }
perf.add('Oj::Saj', 'none') { Oj.saj_parse(no_saj, $json) }
perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl')
perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext')
perf.run($iter)
unless $failed.empty?
puts "The following packages were not included for the reason listed"
$failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
end
oj-2.5.3/test/test_saj.rb 0000755 0000041 0000041 00000010661 12263716750 015321 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'bigdecimal'
require 'oj'
require 'pp'
$json = %{{
"array": [
{
"num" : 3,
"string": "message",
"hash" : {
"h2" : {
"a" : [ 1, 2, 3 ]
}
}
}
],
"boolean" : true
}}
class AllSaj < Oj::Saj
attr_accessor :calls
def initialize()
@calls = []
end
def hash_start(key)
@calls << [:hash_start, key]
end
def hash_end(key)
@calls << [:hash_end, key]
end
def array_start(key)
@calls << [:array_start, key]
end
def array_end(key)
@calls << [:array_end, key]
end
def add_value(value, key)
@calls << [:add_value, value, key]
end
def error(message, line, column)
@calls << [:error, message, line, column]
end
end # AllSaj
class SajTest < ::Test::Unit::TestCase
def test_nil
handler = AllSaj.new()
json = %{null}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, nil, nil]], handler.calls)
end
def test_true
handler = AllSaj.new()
json = %{true}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, true, nil]], handler.calls)
end
def test_false
handler = AllSaj.new()
json = %{false}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, false, nil]], handler.calls)
end
def test_string
handler = AllSaj.new()
json = %{"a string"}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, 'a string', nil]], handler.calls)
end
def test_fixnum
handler = AllSaj.new()
json = %{12345}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, 12345, nil]], handler.calls)
end
def test_float
handler = AllSaj.new()
json = %{12345.6789}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, 12345.6789, nil]], handler.calls)
end
def test_float_exp
handler = AllSaj.new()
json = %{12345.6789e7}
Oj.saj_parse(handler, json)
assert_equal(1, handler.calls.size)
assert_equal(:add_value, handler.calls[0][0])
assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i)
end
def test_array_empty
handler = AllSaj.new()
json = %{[]}
Oj.saj_parse(handler, json)
assert_equal([[:array_start, nil],
[:array_end, nil]], handler.calls)
end
def test_array
handler = AllSaj.new()
json = %{[true,false]}
Oj.saj_parse(handler, json)
assert_equal([[:array_start, nil],
[:add_value, true, nil],
[:add_value, false, nil],
[:array_end, nil]], handler.calls)
end
def test_hash_empty
handler = AllSaj.new()
json = %{{}}
Oj.saj_parse(handler, json)
assert_equal([[:hash_start, nil],
[:hash_end, nil]], handler.calls)
end
def test_hash
handler = AllSaj.new()
json = %{{"one":true,"two":false}}
Oj.saj_parse(handler, json)
assert_equal([[:hash_start, nil],
[:add_value, true, 'one'],
[:add_value, false, 'two'],
[:hash_end, nil]], handler.calls)
end
def test_full
handler = AllSaj.new()
Oj.saj_parse(handler, $json)
assert_equal([[:hash_start, nil],
[:array_start, 'array'],
[:hash_start, nil],
[:add_value, 3, 'num'],
[:add_value, 'message', 'string'],
[:hash_start, 'hash'],
[:hash_start, 'h2'],
[:array_start, 'a'],
[:add_value, 1, nil],
[:add_value, 2, nil],
[:add_value, 3, nil],
[:array_end, 'a'],
[:hash_end, 'h2'],
[:hash_end, 'hash'],
[:hash_end, nil],
[:array_end, 'array'],
[:add_value, true, 'boolean'],
[:hash_end, nil]], handler.calls)
end
def test_fixnum_bad
handler = AllSaj.new()
json = %{12345xyz}
Oj.saj_parse(handler, json)
assert_equal([[:add_value, 12345, nil],
[:error, "invalid format, extra characters at line 1, column 6 [saj.c:705]", 1, 6]], handler.calls)
end
end
oj-2.5.3/test/a.rb 0000755 0000041 0000041 00000002043 12263716750 013720 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'pp'
require 'oj'
require 'perf'
obj = [[1],[2],[3],[4],[5],[6],[7],[8],[9]]
obj = [[],[],[],[],[],[],[],[],[]]
obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]], # mix it up array
'e' => { 'one' => 1, 'two' => 2 }, # hash
'f' => nil, # nil
'g' => 12345678901234567890123456789, # big number
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
json = Oj.dump(obj, mode: :compat)
puts json
#pp Oj.saj_parse(nil, json)
pp Oj.t_parse(json)
if true
perf = Perf.new()
perf.add('SAJ', 'oj') { Oj.saj_parse(nil, json) }
perf.add('T', 'oj') { Oj.t_parse(json) }
perf.add('load', 'oj') { Oj.load(json) }
perf.run(10000)
end
oj-2.5.3/test/x.rb 0000755 0000041 0000041 00000004515 12263716750 013755 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
# Oj is not able to deserialize all classes that are a subclass of a Ruby
# Exception. Only exception that take one required string argument in the
# initialize() method are supported. This is an example of how to write an
# Exception subclass that supports both a single string intializer and an
# Exception as an argument. Additional optional arguments can be added as well.
#
# The reason for this restriction has to do with a design decision on the part
# of the Ruby developers. Exceptions are special Objects. They do not follow the
# rules of other Objects. Exceptions have 'mesg' and a 'bt' attribute. Note that
# these are not '@mesg' and '@bt'. They can not be set using the normal C or
# Ruby calls. The only way I have found to set the 'mesg' attribute is through
# the initializer. Unfortunately that means any subclass that provides a
# different initializer can not be automatically decoded. A way around this is
# to use a create function but this example shows an alternative.
class WrapException < StandardError
attr_reader :original
def initialize(msg_or_err)
if msg_or_err.is_a?(Exception)
super(msg_or_err.message)
@original = msg_or_err
set_backtrace(msg_or_err.backtrace)
else
super(message)
@original = nil
end
end
end
e = WrapException.new(RuntimeError.new("Something broke."))
json = Oj.dump(e, :mode => :object)
puts "original:\n#{json}"
# outputs:
# original:
# {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null}
e2 = Oj.load(json, :mode => :object)
puts "dumped, loaded, and dumped again:\n#{Oj.dump(e2, :mode => :object)}"
# outputs:
# original: {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null}
# dumped, loaded, and dumped again:
# {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null}
oj-2.5.3/test/test_fast.rb 0000755 0000041 0000041 00000025104 12263716750 015477 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'bigdecimal'
require 'oj'
$json1 = %{{
"array": [
{
"num" : 3,
"string": "message",
"hash" : {
"h2" : {
"a" : [ 1, 2, 3 ]
}
}
}
],
"boolean" : true
}}
class DocTest < ::Test::Unit::TestCase
def test_nil
json = %{null}
Oj::Doc.open(json) do |doc|
assert_equal(NilClass, doc.type)
assert_equal(nil, doc.fetch())
end
end
def test_true
json = %{true}
Oj::Doc.open(json) do |doc|
assert_equal(TrueClass, doc.type)
assert_equal(true, doc.fetch())
end
end
def test_false
json = %{false}
Oj::Doc.open(json) do |doc|
assert_equal(FalseClass, doc.type)
assert_equal(false, doc.fetch())
end
end
def test_string
json = %{"a string"}
Oj::Doc.open(json) do |doc|
assert_equal(String, doc.type)
assert_equal('a string', doc.fetch())
end
end
def test_fixnum
json = %{12345}
Oj::Doc.open(json) do |doc|
assert_equal(Fixnum, doc.type)
assert_equal(12345, doc.fetch())
end
end
def test_float
json = %{12345.6789}
Oj::Doc.open(json) do |doc|
assert_equal(Float, doc.type)
assert_equal(12345.6789, doc.fetch())
end
end
def test_float_exp
json = %{12345.6789e7}
Oj::Doc.open(json) do |doc|
assert_equal(Float, doc.type)
#assert_equal(12345.6789e7, doc.fetch())
assert_equal(12345.6789e7.to_i, doc.fetch().to_i)
end
end
def test_array_empty
json = %{[]}
Oj::Doc.open(json) do |doc|
assert_equal(Array, doc.type)
assert_equal([], doc.fetch())
end
end
def test_array
json = %{[true,false]}
Oj::Doc.open(json) do |doc|
assert_equal(Array, doc.type)
assert_equal([true, false], doc.fetch())
end
end
def test_hash_empty
json = %{{}}
Oj::Doc.open(json) do |doc|
assert_equal(Hash, doc.type)
assert_equal({}, doc.fetch())
end
end
def test_hash
json = %{{"one":true,"two":false}}
Oj::Doc.open(json) do |doc|
assert_equal(Hash, doc.type)
assert_equal({'one' => true, 'two' => false}, doc.fetch())
end
end
# move() and where?()
def test_move_hash
json = %{{"one":{"two":false}}}
Oj::Doc.open(json) do |doc|
doc.move('/one')
assert_equal('/one', doc.where?)
doc.move('/one/two')
assert_equal('/one/two', doc.where?)
end
end
def test_move_array
json = %{[1,[2,true]]}
Oj::Doc.open(json) do |doc|
doc.move('/1')
assert_equal('/1', doc.where?)
doc.move('/2/1')
assert_equal('/2/1', doc.where?)
end
end
def test_move
Oj::Doc.open($json1) do |doc|
[ '/',
'/array',
'/boolean',
'/array/1/hash/h2/a/3',
].each do |p|
doc.move(p)
assert_equal(p, doc.where?)
end
begin
doc.move('/array/x')
rescue Exception
assert_equal('/', doc.where?)
assert(true)
end
end
end
def test_move_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', Fixnum],
['/array/1/string', String],
['/array/1/hash/h2/a', Array],
['/array/1/hash/../num', Fixnum],
['/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)
assert_equal(key, doc.local_key())
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}],
].each do |path,val|
assert_equal(val, doc.fetch(path))
end
end
end
def test_home
Oj::Doc.open($json1) do |doc|
doc.move('/array/1/num')
doc.home()
assert_equal('/', doc.where?)
end
end
def test_each_value_root
Oj::Doc.open($json1) do |doc|
values = []
doc.each_value() { |v| values << v.to_s }
assert_equal(['1', '2', '3', '3', 'message', 'true'], values.sort)
end
end
def test_each_value_move
Oj::Doc.open($json1) do |doc|
doc.move('/array/1/hash')
values = []
doc.each_value() { |v| values << v.to_s }
assert_equal(['1', '2', '3'], values.sort)
end
end
def test_each_value_path
Oj::Doc.open($json1) do |doc|
values = []
doc.each_value('/array/1/hash') { |v| values << v.to_s }
assert_equal(['1', '2', '3'], values.sort)
end
end
def test_each_child_move
Oj::Doc.open($json1) do |doc|
locations = []
doc.move('/array/1/hash/h2/a')
doc.each_child() { |d| locations << d.where? }
assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations)
locations = []
doc.move('/array/1')
doc.each_child() { |d| locations << d.where? }
assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations)
end
end
def test_each_child_path
Oj::Doc.open($json1) do |doc|
locations = []
doc.each_child('/array/1/hash/h2/a') { |d| locations << d.where? }
assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations)
locations = []
doc.each_child('/array/1') { |d| locations << d.where? }
assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations)
end
end
def test_size
Oj::Doc.open('[1,2,3]') do |doc|
assert_equal(4, doc.size)
end
Oj::Doc.open('{"a":[1,2,3]}') do |doc|
assert_equal(5, doc.size)
end
end
def test_open_file
filename = 'open_file_test.json'
File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') }
Oj::Doc.open_file(filename) do |doc|
assert_equal(5, doc.size)
end
end
def test_open_close
json = %{{"a":[1,2,3]}}
doc = Oj::Doc.open(json)
assert_equal(Oj::Doc, doc.class)
assert_equal(5, doc.size)
assert_equal('/', doc.where?)
doc.move('a/1')
doc.home()
assert_equal(2, doc.fetch('/a/2'))
assert_equal(2, doc.fetch('a/2'))
doc.close()
begin
doc.home()
rescue Exception
assert(true)
end
end
def test_file_open_close
filename = 'open_file_test.json'
File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') }
doc = Oj::Doc.open_file(filename)
assert_equal(Oj::Doc, doc.class)
assert_equal(5, doc.size)
assert_equal('/', doc.where?)
doc.move('a/1')
doc.home()
assert_equal(2, doc.fetch('/a/2'))
assert_equal(2, doc.fetch('a/2'))
doc.close()
begin
doc.home()
rescue Exception
assert(true)
end
end
def test_open_no_close
json = %{{"a":[1,2,3]}}
doc = Oj::Doc.open(json)
assert_equal(Oj::Doc, doc.class)
assert_equal(5, doc.size)
assert_equal('/', doc.where?)
doc.move('a/1')
doc.home()
assert_equal(2, doc.fetch('/a/2'))
assert_equal(2, doc.fetch('a/2'))
doc = nil
GC.start
# a print statement confirms close is called
end
def test_dump
Oj::Doc.open('[1,[2,3]]') do |doc|
assert_equal('[1,[2,3]]', doc.dump())
end
Oj::Doc.open('[1,[2,3]]') do |doc|
assert_equal('[2,3]', doc.dump('/2'))
end
end
def test_each_leaf
results = Oj::Doc.open('[1,[2,3]]') do |doc|
h = {}
doc.each_leaf() { |d| h[d.where?] = d.fetch() }
h
end
assert_equal({'/1' => 1, '/2/1' => 2, '/2/2' => 3}, results)
end
def test_each_leaf_hash
results = Oj::Doc.open('{"a":{"x":2},"b":{"y":4}}') do |doc|
h = {}
doc.each_leaf() { |d| h[d.where?] = d.fetch() }
h
end
assert_equal({'/a/x' => 2, '/b/y' => 4}, results)
end
def test_comment
json = %{{
"x"/*one*/:/*two*/true,//three
"y":58/*four*/,
"z": [1,2/*five*/,
3 // six
]
}
}
results = Oj::Doc.open(json) do |doc|
h = {}
doc.each_leaf() { |d| h[d.where?] = d.fetch() }
h
end
assert_equal({'/x' => true, '/y' => 58, '/z/1' => 1, '/z/2' => 2, '/z/3' => 3}, results)
end
end # DocTest
oj-2.5.3/test/perf_object.rb 0000755 0000041 0000041 00000007465 12263716750 015777 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
$: << '.'
$: << '../lib'
$: << '../ext'
if __FILE__ == $0
if (i = ARGV.index('-I'))
x,path = ARGV.slice!(i, 2)
$: << path
end
end
require 'optparse'
require 'ox'
require 'oj'
require 'perf'
require 'sample'
require 'files'
$circular = false
$indent = 0
$allow_gc = true
do_sample = false
do_files = false
do_load = false
do_dump = false
do_read = false
do_write = false
$iter = 1000
$mult = 1
opts = OptionParser.new
opts.on("-c", "circular options") { $circular = true }
opts.on("-x", "use sample instead of files") { do_sample = true }
opts.on("-g", "no GC during parsing") { $allow_gc = false }
opts.on("-s", "load and dump as sample Ruby object") { do_sample = true }
opts.on("-f", "load and dump as files Ruby object") { do_files = true }
opts.on("-l", "load") { do_load = true }
opts.on("-d", "dump") { do_dump = true }
opts.on("-r", "read") { do_read = true }
opts.on("-w", "write") { do_write = true }
opts.on("-a", "load, dump, read and write") { do_load = true; do_dump = true; do_read = true; do_write = true }
opts.on("-i", "--iterations [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-m", "--multiply [Int]", Integer, "multiplier") { |i| $mult = i }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
$obj = nil
$xml = nil
$mars = nil
$json = nil
unless do_load || do_dump || do_read || do_write
do_load = true
do_dump = true
do_read = true
do_write = true
end
# prepare all the formats for input
if files.empty?
$obj = []
$mult.times do
$obj << (do_sample ? sample_doc(2) : files('..'))
end
$mars = Marshal.dump($obj)
$xml = Ox.dump($obj, :indent => $indent, :circular => $circular)
$json = Oj.dump($obj, :indent => $indent, :circular => $circular)
File.open('sample.xml', 'w') { |f| f.write($xml) }
File.open('sample.json', 'w') { |f| f.write($json) }
File.open('sample.marshal', 'w') { |f| f.write($mars) }
else
puts "loading and parsing #{files}\n\n"
data = files.map do |f|
$xml = File.read(f)
$obj = Ox.load($xml);
$mars = Marshal.dump($obj)
$json = Oj.dump($obj, :indent => $indent, :circular => $circular)
end
end
Oj.default_options = { :mode => :object, :indent => $indent, :circular => $circular, :allow_gc => $allow_gc }
#puts "json: #{$json.size}"
#puts "xml: #{$xml.size}"
#puts "marshal: #{$mars.size}"
if do_load
puts '-' * 80
puts "Load Performance"
perf = Perf.new()
perf.add('Oj.object', 'load') { Oj.object_load($json) }
perf.add('Ox', 'load') { Ox.load($xml, :mode => :object) }
perf.add('Marshal', 'load') { Marshal.load($mars) }
perf.run($iter)
end
if do_dump
puts '-' * 80
puts "Dump Performance"
perf = Perf.new()
perf.add('Oj', 'dump') { Oj.dump($obj) }
perf.add('Ox', 'dump') { Ox.dump($obj, :indent => $indent, :circular => $circular) }
perf.add('Marshal', 'dump') { Marshal.dump($obj) }
perf.run($iter)
end
if do_read
puts '-' * 80
puts "Read from file Performance"
perf = Perf.new()
perf.add('Oj', 'load') { Oj.load_file('sample.json') }
perf.add('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-2.5.3/test/test_object.rb 0000755 0000041 0000041 00000021712 12263716750 016011 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
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)
self.new(h['x'], h['y'])
end
end # Jeez
module One
module Two
module Three
class Deep
def initialize()
end
def eql?(o)
self.class == o.class
end
alias == eql?
def to_hash()
{'json_class' => "#{self.class.name}"}
end
def to_json(*a)
%{{"json_class":"#{self.class.name}"}}
end
def self.json_create(h)
self.new()
end
end # Deep
end # Three
end # Two
end # One
class Stuck < Struct.new(:a, :b)
end
def hash_eql(h1, h2)
return false if h1.size != h2.size
h1.keys.each do |k|
return false unless h1[k] == h2[k]
end
true
end
class ObjectJuice < ::Test::Unit::TestCase
def test_nil
dump_and_load(nil, false)
end
def test_true
dump_and_load(true, false)
end
def test_false
dump_and_load(false, false)
end
def test_fixnum
dump_and_load(0, false)
dump_and_load(12345, false)
dump_and_load(-54321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54321.012, false)
dump_and_load(2.48e16, false)
dump_and_load(2.48e100 * 1.0e10, false)
dump_and_load(-2.48e100 * 1.0e10, 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_symbol
dump_and_load(:abc, false)
dump_and_load(":abc", 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_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.new('3.14159265358979323846'), false)
end
def test_bigdecimal_load
orig = BigDecimal.new('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)
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 = 'open_file_test.json'
File.open(filename, 'w') { |f| f.write(%{{
"x":true,
"y":58,
"z": [1,2,3]
}
}) }
f = File.new(filename)
obj = Oj.object_load(f)
f.close()
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
# symbol_keys option
def test_symbol_keys
json = %{{
"x":true,
"y":58,
"z": [1,2,3]
}
}
obj = Oj.object_load(json, :symbol_keys => true)
assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj)
end
# 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_time
t = Time.now()
dump_and_load(t, false)
end
def test_time_early
t = Time.xmlschema("1954-01-05T00:00:00.123456")
dump_and_load(t, false)
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("Oj::ParseError", e.class().name)
return
end
assert(false, "*** expected an exception")
end
def test_json_struct
unless 'jruby' == RUBY_DESCRIPTION.split(' ')[0]
obj = Stuck.new(false, 7)
dump_and_load(obj, false)
end
end
def test_json_non_str_hash
obj = { 59 => "young", false => true }
dump_and_load(obj, false)
end
def test_mixed_hash_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_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_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_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
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_odd_date
dump_and_load(Date.new(2012, 6, 19), 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);
assert_equal(obj, loaded)
loaded
end
end
oj-2.5.3/test/test_writer.rb 0000755 0000041 0000041 00000011306 12263716750 016055 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
class OjWriter < ::Test::Unit::TestCase
def test_string_writer_empty_array
w = Oj::StringWriter.new(:indent => 0)
w.push_array()
w.pop()
assert_equal('[]', 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('[[],[[]],[]]', w.to_s)
end
def test_string_writer_empty_object
w = Oj::StringWriter.new(:indent => 0)
w.push_object()
w.pop()
assert_equal('{}', 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":{}}', 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]}', 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}}]', w.to_s)
end
def test_string_writer_pop_excess
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,[]]}', 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
def test_stream_writer_empty_array
output = StringIO.open("", "w+")
w = Oj::StreamWriter.new(output, :indent => 0)
w.push_array()
w.pop()
assert_equal('[]', 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()
assert_equal('{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}', output.string())
end
def test_stream_writer_mixed_file
filename = 'open_file_writer_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":{}}', content)
end
end # OjWriter
oj-2.5.3/test/test_gc.rb 0000755 0000041 0000041 00000002126 12263716750 015132 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'bigdecimal'
require 'oj'
class Goo
attr_accessor :x, :child
def initialize(x, child)
@x = x
@child = child
end
def to_hash()
{ 'json_class' => "#{self.class}", 'x' => x, 'child' => child }
end
def self.json_create(h)
GC.start
self.new(h['x'], h['child'])
end
end # Goo
class GCTest < ::Test::Unit::TestCase
# if no crash then the GC marking is working
def test_parse_compat_gc
g = Goo.new(0, nil)
100.times { |i| g = Goo.new(i, g) }
json = Oj.dump(g, :mode => :compat)
Oj.compat_load(json)
end
def test_parse_object_gc
g = Goo.new(0, nil)
100.times { |i| g = Goo.new(i, g) }
json = Oj.dump(g, :mode => :object)
Oj.object_load(json)
end
end
oj-2.5.3/test/test_strict.rb 0000755 0000041 0000041 00000014325 12263716750 016055 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
def hash_eql(h1, h2)
return false if h1.size != h2.size
h1.keys.each do |k|
return false unless h1[k] == h2[k]
end
true
end
class StrictJuice < ::Test::Unit::TestCase
def test_nil
dump_and_load(nil, false)
end
def test_true
dump_and_load(true, false)
end
def test_false
dump_and_load(false, false)
end
def test_fixnum
dump_and_load(0, false)
dump_and_load(12345, false)
dump_and_load(-54321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54321.012, false)
dump_and_load(2.48e16, false)
dump_and_load(2.48e100 * 1.0e10, false)
dump_and_load(-2.48e100 * 1.0e10, 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 => 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_bignum_object
dump_and_load(7 ** 55, false)
end
# BigDecimal
def test_bigdecimal_strict
dump_and_load(BigDecimal.new('3.14159265358979323846'), false)
end
def test_bigdecimal_load
orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true)
bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true)
assert_equal(BigDecimal, bg.class)
assert_equal(orig, bg)
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 = 'open_file_test.json'
File.open(filename, 'w') { |f| f.write(%{{
"x":true,
"y":58,
"z": [1,2,3]
}
}) }
f = File.new(filename)
obj = Oj.strict_load(f)
f.close()
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
# 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 dump_and_load(obj, trace=false)
json = Oj.dump(obj, :indent => 2)
puts json if trace
loaded = Oj.strict_load(json);
assert_equal(obj, loaded)
loaded
end
end
oj-2.5.3/test/test_mimic.rb 0000755 0000041 0000041 00000012271 12263716750 015641 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
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 to_json()
%{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}}
end
def self.json_create(h)
self.new(h['x'], h['y'])
end
end # Jam
class Mimic < ::Test::Unit::TestCase
def test0_mimic_json
assert(defined?(JSON).nil?)
Oj.mimic_JSON
assert(!defined?(JSON).nil?)
end
# dump
def test_dump_string
json = JSON.dump([1, true, nil])
assert_equal(%{[1,true,null]}, json)
end
def test_dump_io
s = StringIO.new()
json = JSON.dump([1, true, nil], s)
assert_equal(s, json)
assert_equal(%{[1,true,null]}, s.string)
end
# TBD options
# 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 # TBD
children = []
json = %{{"a":1,"b":[true,false]}}
if 'rubinius' == $ruby || 'jruby' == $ruby || '1.8.7' == RUBY_VERSION
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)
# JRuby 1.7.0 rb_yield() is broken and converts the [true, falser] array into true
unless 'jruby' == $ruby && '1.9.3' == RUBY_VERSION
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
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)
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' => 'Jam', 'x' => true, 'y' => 58}, obj)
json.gsub!('json_class', 'kson_class')
JSON.create_id = 'kson_class'
obj = JSON.parse(json, :create_additions => true)
JSON.create_id = 'json_class'
assert_equal(jam, obj)
end
def test_parse_bang
json = %{{"a":1,"b":[true,false]}}
obj = JSON.parse!(json)
assert_equal({ 'a' => 1, 'b' => [true, false]}, obj)
end
# recurse_proc
def test_recurse_proc
children = []
JSON.recurse_proc({ 'a' => 1, 'b' => [true, false]}) { |x| children << x }
# JRuby 1.7.0 rb_yield() is broken and converts the [true, falser] array into true
unless 'jruby' == $ruby && '1.9.3' == RUBY_VERSION
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
end # Mimic
oj-2.5.3/test/sample_json.rb 0000755 0000041 0000041 00000001337 12263716750 016017 0 ustar www-data www-data #!/usr/bin/env ruby -wW2
if $0 == __FILE__
$: << '.'
$: << '..'
$: << '../lib'
$: << '../ext'
end
require 'pp'
require 'oj'
def sample_json(size=3)
colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ]
container = []
size.times do |i|
box = {
'color' => colors[i % colors.size],
'fragile' => (0 == (i % 2)),
'width' => i,
'height' => i,
'depth' => i,
'weight' => i * 1.3,
'address' => {
'street' => "#{i} Main Street",
'city' => 'Sity',
'state' => nil
}
}
container << box
end
container
end
if $0 == __FILE__
File.open('sample.json', "w") { |f| f.write(Oj.dump(sample_json(3), :indent => 2)) }
end
oj-2.5.3/test/sample/ 0000755 0000041 0000041 00000000000 12263716750 014432 5 ustar www-data www-data oj-2.5.3/test/sample/oval.rb 0000644 0000041 0000041 00000000211 12263716750 015712 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-2.5.3/test/sample/hasprops.rb 0000644 0000041 0000041 00000000456 12263716750 016623 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-2.5.3/test/sample/layer.rb 0000644 0000041 0000041 00000000234 12263716750 016072 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-2.5.3/test/sample/rect.rb 0000644 0000041 0000041 00000000211 12263716750 015706 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-2.5.3/test/sample/text.rb 0000644 0000041 0000041 00000000644 12263716750 015747 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-2.5.3/test/sample/doc.rb 0000644 0000041 0000041 00000001370 12263716750 015525 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-2.5.3/test/sample/change.rb 0000644 0000041 0000041 00000000441 12263716750 016203 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-2.5.3/test/sample/line.rb 0000644 0000041 0000041 00000000500 12263716750 015701 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-2.5.3/test/sample/shape.rb 0000644 0000041 0000041 00000001105 12263716750 016054 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-2.5.3/test/sample/file.rb 0000644 0000041 0000041 00000003200 12263716750 015671 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-2.5.3/test/sample/group.rb 0000644 0000041 0000041 00000000306 12263716750 016112 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-2.5.3/test/sample/dir.rb 0000644 0000041 0000041 00000000326 12263716750 015536 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-2.5.3/test/mj.rb 0000755 0000041 0000041 00000003136 12263716750 014112 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
# $: << File.join(File.dirname(__FILE__), "../../multi_json/lib")
require 'multi_json'
require 'benchmark'
require 'yajl'
require 'json'
require 'oj'
iter = 1_000_000
iter = 100_000
json = %({"k1":"val1","k2":"val2","k3":"val3"})
obj = { k1: "val1", k2: "val2", k3: "val3" }
puts "Benchmarks for different JSON handlers with MultiJson."
puts " Ruby #{RUBY_VERSION}"
puts " #{iter} iterations"
MultiJson.engine = :oj
dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }}
et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }}
puts " Oj decode: #{dt} encode: #{et}"
MultiJson.engine = :yajl
dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }}
et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }}
puts " Yajl decode: #{dt} encode: #{et}"
MultiJson.engine = :json_gem
dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }}
et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }}
puts " Json decode: #{dt} encode: #{et}"
Oj.default_options = { :mode => :compat, :time_format => :ruby }
dt = Benchmark.realtime { iter.times { Oj.load(json) }}
et = Benchmark.realtime { iter.times { Oj.dump(obj) }}
puts "Raw Oj decode: #{dt} encode: #{et}"
ye = Yajl::Encoder.new
dt = Benchmark.realtime { iter.times { Yajl::Parser.parse(json) }}
et = Benchmark.realtime { iter.times { Yajl::Encoder.encode(obj) }}
e2 = Benchmark.realtime { iter.times { ye.encode(obj) }}
puts "Raw Yajl decode: #{dt} encode: #{et}, encoder: #{e2}"
oj-2.5.3/test/debian_test.rb 0000755 0000041 0000041 00000003424 12263716750 015765 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
def hash_eql(h1, h2)
return false if h1.size != h2.size
h1.keys.each do |k|
return false unless h1[k] == h2[k]
end
true
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
# contributed by sauliusg to fix as_json
class Orange < Jam
def initialize(x, y)
super
end
def as_json()
puts "Orange.as_json called"
{ :json_class => self.class,
:x => @x,
:y => @y }
end
def self.json_create(h)
puts "Orange.json_create"
self.new(h['x'], h['y'])
end
end
class DebJuice < ::Test::Unit::TestCase
def test_class_hash
Oj.hash_test()
end
def test_as_json_object_compat_hash_cached
Oj.default_options = { :mode => :compat, :class_cache => true }
obj = Orange.new(true, 58)
puts "dumping compat with cache"
json = Oj.dump(obj, :indent => 2)
assert(!json.nil?)
dump_and_load(obj, true)
end
def dump_and_load(obj, trace=false)
puts "dumping"
json = Oj.dump(obj, :indent => 2)
puts json if trace
puts "loading"
loaded = Oj.load(json);
puts "done"
assert_equal(obj, loaded)
loaded
end
end
oj-2.5.3/test/perf_scp.rb 0000755 0000041 0000041 00000007253 12263716750 015311 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << '.'
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'yajl'
require 'perf'
require 'json'
require 'json/ext'
require 'oj'
$verbose = false
$indent = 0
$iter = 50000
$with_bignum = false
$size = 0
opts = OptionParser.new
opts.on("-v", "verbose") { $verbose = true }
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
opts.on("-b", "with bignum") { $with_bignum = true }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
'f' => nil, # nil
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
$obj['g'] = 12345678901234567890123456789 if $with_bignum
if 0 < $size
o = $obj
$obj = []
(4 * $size).times do
$obj << o
end
end
Oj.default_options = { :indent => $indent, :mode => :compat }
$json = Oj.dump($obj)
$failed = {} # key is same as String used in tests later
class AllSaj < Oj::Saj
def initialize()
end
def hash_start(key)
end
def hash_end(key)
end
def array_start(key)
end
def array_end(key)
end
def add_value(value, key)
end
end # AllSaj
class NoSaj < Oj::Saj
def initialize()
end
end # NoSaj
class NoHandler < Oj::ScHandler
def initialize()
end
end # NoHandler
class AllHandler < Oj::ScHandler
def initialize()
end
def hash_start()
return nil
end
def hash_end()
end
def array_start()
return nil
end
def array_end()
end
def add_value(value)
end
def hash_set(h, key, value)
end
def array_append(a, value)
end
end # AllHandler
saj_handler = AllSaj.new()
no_saj = NoSaj.new()
sc_handler = AllHandler.new()
no_handler = NoHandler.new()
def capture_error(tag, orig, load_key, dump_key, &blk)
begin
obj = blk.call(orig)
raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
rescue Exception => e
$failed[tag] = "#{e.class}: #{e.message}"
end
end
# Verify that all packages dump and load correctly and return the same Object as the original.
capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) }
capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse }
if $verbose
puts "json:\n#{$json}\n"
end
puts '-' * 80
puts "Parse Performance"
perf = Perf.new()
perf.add('Oj::Saj', 'all') { Oj.saj_parse(saj_handler, $json) }
perf.add('Oj::Saj', 'none') { Oj.saj_parse(no_saj, $json) }
perf.add('Oj::Scp', 'all') { Oj.sc_parse(sc_handler, $json) }
perf.add('Oj::Scp', 'none') { Oj.sc_parse(no_handler, $json) }
perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl')
perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext')
perf.run($iter)
unless $failed.empty?
puts "The following packages were not included for the reason listed"
$failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
end
oj-2.5.3/test/test_compat.rb 0000755 0000041 0000041 00000020034 12263716750 016022 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'date'
require 'bigdecimal'
require 'oj'
$ruby = RUBY_DESCRIPTION.split(' ')[0]
$ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
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)
self.new(h['x'], h['y'])
end
end # Jeez
module One
module Two
module Three
class Deep
def initialize()
end
def eql?(o)
self.class == o.class
end
alias == eql?
def to_hash()
{'json_class' => "#{self.class.name}"}
end
def to_json(*a)
%{{"json_class":"#{self.class.name}"}}
end
def self.json_create(h)
self.new()
end
end # Deep
end # Three
end # Two
end # One
def hash_eql(h1, h2)
return false if h1.size != h2.size
h1.keys.each do |k|
return false unless h1[k] == h2[k]
end
true
end
class CompatJuice < ::Test::Unit::TestCase
def test_nil
dump_and_load(nil, false)
end
def test_true
dump_and_load(true, false)
end
def test_false
dump_and_load(false, false)
end
def test_fixnum
dump_and_load(0, false)
dump_and_load(12345, false)
dump_and_load(-54321, false)
dump_and_load(1, false)
end
def test_float
dump_and_load(0.0, false)
dump_and_load(12345.6789, false)
dump_and_load(70.35, false)
dump_and_load(-54321.012, false)
dump_and_load(2.48e16, false)
dump_and_load(2.48e100 * 1.0e10, false)
dump_and_load(-2.48e100 * 1.0e10, 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 => 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_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.compat_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_compat
dump_and_load(BigDecimal.new('3.14159265358979323846'), false)
end
def test_bigdecimal_load
orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true)
bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true)
assert_equal(BigDecimal, bg.class)
assert_equal(orig, bg)
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 = 'open_file_test.json'
File.open(filename, 'w') { |f| f.write(%{{
"x":true,
"y":58,
"z": [1,2,3]
}
}) }
f = File.new(filename)
obj = Oj.compat_load(f)
f.close()
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
# symbol_keys option
def test_symbol_keys
json = %{{
"x":true,
"y":58,
"z": [1,2,3]
}
}
obj = Oj.compat_load(json, :symbol_keys => true)
assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj)
end
# comments
def test_comment_slash
json = %{{
"x":true,//three
"y":58,
"z": [1,2,
3 // six
]}
}
obj = Oj.compat_load(json)
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
def test_comment_c
json = %{{
"x"/*one*/:/*two*/true,
"y":58,
"z": [1,2,3]}
}
obj = Oj.compat_load(json)
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
def test_comment
json = %{{
"x"/*one*/:/*two*/true,//three
"y":58/*four*/,
"z": [1,2/*five*/,
3 // six
]
}
}
obj = Oj.compat_load(json)
assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj)
end
def test_json_object_compat
obj = Jeez.new(true, 58)
dump_and_load(obj, false)
end
def test_json_module_object
obj = One::Two::Three::Deep.new()
dump_and_load(obj, false)
end
def test_json_object_create_id
expected = Jeez.new(true, 58)
json = Oj.dump(expected, :indent => 2, :mode => :compat)
obj = Oj.compat_load(json)
assert_equal(expected, obj)
end
def test_json_object_bad
json = %{{"json_class":"Junk","x":true}}
begin
Oj.compat_load(json)
rescue Exception => e
assert_equal("Oj::ParseError", e.class().name)
return
end
assert(false, "*** expected an exception")
end
def test_json_object_create_cache
expected = Jeez.new(true, 58)
json = Oj.dump(expected, :indent => 2, :mode => :compat)
obj = Oj.compat_load(json, :class_cache => true)
assert_equal(expected, obj)
obj = Oj.compat_load(json, :class_cache => false)
assert_equal(expected, obj)
end
def test_json_object_create_id_other
expected = Jeez.new(true, 58)
json = Oj.dump(expected, :indent => 2, :mode => :compat)
json.gsub!('json_class', '_class_')
obj = Oj.compat_load(json, :create_id => "_class_")
assert_equal(expected, obj)
end
def test_json_object_create_deep
expected = One::Two::Three::Deep.new()
json = Oj.dump(expected, :indent => 2, :mode => :compat)
obj = Oj.compat_load(json)
assert_equal(expected, obj)
end
def dump_and_load(obj, trace=false)
json = Oj.dump(obj, :indent => 2, :mode => :compat)
puts json if trace
loaded = Oj.compat_load(json);
assert_equal(obj, loaded)
loaded
end
end
oj-2.5.3/test/test_mimic_after.rb 0000755 0000041 0000041 00000001373 12263716750 017023 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'test/unit'
require 'stringio'
require 'oj'
require 'json'
class MimicAfter < ::Test::Unit::TestCase
def test0_mimic_json
assert(!defined?(JSON).nil?)
Oj.mimic_JSON
end
# dump
def test_dump_string
Oj.default_options= {:indent => 2} # JSON this will not change anything
json = JSON.dump([1, true, nil])
assert_equal(%{[
1,
true,
null]}, json)
end
end # MimicAfter
oj-2.5.3/test/perf_compat.rb 0000755 0000041 0000041 00000006645 12263716750 016013 0 ustar www-data www-data #!/usr/bin/env ruby -wW1
# encoding: UTF-8
$: << '.'
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'optparse'
require 'perf'
require 'oj'
$verbose = false
$indent = 0
$iter = 20000
$with_bignum = false
$with_nums = true
$size = 0
opts = OptionParser.new
opts.on("-v", "verbose") { $verbose = true }
opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
files = opts.parse(ARGV)
module One
module Two
module Three
class Empty
def initialize()
end
def eql?(o)
self.class == o.class
end
alias == eql?
def to_hash()
{'json_class' => "#{self.class.name}"}
end
def to_json(*a)
%{{"json_class":"#{self.class.name}"}}
end
def self.json_create(h)
self.new()
end
end # Empty
end # Three
end # Two
end # One
$obj = {
'a' => 'Alpha', # string
'b' => true, # boolean
'c' => 12345, # number
'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
'f' => nil, # nil
'g' => One::Two::Three::Empty.new(),
'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
}
Oj.default_options = { :indent => $indent, :mode => :compat }
if 0 < $size
s = Oj.dump($obj, :mode => :compat).size + 1
cnt = $size * 1024 / s
o = $obj
$obj = []
cnt.times do
$obj << o
end
end
$json = Oj.dump($obj, :mode => :compat)
$failed = {} # key is same as String used in tests later
def capture_error(tag, orig, load_key, dump_key, &blk)
begin
obj = blk.call(orig)
raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
rescue Exception => e
$failed[tag] = "#{e.class}: #{e.message}"
end
end
# Verify that all packages dump and load correctly and return the same Object as the original.
capture_error('Oj:compat', $obj, 'load', 'dump') { |o| Oj.compat_load(Oj.dump(o, :mode => :compat)) }
capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o|
require 'json'
require 'json/ext'
JSON.generator = JSON::Ext::Generator
JSON.parser = JSON::Ext::Parser
JSON.load(JSON.generate(o))
}
if $verbose
puts "size: #{$json.size}"
puts "json:\n#{$json}\n"
puts "Oj:compat loaded object:\n#{Oj.compat_load($json)}\n"
puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n"
end
puts '-' * 80
puts "Compat Parse Performance"
perf = Perf.new()
unless $failed.has_key?('JSON::Ext')
perf.add('JSON::Ext', 'parse') { JSON.load($json) }
perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
end
unless $failed.has_key?('Oj:compat')
perf.add('Oj:compat', 'compat_load') { Oj.compat_load($json) }
end
perf.run($iter)
puts
puts '-' * 80
puts
unless $failed.empty?
puts "The following packages were not included for the reason listed"
$failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
end
oj-2.5.3/test/sample.rb 0000644 0000041 0000041 00000002441 12263716750 014760 0 ustar www-data www-data #!/usr/bin/env ruby -wW2
if $0 == __FILE__
$: << '.'
$: << '..'
$: << '../lib'
$: << '../ext'
end
require 'pp'
require 'sample/doc'
def sample_doc(size=3)
colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ]
d = ::Sample::Doc.new('Sample')
# add some history
(0..size * 10).each do |i|
d.add_change("Changed at t+#{i}.")
end
# add some layers
(1..size).each do |i|
layer = ::Sample::Layer.new("Layer-#{i}")
(1..size).each do |j|
g = ::Sample::Group.new
(1..size).each do |k|
g2 = ::Sample::Group.new
r = ::Sample::Rect.new(j * 40 + 10.0, i * 10.0,
10.123456 / k, 10.0 / k, colors[(i + j + k) % colors.size])
r.add_prop(:part_of, layer.name)
g2 << r
g2 << ::Sample::Text.new("#{k} in #{j}", r.left, r.top, r.width, r.height)
g << g2
end
g2 = ::Sample::Group.new
(1..size).each do |k|
o = ::Sample::Oval.new(j * 40 + 12.0, i * 10.0 + 2.0,
6.0 / k, 6.0 / k, colors[(i + j + k) % colors.size])
o.add_prop(:inside, true)
g << o
end
g << g2
layer << g
end
d.layers[layer.name] = layer
end
# some properties
d.add_prop(:purpose, 'an example')
d
end
oj-2.5.3/test/bug.rb 0000755 0000041 0000041 00000000724 12263716750 014261 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: UTF-8
# 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
$: << File.join(File.dirname(__FILE__), "../lib")
$: << File.join(File.dirname(__FILE__), "../ext")
require 'oj'
obj = Oj.load_file('bug.json', :mode => :object)
puts Oj.dump(obj, :mode => :object, :indent => 0)
oj-2.5.3/LICENSE 0000644 0000041 0000041 00000002720 12263716750 013200 0 ustar www-data www-data Copyright (c) 2012, Peter Ohler
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Peter Ohler nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
oj-2.5.3/checksums.yaml.gz 0000444 0000041 0000041 00000000415 12263716750 015460 0 ustar www-data www-data _Re9R0E"c-֒ȲDEäi?n+/g=b#^>dYY2a1HUIpO_ONV8P s)0b,¹:Ԛu5D+b"&uWHa]lUpօ0`)gXXMCN>vΡ;ӋB6gYbi`&YTEnɈ@+耮6ΩӳVū(iVf_~ , oj-2.5.3/ext/ 0000755 0000041 0000041 00000000000 12263716750 012772 5 ustar www-data www-data oj-2.5.3/ext/oj/ 0000755 0000041 0000041 00000000000 12263716750 013402 5 ustar www-data www-data oj-2.5.3/ext/oj/resolve.c 0000644 0000041 0000041 00000007244 12263716750 015234 0 ustar www-data www-data /* resolve.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#if USE_PTHREAD_MUTEX
#include
#endif
#include "oj.h"
#include "err.h"
#include "parse.h"
#include "hash.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) {
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))) {
if (sizeof(class_name) - 1 < len) {
len = sizeof(class_name) - 1;
}
memcpy(class_name, name, len);
class_name[len] = '\0';
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "class %s is not defined", class_name);
}
return clas;
}
VALUE
oj_name2class(ParseInfo pi, const char *name, size_t len, int auto_define) {
VALUE clas;
VALUE *slot;
if (No == pi->options.class_cache) {
return resolve_classpath(pi, name, len, auto_define);
}
#if USE_PTHREAD_MUTEX
pthread_mutex_lock(&oj_cache_mutex);
#elif USE_RB_MUTEX
rb_mutex_lock(oj_cache_mutex);
#endif
if (Qnil == (clas = oj_class_hash_get(name, len, &slot))) {
if (Qundef != (clas = resolve_classpath(pi, name, len, auto_define))) {
*slot = clas;
}
}
#if USE_PTHREAD_MUTEX
pthread_mutex_unlock(&oj_cache_mutex);
#elif USE_RB_MUTEX
rb_mutex_unlock(oj_cache_mutex);
#endif
return clas;
}
oj-2.5.3/ext/oj/object.c 0000644 0000041 0000041 00000035766 12263716750 015035 0 ustar www-data www-data /* object.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include "oj.h"
#include "err.h"
#include "parse.h"
#include "resolve.h"
#include "hash.h"
#include "odd.h"
#include "encode.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
hash_key(ParseInfo pi, const char *key, size_t klen, char k1) {
VALUE rkey;
if (':' == k1) {
rkey = rb_str_new(key + 1, klen - 1);
rkey = oj_encode(rkey);
rkey = rb_funcall(rkey, oj_to_sym_id, 0);
} else {
rkey = rb_str_new(key, klen);
rkey = oj_encode(rkey);
if (Yes == pi->options.sym_key) {
rkey = rb_str_intern(rkey);
}
}
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 = rb_str_new(str + 1, len - 1);
rstr = oj_encode(rstr);
rstr = rb_funcall(rstr, oj_to_sym_id, 0);
} 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_str_new(str, len);
rstr = oj_encode(rstr);
}
return rstr;
}
static int
hat_cstr(ParseInfo pi, Val parent, const char *key, size_t klen, const char *str, size_t len) {
if (2 == klen) {
switch (key[1]) {
case 'o': // object
{ // name2class sets and error if the class is not found or created
VALUE clas = oj_name2class(pi, str, len, Yes == pi->options.auto_define);
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 = rb_str_new(str + 1, len - 1);
parent->val = oj_encode(parent->val);
parent->val = rb_funcall(parent->val, oj_to_sym_id, 0);
break;
case 's':
parent->val = rb_str_new(str, len);
parent->val = oj_encode(parent->val);
break;
case 'c': // class
parent->val = oj_name2class(pi, str, len, Yes == pi->options.auto_define);
break;
default:
return 0;
break;
}
return 1; // handled
}
return 0;
}
static int
hat_num(ParseInfo pi, Val parent, const char *key, size_t klen, NumInfo ni) {
if (2 == klen) {
switch (key[1]) {
case 't': // time as a float
{
int64_t nsec = ni->num * 1000000000LL / ni->div;
if (ni->neg) {
ni->i = -ni->i;
if (0 < nsec) {
ni->i--;
nsec = 1000000000LL - nsec;
}
}
#if HAS_NANO_TIME
parent->val = rb_time_nano_new(ni->i, (long)nsec);
#else
parent->val = rb_time_new(ni->i, (long)(nsec / 1000));
#endif
}
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]) {
VALUE sc;
if (0 == len) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
return 1;
}
// If struct is not defined then we let this fail and raise an exception.
sc = rb_const_get(oj_struct_class, rb_to_id(*RARRAY_PTR(value)));
// 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
// MRI >= 1.9
if (len - 1 > RSTRUCT_LEN(parent->val)) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
} else {
MEMCPY(RSTRUCT_PTR(parent->val), RARRAY_PTR(value) + 1, VALUE, len - 1);
}
#else
{
// MRI < 1.9 or Rubinius
int slen = FIX2INT(rb_funcall2(parent->val, oj_length_id, 0, 0));
int i;
if (len - 1 > slen) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data");
} else {
for (i = 0; i < slen; i++) {
rb_struct_aset(parent->val, INT2FIX(i), RARRAY_PTR(value)[i + 1]);
}
}
}
#endif
return 1;
} else if (3 <= klen && '#' == key[1]) {
volatile VALUE *a;
if (2 != len) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair");
return 1;
}
parent->val = rb_hash_new();
a = RARRAY_PTR(value);
rb_hash_aset(parent->val, *a, a[1]);
return 1;
}
}
return 0;
}
static void
copy_ivars(VALUE target, VALUE src) {
volatile VALUE vars = rb_funcall(src, oj_instance_variables_id, 0);
volatile VALUE *np = RARRAY_PTR(vars);
ID vid;
int i, cnt = (int)RARRAY_LEN(vars);
const char *attr;
for (i = cnt; 0 < i; i--, np++) {
vid = rb_to_id(*np);
attr = rb_id2name(vid);
if ('@' == *attr) {
rb_ivar_set(target, vid, rb_ivar_get(src, vid));
}
}
}
static void
set_obj_ivar(Val parent, const char *key, size_t klen, VALUE value) {
ID var_id;
ID *slot;
if ('~' == *key && Qtrue == rb_obj_is_kind_of(parent->val, rb_eException)) {
if (5 == klen && 0 == strncmp("~mesg", key, klen)) {
VALUE args[1];
VALUE prev = parent->val;
args[0] = value;
parent->val = rb_class_new_instance(1, args, rb_class_of(parent->val));
copy_ivars(parent->val, prev);
} else if (3 == klen && 0 == strncmp("~bt", key, klen)) {
rb_funcall(parent->val, rb_intern("set_backtrace"), 1, value);
}
}
#if USE_PTHREAD_MUTEX
pthread_mutex_lock(&oj_cache_mutex);
#elif USE_RB_MUTEX
rb_mutex_lock(oj_cache_mutex);
#endif
if (0 == (var_id = oj_attr_hash_get(key, klen, &slot))) {
char attr[256];
if (sizeof(attr) <= klen + 2) {
char *buf = ALLOC_N(char, klen + 2);
if ('~' == *key) {
strncpy(buf, key + 1, klen - 1);
buf[klen - 1] = '\0';
} else {
*buf = '@';
strncpy(buf + 1, key, klen);
buf[klen + 1] = '\0';
}
var_id = rb_intern(buf);
xfree(buf);
} else {
if ('~' == *key) {
strncpy(attr, key + 1, klen - 1);
attr[klen - 1] = '\0';
} else {
*attr = '@';
strncpy(attr + 1, key, klen);
attr[klen + 1] = '\0';
}
var_id = rb_intern(attr);
}
*slot = var_id;
}
#if USE_PTHREAD_MUTEX
pthread_mutex_unlock(&oj_cache_mutex);
#elif USE_RB_MUTEX
rb_mutex_unlock(oj_cache_mutex);
#endif
rb_ivar_set(parent->val, var_id, value);
}
static void
hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) {
Val parent = stack_peek(&pi->stack);
WHICH_TYPE:
switch (rb_type(parent->val)) {
case T_NIL:
parent->odd_args = 0; // make sure it is 0 in case not odd
if ('^' != *key || !hat_cstr(pi, parent, key, klen, str, len)) {
parent->val = rb_hash_new();
goto WHICH_TYPE;
}
break;
case T_HASH:
rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), str_to_value(pi, str, len, orig));
break;
case T_OBJECT:
set_obj_ivar(parent, key, klen, str_to_value(pi, str, len, orig));
break;
case T_CLASS:
if (0 == 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, str_to_value(pi, str, len, orig))) {
char buf[256];
if (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;
}
}
static void
hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) {
Val parent = stack_peek(&pi->stack);
WHICH_TYPE:
switch (rb_type(parent->val)) {
case T_NIL:
parent->odd_args = 0; // make sure it is 0 in case not odd
if ('^' != *key || !hat_num(pi, parent, key, klen, ni)) {
parent->val = rb_hash_new();
goto WHICH_TYPE;
}
break;
case T_HASH:
rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), oj_num_as_value(ni));
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 {
set_obj_ivar(parent, key, klen, oj_num_as_value(ni));
}
break;
case T_CLASS:
if (0 == 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, oj_num_as_value(ni))) {
char buf[256];
if (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;
}
}
static void
hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) {
Val parent = stack_peek(&pi->stack);
WHICH_TYPE:
switch (rb_type(parent->val)) {
case T_NIL:
parent->odd_args = 0; // make sure it is 0 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 (3 <= klen && '#' == key[1] && T_ARRAY == rb_type(value)) {
long len = RARRAY_LEN(value);
VALUE *a = RARRAY_PTR(value);
if (2 != len) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair");
return;
}
rb_hash_aset(parent->val, *a, a[1]);
} else {
rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), value);
}
break;
case T_OBJECT:
set_obj_ivar(parent, key, klen, value);
break;
case T_CLASS:
if (0 == 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 (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;
}
}
static VALUE
start_hash(ParseInfo pi) {
return Qnil;
}
static void
end_hash(struct _ParseInfo *pi) {
Val parent = stack_peek(&pi->stack);
if (Qnil == parent->val) {
parent->val = rb_hash_new();
} else if (0 != 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 = 0;
}
}
static void
array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
if (3 <= len && 0 != pi->circ_array) {
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;
}
}
}
rb_ary_push(stack_peek(&pi->stack)->val, str_to_value(pi, str, len, orig));
}
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);
}
VALUE
oj_object_parse(int argc, VALUE *argv, VALUE self) {
struct _ParseInfo pi;
pi.options = oj_default_options;
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.array_append_cstr = array_append_cstr;
return oj_pi_parse(argc, argv, &pi, 0, 0);
}
VALUE
oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len) {
struct _ParseInfo pi;
pi.options = oj_default_options;
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.array_append_cstr = array_append_cstr;
return oj_pi_parse(argc, argv, &pi, json, len);
}
oj-2.5.3/ext/oj/buf.h 0000644 0000041 0000041 00000006126 12263716750 014334 0 ustar www-data www-data /* buf.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_BUF_H__
#define __OJ_BUF_H__
#include "ruby.h"
typedef struct _Buf {
char *head;
char *end;
char *tail;
char base[1024];
} *Buf;
inline static void
buf_init(Buf buf) {
buf->head = buf->base;
buf->end = buf->base + sizeof(buf->base) - 1;
buf->tail = buf->head;
}
inline static void
buf_cleanup(Buf buf) {
if (buf->base != buf->head) {
xfree(buf->head);
}
}
inline static size_t
buf_len(Buf buf) {
return buf->tail - buf->head;
}
inline static void
buf_append_string(Buf buf, const char *s, size_t slen) {
if (buf->end <= buf->tail + slen) {
size_t len = buf->end - buf->head;
size_t toff = buf->tail - buf->head;
size_t new_len = len + slen + len / 2;
if (buf->base == buf->head) {
buf->head = ALLOC_N(char, new_len);
memcpy(buf->head, buf->base, len);
} else {
REALLOC_N(buf->head, char, new_len);
}
buf->tail = buf->head + toff;
buf->end = buf->head + new_len - 1;
}
memcpy(buf->tail, s, slen);
buf->tail += slen;
}
inline static void
buf_append(Buf buf, char c) {
if (buf->end <= buf->tail) {
size_t len = buf->end - buf->head;
size_t toff = buf->tail - buf->head;
size_t new_len = len + len / 2;
if (buf->base == buf->head) {
buf->head = ALLOC_N(char, new_len);
memcpy(buf->head, buf->base, len);
} else {
REALLOC_N(buf->head, char, new_len);
}
buf->tail = buf->head + toff;
buf->end = buf->head + new_len - 1;
}
*buf->tail = c;
buf->tail++;
*buf->tail = '\0'; // TBD temp for debugging
}
#endif /* __OJ_BUF_H__ */
oj-2.5.3/ext/oj/extconf.rb 0000644 0000041 0000041 00000004731 12263716750 015402 0 ustar www-data www-data require 'mkmf'
require 'rbconfig'
extension_name = 'oj'
dir_config(extension_name)
parts = RUBY_DESCRIPTION.split(' ')
type = parts[0]
type = type[4..-1] if type.start_with?('tcs-')
type = 'ree' if 'ruby' == type && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
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],
'HAS_RB_TIME_TIMESPEC' => (!is_windows && 'ruby' == type && ('1.9.3' == RUBY_VERSION || '2' <= version[0])) ? 1 : 0,
'HAS_ENCODING_SUPPORT' => (('ruby' == type || 'rubinius' == type) &&
(('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
'HAS_NANO_TIME' => ('ruby' == type && ('1' == version[0] && '9' == version[1]) || '2' <= version[0]) ? 1 : 0,
'HAS_IVAR_HELPERS' => ('ruby' == type && !is_windows && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
'HAS_EXCEPTION_MAGIC' => ('ruby' == type && ('1' == version[0] && '9' == version[1])) ? 0 : 1,
'HAS_PROC_WITH_BLOCK' => ('ruby' == type && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
'HAS_TOP_LEVEL_ST_H' => ('ree' == type || ('ruby' == type && '1' == version[0] && '8' == version[1])) ? 1 : 0,
'NEEDS_RATIONAL' => ('1' == version[0] && '8' == version[1]) ? 1 : 0,
'IS_WINDOWS' => is_windows ? 1 : 0,
'USE_PTHREAD_MUTEX' => is_windows ? 0 : 1,
'USE_RB_MUTEX' => (is_windows && !('1' == version[0] && '8' == version[1])) ? 1 : 0,
}
# This is a monster hack to get around issues with 1.9.3-p0 on CentOS 5.4. SO
# some reason math.h and string.h contents are not processed. Might be a
# missing #define. This is the quick and easy way around it.
if 'x86_64-linux' == RUBY_PLATFORM && '1.9.3' == RUBY_VERSION && '2011-10-30' == RUBY_RELEASE_DATE
begin
dflags['NEEDS_STPCPY'] = nil if File.read('/etc/redhat-release').include?('CentOS release 5.4')
rescue Exception
end
else
dflags['NEEDS_STPCPY'] = nil if is_windows
end
dflags.each do |k,v|
if v.nil?
$CPPFLAGS += " -D#{k}"
else
$CPPFLAGS += " -D#{k}=#{v}"
end
end
$CPPFLAGS += ' -Wall'
#puts "*** $CPPFLAGS: #{$CPPFLAGS}"
create_makefile(extension_name)
%x{make clean}
oj-2.5.3/ext/oj/resolve.h 0000644 0000041 0000041 00000003361 12263716750 015235 0 ustar www-data www-data /* resolve.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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);
#endif /* __OJ_RESOLVE_H__ */
oj-2.5.3/ext/oj/circarray.c 0000644 0000041 0000041 00000005344 12263716750 015533 0 ustar www-data www-data /* circarray.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "circarray.h"
CircArray
oj_circ_array_new() {
CircArray ca;
if (0 == (ca = ALLOC(struct _CircArray))) {
rb_raise(rb_eNoMemError, "not enough memory\n");
}
ca->objs = ca->obj_array;
ca->size = sizeof(ca->obj_array) / sizeof(VALUE);
ca->cnt = 0;
return ca;
}
void
oj_circ_array_free(CircArray ca) {
if (ca->objs != ca->obj_array) {
xfree(ca->objs);
}
xfree(ca);
}
void
oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id) {
if (0 < id && 0 != ca) {
unsigned long i;
if (ca->size < id) {
unsigned long cnt = id + 512;
if (ca->objs == ca->obj_array) {
if (0 == (ca->objs = ALLOC_N(VALUE, cnt))) {
rb_raise(rb_eNoMemError, "not enough memory\n");
}
memcpy(ca->objs, ca->obj_array, sizeof(VALUE) * ca->cnt);
} else {
REALLOC_N(ca->objs, VALUE, cnt);
}
ca->size = cnt;
}
id--;
for (i = ca->cnt; i < id; i++) {
ca->objs[i] = Qnil;
}
ca->objs[id] = obj;
if (ca->cnt <= id) {
ca->cnt = id + 1;
}
}
}
VALUE
oj_circ_array_get(CircArray ca, unsigned long id) {
VALUE obj = Qnil;
if (id <= ca->cnt && 0 != ca) {
obj = ca->objs[id - 1];
}
return obj;
}
oj-2.5.3/ext/oj/strict.c 0000644 0000041 0000041 00000011200 12263716750 015050 0 ustar www-data www-data /* strict.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include "oj.h"
#include "err.h"
#include "parse.h"
#include "encode.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 void
noop_end(struct _ParseInfo *pi) {
}
static void
add_value(ParseInfo pi, VALUE val) {
pi->stack.head->val = 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);
pi->stack.head->val = rstr;
}
static void
add_num(ParseInfo pi, NumInfo ni) {
pi->stack.head->val = oj_num_as_value(ni);
}
static VALUE
start_hash(ParseInfo pi) {
return rb_hash_new();
}
static VALUE
hash_key(ParseInfo pi, const char *key, size_t klen) {
volatile VALUE rkey = rb_str_new(key, klen);
rkey = oj_encode(rkey);
if (Yes == pi->options.sym_key) {
rkey = rb_str_intern(rkey);
}
return rkey;
}
static void
hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) {
volatile VALUE rstr = rb_str_new(str, len);
rstr = oj_encode(rstr);
rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), rstr);
}
static void
hash_set_num(struct _ParseInfo *pi, const char *key, size_t klen, NumInfo ni) {
rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), oj_num_as_value(ni));
}
static void
hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) {
rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), value);
}
static VALUE
start_array(ParseInfo pi) {
return rb_ary_new();
}
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_ary_push(stack_peek(&pi->stack)->val, rstr);
}
static void
array_append_num(ParseInfo pi, NumInfo ni) {
rb_ary_push(stack_peek(&pi->stack)->val, oj_num_as_value(ni));
}
static void
array_append_value(ParseInfo pi, VALUE value) {
rb_ary_push(stack_peek(&pi->stack)->val, value);
}
void
oj_set_strict_callbacks(ParseInfo pi) {
pi->start_hash = start_hash;
pi->end_hash = noop_end;
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 = noop_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;
pi.options = oj_default_options;
oj_set_strict_callbacks(&pi);
return oj_pi_parse(argc, argv, &pi, 0, 0);
}
VALUE
oj_strict_parse_cstr(int argc, VALUE *argv, char *json, size_t len) {
struct _ParseInfo pi;
pi.options = oj_default_options;
oj_set_strict_callbacks(&pi);
return oj_pi_parse(argc, argv, &pi, json, len);
}
oj-2.5.3/ext/oj/hash.h 0000644 0000041 0000041 00000003651 12263716750 014503 0 ustar www-data www-data /* hash.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_HASH_H__
#define __OJ_HASH_H__
#include "ruby.h"
typedef struct _Hash *Hash;
extern void oj_hash_init();
extern VALUE oj_class_hash_get(const char *key, size_t len, VALUE **slotp);
extern ID oj_attr_hash_get(const char *key, size_t len, ID **slotp);
extern void oj_hash_print();
extern char* oj_strndup(const char *s, size_t len);
#endif /* __OJ_HASH_H__ */
oj-2.5.3/ext/oj/saj.c 0000644 0000041 0000041 00000045720 12263716750 014333 0 ustar www-data www-data /* sajkey.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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 "oj.h"
#include "encode.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 XML 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
* XML 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 mispelled 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
next_white(ParseInfo pi) {
for (; 1; pi->s++) {
switch(*pi->s) {
case ' ':
case '\t':
case '\f':
case '\n':
case '\r':
case '\0':
return;
default:
break;
}
}
}
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(oj_bigdecimal_class, oj_new_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(oj_bigdecimal_class, oj_new_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;
}
inline static int
respond_to(VALUE obj, ID method) {
#ifdef JRUBY_RUBY
/* There is a bug in JRuby where rb_respond_to() returns true (1) even if
* a method is private. */
{
VALUE args[1];
*args = ID2SYM(method);
return (Qtrue == rb_funcall2(obj, rb_intern("respond_to?"), 1, args));
}
#else
return rb_respond_to(obj, method);
#endif
}
static void
sajkey_parse(VALUE handler, char *json) {
volatile VALUE obj = Qnil;
struct _ParseInfo pi;
if (0 == json) {
if (pi.has_error) {
call_error("Invalid arg, xml string can not be null", &pi, __FILE__, __LINE__);
}
raise_error("Invalid arg, xml string can not be null", json, 0);
}
/* skip UTF-8 BOM if present */
if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) {
json += 3;
}
/* initialize parse info */
pi.str = json;
pi.s = json;
#if IS_WINDOWS
pi.stack_min = (void*)((char*)&obj - (512 * 1024)); /* assume a 1M stack and give half to ruby */
#else
{
struct rlimit lim;
if (0 == getrlimit(RLIMIT_STACK, &lim)) {
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 = respond_to(handler, oj_hash_start_id);
pi.has_hash_end = respond_to(handler, oj_hash_end_id);
pi.has_array_start = respond_to(handler, oj_array_start_id);
pi.has_array_end = respond_to(handler, oj_array_end_id);
pi.has_add_value = respond_to(handler, oj_add_value_id);
pi.has_error = 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: sajkey_parse(handler, io)
*
* Parses an IO stream or file containing an JSON document. Raises an exception
* if the JSON is malformed.
* @param [Oj::SajKey] handler SajKey (responds to Oj::SajKey methods) like handler
* @param [IO|String] io IO Object to read from
*/
VALUE
oj_saj_parse(int argc, VALUE *argv, VALUE self) {
char *json = 0;
size_t len = 0;
VALUE input = argv[1];
if (argc < 2) {
rb_raise(rb_eArgError, "Wrong number of arguments to saj_parse.\n");
}
if (rb_type(input) == T_STRING) {
// the json string gets modified so make a copy of it
len = RSTRING_LEN(input) + 1;
json = ALLOC_N(char, len);
strcpy(json, StringValuePtr(input));
} else {
VALUE clas = rb_obj_class(input);
volatile VALUE s;
if (oj_stringio_class == clas) {
s = rb_funcall2(input, oj_string_id, 0, 0);
len = RSTRING_LEN(s) + 1;
json = ALLOC_N(char, len);
strcpy(json, rb_string_value_cstr((VALUE*)&s));
#ifndef JRUBY_RUBY
#if !IS_WINDOWS
// JRuby gets confused with what is the real fileno.
} else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) {
int fd = FIX2INT(s);
ssize_t cnt;
len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
json = ALLOC_N(char, len + 1);
if (0 >= (cnt = read(fd, json, len)) || cnt != (ssize_t)len) {
rb_raise(rb_eIOError, "failed to read from IO Object.");
}
json[len] = '\0';
#endif
#endif
} else if (rb_respond_to(input, oj_read_id)) {
s = rb_funcall2(input, oj_read_id, 0, 0);
len = RSTRING_LEN(s) + 1;
json = ALLOC_N(char, len);
strcpy(json, rb_string_value_cstr((VALUE*)&s));
} else {
rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object.");
}
}
sajkey_parse(*argv, json);
xfree(json);
return Qnil;
}
oj-2.5.3/ext/oj/fast.c 0000644 0000041 0000041 00000120302 12263716750 014501 0 ustar www-data www-data /* fast.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !IS_WINDOWS
#include // for getrlimit() on linux
#endif
#include
#include
#include
#include
#include
#include "oj.h"
#include "encode.h"
// maximum to allocate on the stack, arbitrary limit
#define SMALL_XML 65536
#define MAX_STACK 100
//#define BATCH_SIZE (4096 / sizeof(struct _Leaf) - 1)
#define BATCH_SIZE 100
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, int given, int allocated);
static void each_leaf(Doc doc, VALUE self);
static int move_step(Doc doc, const char *path, int loc);
static Leaf get_doc_leaf(Doc doc, const char *path);
static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path);
static void each_value(Doc doc, Leaf leaf);
static void doc_init(Doc doc);
static void doc_free(Doc doc);
static VALUE doc_open(VALUE clas, VALUE str);
static VALUE doc_open_file(VALUE clas, VALUE filename);
static VALUE doc_where(VALUE self);
static VALUE doc_local_key(VALUE self);
static VALUE doc_home(VALUE self);
static VALUE doc_type(int argc, VALUE *argv, VALUE self);
static VALUE doc_fetch(int argc, VALUE *argv, VALUE self);
static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self);
static VALUE doc_move(VALUE self, VALUE str);
static VALUE doc_each_child(int argc, VALUE *argv, VALUE self);
static VALUE doc_each_value(int argc, VALUE *argv, VALUE self);
static VALUE doc_dump(int argc, VALUE *argv, VALUE self);
static VALUE doc_size(VALUE self);
VALUE oj_doc_class = 0;
// This is only for CentOS 5.4 with Ruby 1.9.3-p0.
#ifdef NEEDS_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 void
next_white(ParseInfo pi) {
for (; 1; pi->s++) {
switch(*pi->s) {
case ' ':
case '\t':
case '\f':
case '\n':
case '\r':
case '\0':
return;
default:
break;
}
}
}
inline static char*
ulong_fill(char *s, size_t num) {
char buf[32];
char *b = buf + sizeof(buf) - 1;
*b-- = '\0';
for (; 0 < num; num /= 10, b--) {
*b = (num % 10) + '0';
}
b++;
if ('\0' == *b) {
b--;
*b = '0';
}
for (; '\0' != *b; b++, s++) {
*s = *b;
}
return s;
}
inline static void
leaf_init(Leaf leaf, int type) {
leaf->next = 0;
leaf->type = type;
leaf->parent_type = T_NONE;
switch (type) {
case T_ARRAY:
case T_HASH:
leaf->elements = 0;
leaf->value_type = COL_VAL;
break;
case T_NIL:
leaf->value = Qnil;
leaf->value_type = RUBY_VAL;
break;
case T_TRUE:
leaf->value = Qtrue;
leaf->value_type = RUBY_VAL;
break;
case T_FALSE:
leaf->value = Qfalse;
leaf->value_type = RUBY_VAL;
break;
case T_FIXNUM:
case T_FLOAT:
case T_STRING:
default:
leaf->value_type = STR_VAL;
break;
}
}
inline static Leaf
leaf_new(Doc doc, int type) {
Leaf leaf;
if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) {
Batch b = ALLOC(struct _Batch);
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->type) {
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->type);
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 = LONG2NUM(n);
}
leaf->value_type = RUBY_VAL;
}
#ifdef JRUBY_RUBY
static void
leaf_float_value(Leaf leaf) {
char *s = leaf->str;
int64_t n = 0;
long a = 0;
long div = 1;
long e = 0;
int neg = 0;
int eneg = 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 {
double d;
if ('.' == *s) {
s++;
for (; '0' <= *s && *s <= '9'; s++) {
a = a * 10 + (*s - '0');
div *= 10;
}
}
if ('e' == *s || 'E' == *s) {
s++;
if ('-' == *s) {
s++;
eneg = 1;
} else if ('+' == *s) {
s++;
}
for (; '0' <= *s && *s <= '9'; s++) {
e = e * 10 + (*s - '0');
}
}
d = (double)n + (double)a / (double)div;
if (neg) {
d = -d;
}
if (0 != e) {
if (eneg) {
e = -e;
}
d *= pow(10.0, e);
}
leaf->value = rb_float_new(d);
}
leaf->value_type = RUBY_VAL;
}
#else
static void
leaf_float_value(Leaf leaf) {
leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1));
leaf->value_type = RUBY_VAL;
}
#endif
static VALUE
leaf_array_value(Doc doc, Leaf leaf) {
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) {
VALUE h = rb_hash_new();
if (0 != leaf->elements) {
Leaf first = leaf->elements->next;
Leaf e = first;
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 char
read_hex(ParseInfo pi, char *h) {
uint8_t b = 0;
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;
raise_error("invalid hex character", pi->str, pi->s);
}
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;
raise_error("invalid hex character", pi->str, pi->s);
}
return (char)b;
}
/* 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':
h++;
*t = read_hex(pi, h);
h += 2;
if ('\0' != *t) {
t++;
}
*t = read_hex(pi, h);
h++;
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) {
doc->where = doc->where_path;
*doc->where = 0;
doc->data = 0;
doc->self = Qundef;
doc->size = 0;
doc->json = 0;
doc->batches = &doc->batch0;
doc->batch0.next = 0;
doc->batch0.next_avail = 0;
}
static void
doc_free(Doc doc) {
if (0 != doc) {
Batch b;
while (0 != (b = doc->batches)) {
doc->batches = doc->batches->next;
if (&doc->batch0 != b) {
xfree(b);
}
}
//xfree(f);
}
}
static VALUE
protect_open_proc(VALUE x) {
ParseInfo pi = (ParseInfo)x;
pi->doc->data = read_next(pi); // parse
*pi->doc->where = pi->doc->data;
pi->doc->where = pi->doc->where_path;
if (rb_block_given_p()) {
return rb_yield(pi->doc->self); // caller processing
}
return Qnil;
}
static void
free_doc_cb(void *x) {
Doc doc = (Doc)x;
if (0 != doc) {
xfree(doc->json);
doc_free(doc);
}
}
static VALUE
parse_json(VALUE clas, char *json, int given, int allocated) {
struct _ParseInfo pi;
VALUE result = Qnil;
Doc doc;
int ex = 0;
if (given) {
doc = ALLOCA_N(struct _Doc, 1);
} else {
doc = 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
pi.stack_min = (void*)((char*)&pi - (512 * 1024)); // assume a 1M stack and give half to ruby
#else
{
struct rlimit lim;
if (0 == getrlimit(RLIMIT_STACK, &lim)) {
pi.stack_min = (void*)((char*)&lim - (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
// last arg is free func void* func(void*)
doc->self = rb_data_object_alloc(clas, doc, 0, free_doc_cb);
rb_gc_register_address(&doc->self);
doc->json = json;
DATA_PTR(doc->self) = doc;
result = rb_protect(protect_open_proc, (VALUE)&pi, &ex);
if (given || 0 != ex) {
rb_gc_unregister_address(&doc->self);
DATA_PTR(doc->self) = 0;
doc_free(pi.doc);
if (allocated && 0 != ex) { // will jump so caller will not free
xfree(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);
lp = stack + cnt;
}
return get_leaf(stack, lp, path);
}
return leaf;
}
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 (COL_VAL == leaf->value_type && 0 != leaf->elements) {
Leaf first = leaf->elements->next;
Leaf e = first;
int type = leaf->type;
leaf = 0;
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 = strchr(path, '/');
int klen;
if (0 == slash) {
klen = (int)strlen(key);
path += klen;
} else {
klen = (int)(slash - key);
path += klen + 1;
}
do {
if (0 == strncmp(key, e->key, klen) && '\0' == 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->type) {
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->type) {
const char *key = path;
const char *slash = strchr(path, '/');
int klen;
if (0 == slash) {
klen = (int)strlen(key);
path += klen;
} else {
klen = (int)(slash - key);
path += klen + 1;
}
do {
if (0 == strncmp(key, e->key, klen) && '\0' == 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
/* call-seq: 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;
VALUE obj;
int given = rb_block_given_p();
int allocate;
Check_Type(str, T_STRING);
len = RSTRING_LEN(str) + 1;
allocate = (SMALL_XML < len || !given);
if (allocate) {
json = ALLOC_N(char, len);
} else {
json = ALLOCA_N(char, len);
}
memcpy(json, StringValuePtr(str), len);
obj = parse_json(clas, json, given, allocate);
if (given && allocate) {
xfree(json);
}
return obj;
}
/* call-seq: 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;
VALUE obj;
int given = rb_block_given_p();
int allocate;
Check_Type(filename, T_STRING);
path = StringValuePtr(filename);
if (0 == (f = fopen(path, "r"))) {
rb_raise(rb_eIOError, "%s", strerror(errno));
}
fseek(f, 0, SEEK_END);
len = ftell(f);
allocate = (SMALL_XML < len || !given);
if (allocate) {
json = ALLOC_N(char, len + 1);
} else {
json = ALLOCA_N(char, len + 1);
}
fseek(f, 0, SEEK_SET);
if (len != fread(json, 1, len, f)) {
fclose(f);
rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")),
"Failed to read %lu bytes from %s.", (unsigned long)len, path);
}
fclose(f);
json[len] = '\0';
obj = parse_json(clas, json, given, allocate);
if (given && allocate) {
xfree(json);
}
return obj;
}
/* Document-method: parse
* @see Oj::Doc.open
*/
/* call-seq: 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 += 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 = stpcpy(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);
}
}
/* call-seq: 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;
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;
}
/* call-seq: 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;
}
/* call-seq: type(path=nil) => Class
*
* Returns the Class of the data value at the location identified by the path
* or the current location if the path is nil or not provided. This method
* does not create the Ruby Object at the location specified so the overhead
* is low.
* @param [String] path path to the location to get the type of if provided
* @example
* Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array
* Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum
*/
static VALUE
doc_type(int argc, VALUE *argv, VALUE self) {
Doc doc = self_doc(self);
Leaf leaf;
const char *path = 0;
VALUE type = Qnil;
if (1 <= argc) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
}
if (0 != (leaf = get_doc_leaf(doc, path))) {
switch (leaf->type) {
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_cFixnum; 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;
}
/* call-seq: fetch(path=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.
* @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;
VALUE val = Qnil;
const char *path = 0;
doc = self_doc(self);
if (1 <= argc) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
if (2 == argc) {
val = argv[1];
}
}
if (0 != (leaf = get_doc_leaf(doc, path))) {
val = leaf_value(doc, leaf);
}
return val;
}
/* call-seq: 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);
}
if (1 <= argc) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
if ('/' == *path) {
doc->where = doc->where_path;
path++;
}
if (0 != move_step(doc, path, 1)) {
if (0 < wlen) {
memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
}
return Qnil;
}
}
each_leaf(doc, self);
if (0 < wlen) {
memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
}
}
return Qnil;
}
/* call-seq: move(path) => nil
*
* Moves the document marker to the path specified. The path can an absolute
* path or a relative path.
* @param [String] path path to the location to move to
* @example
* Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=> "/one/2"
*/
static VALUE
doc_move(VALUE self, VALUE str) {
Doc doc = self_doc(self);
const char *path;
int loc;
Check_Type(str, T_STRING);
path = StringValuePtr(str);
if ('/' == *path) {
doc->where = doc->where_path;
path++;
}
if (0 != (loc = move_step(doc, path, 1))) {
rb_raise(rb_eArgError, "Failed to locate element %d of the path %s.", loc, path);
}
return Qnil;
}
/* call-seq: 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 chilren of
* @yieldparam [Doc] Doc at the child location
* @example
* Oj::Doc.open('[3,[2,1]]') { |doc|
* result = []
* doc.each_value('/2') { |doc| result << doc.where? }
* result
* }
* #=> ["/2/1", "/2/2"]
*/
static VALUE
doc_each_child(int argc, VALUE *argv, VALUE self) {
if (rb_block_given_p()) {
Leaf save_path[MAX_STACK];
Doc doc = self_doc(self);
const char *path = 0;
size_t wlen;
wlen = doc->where - doc->where_path;
if (0 < wlen) {
memcpy(save_path, doc->where_path, sizeof(Leaf) * wlen);
}
if (1 <= argc) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
if ('/' == *path) {
doc->where = doc->where_path;
path++;
}
if (0 != move_step(doc, path, 1)) {
if (0 < wlen) {
memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
}
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);
}
}
return Qnil;
}
/* call-seq: each_value(path=nil) { |val| ... } => nil
*
* Yields to the provided block for each leaf value in the identified location
* of the JSON document. The parameter passed to the block on yield is the
* value of the leaf. Only those leaves below the element specified by the
* path parameter are processed.
* @param [String] path if provided it identified the top of the branch to process the leaf values of
* @yieldparam [Object] val each leaf value
* @example
* Oj::Doc.open('[3,[2,1]]') { |doc|
* result = []
* doc.each_value() { |v| result << v }
* result
* }
* #=> [3, 2, 1]
*
* Oj::Doc.open('[3,[2,1]]') { |doc|
* result = []
* doc.each_value('/2') { |v| result << v }
* result
* }
* #=> [2, 1]
*/
static VALUE
doc_each_value(int argc, VALUE *argv, VALUE self) {
if (rb_block_given_p()) {
Doc doc = self_doc(self);
const char *path = 0;
Leaf leaf;
if (1 <= argc) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
}
if (0 != (leaf = get_doc_leaf(doc, path))) {
each_value(doc, leaf);
}
}
return Qnil;
}
/* call-seq: dump(path=nil) => String
*
* Dumps the document or nodes to a new JSON document. It uses the default
* options for generating the JSON.
* @param [String] path if provided it identified the top of the branch to dump to JSON
* @param [String] filename if provided it is the filename to write the output to
* @example
* Oj::Doc.open('[3,[2,1]]') { |doc|
* doc.dump('/2')
* }
* #=> "[2,1]"
*/
static VALUE
doc_dump(int argc, VALUE *argv, VALUE self) {
Doc doc = self_doc(self);
Leaf leaf;
const char *path = 0;
const char *filename = 0;
if (1 <= argc) {
if (Qnil != *argv) {
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
}
if (2 <= argc) {
Check_Type(argv[1], T_STRING);
filename = StringValuePtr(argv[1]);
}
}
if (0 != (leaf = get_doc_leaf(doc, path))) {
VALUE rjson;
if (0 == filename) {
char buf[4096];
struct _Out out;
out.buf = buf;
out.end = buf + sizeof(buf) - 10;
out.allocated = 0;
oj_dump_leaf_to_json(leaf, &oj_default_options, &out);
rjson = rb_str_new2(out.buf);
if (out.allocated) {
xfree(out.buf);
}
} else {
oj_write_leaf_to_file(leaf, filename, &oj_default_options);
rjson = Qnil;
}
return rjson;
}
return Qnil;
}
/* call-seq: size() => Fixnum
*
* Returns the number of nodes in the JSON document where a node is any one of
* the basic JSON components.
* @return Returns the size of the JSON document.
* @example
* Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4
*/
static VALUE
doc_size(VALUE self) {
return ULONG2NUM(((Doc)DATA_PTR(self))->size);
}
/* call-seq: close() => nil
*
* Closes an open document. No further calls to the document will be valid
* after closing.
* @example
* doc = Oj::Doc.open('[1,2,3]')
* doc.size() #=> 4
* doc.close()
*/
static VALUE
doc_close(VALUE self) {
Doc doc = self_doc(self);
rb_gc_unregister_address(&doc->self);
DATA_PTR(doc->self) = 0;
if (0 != doc) {
xfree(doc->json);
doc_free(doc);
}
return Qnil;
}
#if 0
// hack to keep the doc generator happy
Oj = rb_define_module("Oj");
#endif
/* Document-class: Oj::Doc
*
* The Doc class is used to parse and navigate a JSON document. The model it
* employs is that of a document that while open can be navigated and values
* extracted. Once the document is closed the document can not longer be
* accessed. This allows the parsing and data extraction to be extremely fast
* compared to other JSON parses.
*
* An Oj::Doc class is not created directly but the _open()_ class method is
* used to open a document and the yield parameter to the block of the #open()
* call is the Doc instance. The Doc instance can be moved across, up, and
* down the JSON document. At each element the data associated with the
* element can be extracted. It is also possible to just provide a path to the
* data to be extracted and retrieve the data in that manner.
*
* For many of the methods a path is used to describe the location of an
* element. Paths follow a subset of the XPath syntax. The slash ('/')
* character is the separator. Each step in the path identifies the next
* branch to take through the document. A JSON object will expect a key string
* while an array will expect a positive index. A .. step indicates a move up
* the JSON document.
*
* @example
* json = %{[
* {
* "one" : 1,
* "two" : 2
* },
* {
* "three" : 3,
* "four" : 4
* }
* ]}
* # move and get value
* Oj::Doc.open(json) do |doc|
* doc.move('/1/two')
* # doc location is now at the 'two' element of the hash that is the first element of the array.
* doc.fetch()
* end
* #=> 2
*
* # Now try again using a path to Oj::Doc.fetch() directly and not using a block.
* doc = Oj::Doc.open(json)
* doc.fetch('/2/three') #=> 3
* doc.close()
*/
void
oj_init_doc() {
oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject);
rb_define_singleton_method(oj_doc_class, "open", doc_open, 1);
rb_define_singleton_method(oj_doc_class, "open_file", doc_open_file, 1);
rb_define_singleton_method(oj_doc_class, "parse", doc_open, 1);
rb_define_method(oj_doc_class, "where?", doc_where, 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, "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);
}
oj-2.5.3/ext/oj/err.h 0000644 0000041 0000041 00000004731 12263716750 014350 0 ustar www-data www-data /* err.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_ERR_H__
#define __OJ_ERR_H__
#include "ruby.h"
#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);
extern void oj_err_raise(Err e);
// TBD remove
#define raise_error(msg, json, current) _oj_raise_error(msg, json, current, __FILE__, __LINE__)
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-2.5.3/ext/oj/hash.c 0000644 0000041 0000041 00000010165 12263716750 014474 0 ustar www-data www-data /* hash.c
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "hash.h"
#include
#define HASH_MASK 0x000003FF
#define HASH_SLOT_CNT 1024
typedef struct _KeyVal {
struct _KeyVal *next;
const char *key;
size_t len;
VALUE val;
} *KeyVal;
struct _Hash {
struct _KeyVal slots[HASH_SLOT_CNT];
};
struct _Hash class_hash;
struct _Hash intern_hash;
// almost the Murmur hash algorithm
#define M 0x5bd1e995
#define C1 0xCC9E2D51
#define C2 0x1B873593
#define N 0xE6546B64
static uint32_t
hash_calc(const uint8_t *key, size_t len) {
const uint8_t *end = key + len;
const uint8_t *endless = key + (len / 4 * 4);
uint32_t h = (uint32_t)len;
uint32_t k;
while (key < endless) {
k = (uint32_t)*key++;
k |= (uint32_t)*key++ << 8;
k |= (uint32_t)*key++ << 16;
k |= (uint32_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;
}
void
oj_hash_init() {
memset(class_hash.slots, 0, sizeof(class_hash.slots));
memset(intern_hash.slots, 0, sizeof(intern_hash.slots));
}
// if slotp is 0 then just lookup
static VALUE
hash_get(Hash hash, const char *key, size_t len, VALUE **slotp, VALUE def_value) {
uint32_t h = hash_calc((const uint8_t*)key, len) & HASH_MASK;
KeyVal bucket = hash->slots + h;
if (0 != bucket->key) {
KeyVal b;
for (b = bucket; 0 != b; b = b->next) {
if (len == b->len && 0 == strncmp(b->key, key, len)) {
*slotp = &b->val;
return b->val;
}
bucket = b;
}
}
if (0 != slotp) {
if (0 != bucket->key) {
KeyVal b = ALLOC(struct _KeyVal);
b->next = 0;
bucket->next = b;
bucket = b;
}
bucket->key = oj_strndup(key, len);
bucket->len = len;
bucket->val = def_value;
*slotp = &bucket->val;
}
return def_value;
}
void
oj_hash_print() {
int i;
KeyVal b;
for (i = 0; i < HASH_SLOT_CNT; i++) {
printf("%4d:", i);
for (b = class_hash.slots + i; 0 != b && 0 != b->key; b = b->next) {
printf(" %s", b->key);
}
printf("\n");
}
}
VALUE
oj_class_hash_get(const char *key, size_t len, VALUE **slotp) {
return hash_get(&class_hash, key, len, slotp, Qnil);
}
ID
oj_attr_hash_get(const char *key, size_t len, ID **slotp) {
return (ID)hash_get(&intern_hash, key, len, (VALUE**)slotp, 0);
}
char*
oj_strndup(const char *s, size_t len) {
char *d = ALLOC_N(char, len + 1);
memcpy(d, s, len);
d[len] = '\0';
return d;
}
oj-2.5.3/ext/oj/err.c 0000644 0000041 0000041 00000005331 12263716750 014340 0 ustar www-data www-data /* err.c
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include "err.h"
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-2.5.3/ext/oj/cache8.c 0000644 0000041 0000041 00000004165 12263716750 014707 0 ustar www-data www-data
#include
#include
#include
#include
#include
#include "ruby.h"
#include "cache8.h"
#define BITS 4
#define MASK 0x000000000000000FULL
#define SLOT_CNT 16
#define DEPTH 16
typedef union {
struct _Cache8 *child;
slot_t value;
} Bucket;
struct _Cache8 {
Bucket buckets[SLOT_CNT];
};
static void cache8_delete(Cache8 cache, int depth);
static void slot_print(Cache8 cache, sid_t key, unsigned int depth);
void
oj_cache8_new(Cache8 *cache) {
Bucket *b;
int i;
*cache = ALLOC(struct _Cache8);
for (i = SLOT_CNT, b = (*cache)->buckets; 0 < i; i--, b++) {
b->value = 0;
}
}
void
oj_cache8_delete(Cache8 cache) {
cache8_delete(cache, 0);
}
static void
cache8_delete(Cache8 cache, int depth) {
Bucket *b;
unsigned int i;
for (i = 0, b = cache->buckets; i < SLOT_CNT; i++, b++) {
if (0 != b->child) {
if (DEPTH - 1 != depth) {
cache8_delete(b->child, depth + 1);
}
}
}
xfree(cache);
}
slot_t
oj_cache8_get(Cache8 cache, sid_t key, slot_t **slot) {
Bucket *b;
int i;
sid_t k8 = (sid_t)key;
sid_t k;
for (i = 64 - BITS; 0 < i; i -= BITS) {
k = (k8 >> i) & MASK;
b = cache->buckets + k;
if (0 == b->child) {
oj_cache8_new(&b->child);
}
cache = b->child;
}
*slot = &(cache->buckets + (k8 & MASK))->value;
return **slot;
}
void
oj_cache8_print(Cache8 cache) {
/*printf("-------------------------------------------\n"); */
slot_print(cache, 0, 0);
}
static void
slot_print(Cache8 c, sid_t key, unsigned int depth) {
Bucket *b;
unsigned int i;
sid_t k8 = (sid_t)key;
sid_t k;
for (i = 0, b = c->buckets; i < SLOT_CNT; i++, b++) {
if (0 != b->child) {
k = (k8 << BITS) | i;
/*printf("*** key: 0x%016llx depth: %u i: %u\n", k, depth, i); */
if (DEPTH - 1 == depth) {
#if IS_WINDOWS
printf("0x%016lx: %4lu\n", (long unsigned int)k, (long unsigned int)b->value);
#else
printf("0x%016llx: %4llu\n", (long long unsigned int)k, (long long unsigned int)b->value);
#endif
} else {
slot_print(b->child, k, depth + 1);
}
}
}
}
oj-2.5.3/ext/oj/val_stack.h 0000644 0000041 0000041 00000011164 12263716750 015525 0 ustar www-data www-data /* val_stack.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_VAL_STACK_H__
#define __OJ_VAL_STACK_H__
#include "ruby.h"
#include "odd.h"
#include
#if USE_PTHREAD_MUTEX
#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 {
VALUE val;
const char *key;
union {
const char *classname;
OddArgs odd_args;
};
uint16_t klen;
uint16_t clen;
char next; // ValNext
char k1; // first original character in the key
} *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
#if USE_PTHREAD_MUTEX
pthread_mutex_t mutex;
#elif USE_RB_MUTEX
VALUE mutex;
#endif
} *ValStack;
extern VALUE oj_stack_init(ValStack stack);
inline static int
stack_empty(ValStack stack) {
return (stack->head == stack->tail);
}
inline static void
stack_cleanup(ValStack stack) {
if (stack->base != stack->head) {
xfree(stack->head);
}
}
inline static void
stack_push(ValStack stack, VALUE val, ValNext next) {
if (stack->end <= stack->tail) {
size_t len = stack->end - stack->head;
size_t toff = stack->tail - stack->head;
Val head = stack->head;
// A realloc can trigger a GC so make sure it happens outside the lock
// but lock before changing pointers.
if (stack->base == stack->head) {
head = ALLOC_N(struct _Val, len + STACK_INC);
memcpy(head, stack->base, sizeof(struct _Val) * len);
} else {
REALLOC_N(head, struct _Val, len + STACK_INC);
}
#if USE_PTHREAD_MUTEX
pthread_mutex_lock(&stack->mutex);
#elif USE_RB_MUTEX
rb_mutex_lock(stack->mutex);
#endif
stack->head = head;
stack->tail = stack->head + toff;
stack->end = stack->head + len + STACK_INC;
#if USE_PTHREAD_MUTEX
pthread_mutex_unlock(&stack->mutex);
#elif USE_RB_MUTEX
rb_mutex_unlock(stack->mutex);
#endif
}
stack->tail->val = val;
stack->tail->next = next;
stack->tail->classname = 0;
stack->tail->key = 0;
stack->tail->clen = 0;
stack->tail->klen = 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-2.5.3/ext/oj/hash_test.c 0000644 0000041 0000041 00000040054 12263716750 015533 0 ustar www-data www-data /* hash_test.c
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// if windows, comment out the whole file. It's only a performance test.
#ifndef _WIN32
#include
#include
#include "hash.h"
#include
/* Define printf formats for standard types, like PRIu64 for uint64_t. */
#define __STDC_FORMAT_MACROS
#include
typedef struct _StrLen {
const char *str;
size_t len;
} *StrLen;
static struct _StrLen data[] = {
{ "Gem::Version", 12 },
{ "TracePoint", 10 },
{ "Complex::compatible", 19 },
{ "Complex", 7 },
{ "Rational::compatible", 20 },
{ "Rational", 8 },
{ "FiberError", 10 },
{ "Fiber", 5 },
{ "ThreadError", 11 },
{ "Mutex", 5 },
{ "ThreadGroup", 11 },
{ "RubyVM::InstructionSequence", 27 },
{ "Thread::Backtrace::Location", 27 },
{ "Thread::Backtrace", 17 },
{ "Thread", 6 },
{ "RubyVM::Env", 11 },
{ "RubyVM", 6 },
{ "Enumerator::Yielder", 19 },
{ "Enumerator::Generator", 21 },
{ "StopIteration", 13 },
{ "Enumerator::Lazy", 16 },
{ "Enumerator", 10 },
{ "ObjectSpace::WeakMap", 20 },
{ "Math::DomainError", 17 },
{ "Binding", 7 },
{ "UnboundMethod", 13 },
{ "Method", 6 },
{ "SystemStackError", 16 },
{ "LocalJumpError", 14 },
{ "Proc", 4 },
{ "Struct::Tms", 11 },
{ "Process::Status", 15 },
{ "Random", 6 },
{ "Time", 4 },
{ "Dir", 3 },
{ "File::Stat", 10 },
{ "File", 4 },
{ "ARGF.class", 10 },
{ "IO", 2 },
{ "EOFError", 8 },
{ "IOError", 7 },
{ "Range", 5 },
{ "Encoding::Converter", 19 },
{ "Encoding::ConverterNotFoundError", 32 },
{ "Encoding::InvalidByteSequenceError", 34 },
{ "Encoding::UndefinedConversionError", 34 },
{ "MatchData", 9 },
{ "Regexp", 6 },
{ "RegexpError", 11 },
{ "Struct", 6 },
{ "Hash", 4 },
{ "Array", 5 },
{ "Errno::ERPCMISMATCH", 19 },
{ "Errno::EPROGUNAVAIL", 19 },
{ "Errno::EPROGMISMATCH", 20 },
{ "Errno::EPROCUNAVAIL", 19 },
{ "Errno::EPROCLIM", 15 },
{ "Errno::ENOTSUP", 14 },
{ "Errno::ENOATTR", 14 },
{ "Errno::ENEEDAUTH", 16 },
{ "Errno::EFTYPE", 13 },
{ "Errno::EBADRPC", 14 },
{ "Errno::EAUTH", 12 },
{ "Errno::EOWNERDEAD", 17 },
{ "Errno::ENOTRECOVERABLE", 22 },
{ "Errno::ECANCELED", 16 },
{ "Errno::EDQUOT", 13 },
{ "Errno::ESTALE", 13 },
{ "Errno::EINPROGRESS", 18 },
{ "Errno::EALREADY", 15 },
{ "Errno::EHOSTUNREACH", 19 },
{ "Errno::EHOSTDOWN", 16 },
{ "Errno::ECONNREFUSED", 19 },
{ "Errno::ETIMEDOUT", 16 },
{ "Errno::ETOOMANYREFS", 19 },
{ "Errno::ESHUTDOWN", 16 },
{ "Errno::ENOTCONN", 15 },
{ "Errno::EISCONN", 14 },
{ "Errno::ENOBUFS", 14 },
{ "Errno::ECONNRESET", 17 },
{ "Errno::ECONNABORTED", 19 },
{ "Errno::ENETRESET", 16 },
{ "Errno::ENETUNREACH", 18 },
{ "Errno::ENETDOWN", 15 },
{ "Errno::EADDRNOTAVAIL", 20 },
{ "Errno::EADDRINUSE", 17 },
{ "Errno::EAFNOSUPPORT", 19 },
{ "Errno::EPFNOSUPPORT", 19 },
{ "Errno::EOPNOTSUPP", 17 },
{ "Errno::ESOCKTNOSUPPORT", 22 },
{ "Errno::EPROTONOSUPPORT", 22 },
{ "Errno::ENOPROTOOPT", 18 },
{ "Errno::EPROTOTYPE", 17 },
{ "Errno::EMSGSIZE", 15 },
{ "Errno::EDESTADDRREQ", 19 },
{ "Errno::ENOTSOCK", 15 },
{ "Errno::EUSERS", 13 },
{ "Errno::EILSEQ", 13 },
{ "Errno::EOVERFLOW", 16 },
{ "Errno::EBADMSG", 14 },
{ "Errno::EMULTIHOP", 16 },
{ "Errno::EPROTO", 13 },
{ "Errno::ENOLINK", 14 },
{ "Errno::EREMOTE", 14 },
{ "Errno::ENOSR", 12 },
{ "Errno::ETIME", 12 },
{ "Errno::ENODATA", 14 },
{ "Errno::ENOSTR", 13 },
{ "Errno::EIDRM", 12 },
{ "Errno::ENOMSG", 13 },
{ "Errno::ELOOP", 12 },
{ "Errno::ENOTEMPTY", 16 },
{ "Errno::ENOSYS", 13 },
{ "Errno::ENOLCK", 13 },
{ "Errno::ENAMETOOLONG", 19 },
{ "Errno::EDEADLK", 14 },
{ "Errno::ERANGE", 13 },
{ "Errno::EDOM", 11 },
{ "Errno::EPIPE", 12 },
{ "Errno::EMLINK", 13 },
{ "Errno::EROFS", 12 },
{ "Errno::ESPIPE", 13 },
{ "Errno::ENOSPC", 13 },
{ "Errno::EFBIG", 12 },
{ "Errno::ETXTBSY", 14 },
{ "Errno::ENOTTY", 13 },
{ "Errno::EMFILE", 13 },
{ "Errno::ENFILE", 13 },
{ "Errno::EINVAL", 13 },
{ "Errno::EISDIR", 13 },
{ "Errno::ENOTDIR", 14 },
{ "Errno::ENODEV", 13 },
{ "Errno::EXDEV", 12 },
{ "Errno::EEXIST", 13 },
{ "Errno::EBUSY", 12 },
{ "Errno::ENOTBLK", 14 },
{ "Errno::EFAULT", 13 },
{ "Errno::EACCES", 13 },
{ "Errno::ENOMEM", 13 },
{ "Errno::EAGAIN", 13 },
{ "Errno::ECHILD", 13 },
{ "Errno::EBADF", 12 },
{ "Errno::ENOEXEC", 14 },
{ "Errno::E2BIG", 12 },
{ "Errno::ENXIO", 12 },
{ "Errno::EIO", 10 },
{ "Errno::EINTR", 12 },
{ "Errno::ESRCH", 12 },
{ "Errno::ENOENT", 13 },
{ "Errno::EPERM", 12 },
{ "Errno::NOERROR", 14 },
{ "Bignum", 6 },
{ "Float", 5 },
{ "Fixnum", 6 },
{ "Integer", 7 },
{ "Numeric", 7 },
{ "FloatDomainError", 16 },
{ "ZeroDivisionError", 17 },
{ "SystemCallError", 15 },
{ "Encoding::CompatibilityError", 28 },
{ "EncodingError", 13 },
{ "NoMemoryError", 13 },
{ "SecurityError", 13 },
{ "RuntimeError", 12 },
{ "NoMethodError", 13 },
{ "NameError::message", 18 },
{ "NameError", 9 },
{ "NotImplementedError", 19 },
{ "LoadError", 9 },
{ "SyntaxError", 11 },
{ "ScriptError", 11 },
{ "RangeError", 10 },
{ "KeyError", 8 },
{ "IndexError", 10 },
{ "ArgumentError", 13 },
{ "TypeError", 9 },
{ "StandardError", 13 },
{ "Interrupt", 9 },
{ "SignalException", 15 },
{ "SystemExit", 10 },
{ "Exception", 9 },
{ "Symbol", 6 },
{ "String", 6 },
{ "Encoding", 8 },
{ "FalseClass", 10 },
{ "TrueClass", 9 },
{ "Data", 4 },
{ "NilClass", 8 },
{ "Class", 5 },
{ "Module", 6 },
{ "Object", 6 },
{ "BasicObject", 11 },
{ "Gem::Requirement::BadRequirementError", 37 },
{ "Gem::Requirement", 16 },
{ "Gem::SourceFetchProblem", 23 },
{ "Gem::PlatformMismatch", 21 },
{ "Gem::ErrorReason", 16 },
{ "Gem::LoadError", 14 },
{ "Gem::RemoteSourceException", 26 },
{ "Gem::RemoteInstallationSkipped", 30 },
{ "Gem::RemoteInstallationCancelled", 32 },
{ "Gem::RemoteError", 16 },
{ "Gem::OperationNotSupportedError", 31 },
{ "Gem::InvalidSpecificationException", 34 },
{ "Gem::InstallError", 17 },
{ "Gem::Specification", 18 },
{ "Date", 4 },
{ "Gem::Platform", 13 },
{ "Gem::SpecificGemNotFoundException", 33 },
{ "Gem::GemNotFoundException", 25 },
{ "Gem::FormatException", 20 },
{ "Gem::FilePermissionError", 24 },
{ "Gem::EndOfYAMLException", 23 },
{ "Gem::DocumentError", 18 },
{ "Gem::GemNotInHomeException", 26 },
{ "Gem::DependencyRemovalException", 31 },
{ "Gem::DependencyError", 20 },
{ "Gem::CommandLineError", 21 },
{ "Gem::Exception", 14 },
{ "IRB::SLex", 9 },
{ "IRB::Notifier::NoMsgNotifier", 28 },
{ "IRB::Notifier::LeveledNotifier", 30 },
{ "IRB::Notifier::CompositeNotifier", 32 },
{ "IRB::Notifier::AbstractNotifier", 31 },
{ "IRB::Notifier::ErrUnrecognizedLevel", 35 },
{ "IRB::Notifier::ErrUndefinedNotifier", 35 },
{ "IRB::StdioOutputMethod", 22 },
{ "IRB::OutputMethod::NotImplementedError", 38 },
{ "IRB::OutputMethod", 17 },
{ "IRB::IllegalRCGenerator", 23 },
{ "IRB::UndefinedPromptMode", 24 },
{ "IRB::CantChangeBinding", 22 },
{ "IRB::CantShiftToMultiIrbMode", 28 },
{ "IRB::NoSuchJob", 14 },
{ "IRB::IrbSwitchedToCurrentThread", 31 },
{ "IRB::IrbAlreadyDead", 19 },
{ "IRB::IllegalParameter", 21 },
{ "IRB::CantReturnToNormalMode", 27 },
{ "IRB::NotImplementedError", 24 },
{ "IRB::UnrecognizedSwitch", 23 },
{ "IRB::Irb", 8 },
{ "IRB::Abort", 10 },
{ "IRB::Locale", 11 },
{ "IRB::SLex::ErrNodeNothing", 25 },
{ "IRB::SLex::ErrNodeAlreadyExists", 31 },
{ "RubyLex", 7 },
{ "IRB::SLex::Node", 15 },
{ "Gem::SystemExitException", 24 },
{ "Gem::VerificationError", 22 },
{ "RubyToken::TkError", 18 },
{ "RubyToken::TkUnknownChar", 24 },
{ "RubyToken::TkOPASGN", 19 },
{ "RubyToken::TkOp", 15 },
{ "RubyToken::TkVal", 16 },
{ "RubyToken::TkId", 15 },
{ "RubyToken::TkNode", 17 },
{ "RubyToken::Token", 16 },
{ "RubyToken::TkUNDEF", 18 },
{ "RubyToken::TkDEF", 16 },
{ "RubyToken::TkMODULE", 19 },
{ "RubyToken::TkCLASS", 18 },
{ "RubyToken::TkWHILE", 18 },
{ "RubyToken::TkWHEN", 17 },
{ "RubyToken::TkCASE", 17 },
{ "RubyToken::TkELSE", 17 },
{ "RubyToken::TkELSIF", 18 },
{ "RubyToken::TkTHEN", 17 },
{ "RubyToken::TkUNLESS", 19 },
{ "RubyToken::TkIF", 15 },
{ "RubyToken::TkEND", 16 },
{ "RubyToken::TkENSURE", 19 },
{ "RubyToken::TkRESCUE", 19 },
{ "RubyToken::TkBEGIN", 18 },
{ "RubyToken::TkDO", 15 },
{ "RubyToken::TkIN", 15 },
{ "RubyToken::TkRETRY", 18 },
{ "RubyToken::TkREDO", 17 },
{ "RubyToken::TkNEXT", 17 },
{ "RubyToken::TkBREAK", 18 },
{ "RubyToken::TkFOR", 16 },
{ "RubyToken::TkUNTIL", 18 },
{ "RubyToken::TkTRUE", 17 },
{ "RubyToken::TkNIL", 16 },
{ "RubyToken::TkSELF", 17 },
{ "RubyToken::TkSUPER", 18 },
{ "RubyToken::TkYIELD", 18 },
{ "RubyToken::TkRETURN", 19 },
{ "RubyToken::TkAND", 16 },
{ "RubyToken::TkFALSE", 18 },
{ "RubyToken::TkUNLESS_MOD", 23 },
{ "RubyToken::TkIF_MOD", 19 },
{ "RubyToken::TkNOT", 16 },
{ "RubyToken::TkOR", 15 },
{ "RubyToken::TkALIAS", 18 },
{ "RubyToken::TkUNTIL_MOD", 22 },
{ "RubyToken::TkWHILE_MOD", 22 },
{ "RubyToken::TkGVAR", 17 },
{ "RubyToken::TkFID", 16 },
{ "RubyToken::TkIDENTIFIER", 23 },
{ "RubyToken::Tk__FILE__", 21 },
{ "RubyToken::Tk__LINE__", 21 },
{ "RubyToken::TklEND", 17 },
{ "RubyToken::TklBEGIN", 19 },
{ "RubyToken::TkDEFINED", 20 },
{ "RubyToken::TkDREGEXP", 20 },
{ "RubyToken::TkDXSTRING", 21 },
{ "RubyToken::TkDSTRING", 20 },
{ "RubyToken::TkSYMBOL", 19 },
{ "RubyToken::TkREGEXP", 19 },
{ "RubyToken::TkXSTRING", 20 },
{ "RubyToken::TkSTRING", 19 },
{ "RubyToken::TkFLOAT", 18 },
{ "RubyToken::TkINTEGER", 20 },
{ "RubyToken::TkCONSTANT", 21 },
{ "RubyToken::TkIVAR", 17 },
{ "RubyToken::TkCVAR", 17 },
{ "RubyToken::TkNEQ", 16 },
{ "RubyToken::TkEQQ", 16 },
{ "RubyToken::TkEQ", 15 },
{ "RubyToken::TkCMP", 16 },
{ "RubyToken::TkPOW", 16 },
{ "RubyToken::TkUMINUS", 19 },
{ "RubyToken::TkUPLUS", 18 },
{ "Exception2MessageMapper::ErrNotRegisteredException", 50 },
{ "RubyToken::TkBACK_REF", 21 },
{ "RubyToken::TkNTH_REF", 20 },
{ "RubyToken::TkLSHFT", 18 },
{ "RubyToken::TkASET", 17 },
{ "RubyToken::TkAREF", 17 },
{ "RubyToken::TkDOT3", 17 },
{ "RubyToken::TkDOT2", 17 },
{ "RubyToken::TkNMATCH", 19 },
{ "RubyToken::TkMATCH", 18 },
{ "RubyToken::TkOROP", 17 },
{ "RubyToken::TkANDOP", 18 },
{ "RubyToken::TkLEQ", 16 },
{ "RubyToken::TkGEQ", 16 },
{ "RubyToken::TkAMPER", 18 },
{ "RubyToken::TkSTAR", 17 },
{ "RubyToken::TkfLBRACE", 20 },
{ "RubyToken::TkfLBRACK", 20 },
{ "RubyToken::TkfLPAREN", 20 },
{ "RubyToken::TkCOLON", 18 },
{ "RubyToken::TkQUESTION", 21 },
{ "RubyToken::TkASSOC", 18 },
{ "RubyToken::TkCOLON3", 19 },
{ "RubyToken::TkCOLON2", 19 },
{ "RubyToken::TkRSHFT", 18 },
{ "RubyToken::TkBITAND", 19 },
{ "RubyToken::TkBITXOR", 19 },
{ "RubyToken::TkBITOR", 18 },
{ "RubyToken::TkMOD", 16 },
{ "RubyToken::TkDIV", 16 },
{ "RubyToken::TkMULT", 17 },
{ "RubyToken::TkMINUS", 18 },
{ "RubyToken::TkPLUS", 17 },
{ "RubyToken::TkLT", 15 },
{ "RubyToken::TkGT", 15 },
{ "RubyToken::TkSYMBEG", 19 },
{ "IRB::DefaultEncodings", 21 },
{ "RubyToken::TkRPAREN", 19 },
{ "RubyToken::TkLBRACE", 19 },
{ "RubyToken::TkLBRACK", 19 },
{ "RubyToken::TkLPAREN", 19 },
{ "RubyToken::TkDOT", 16 },
{ "RubyToken::TkASSIGN", 19 },
{ "RubyToken::TkBACKQUOTE", 22 },
{ "RubyToken::TkNOTOP", 18 },
{ "RubyToken::TkBITNOT", 19 },
{ "RubyToken::TkDOLLAR", 19 },
{ "RubyToken::TkAT", 15 },
{ "RubyToken::TkBACKSLASH", 22 },
{ "RubyToken::TkEND_OF_SCRIPT", 26 },
{ "RubyToken::TkNL", 15 },
{ "RubyToken::TkSPACE", 18 },
{ "RubyToken::TkRD_COMMENT", 23 },
{ "RubyToken::TkCOMMENT", 20 },
{ "RubyToken::TkSEMICOLON", 22 },
{ "RubyToken::TkCOMMA", 18 },
{ "RubyToken::TkRBRACE", 19 },
{ "RubyToken::TkRBRACK", 19 },
{ "RubyLex::TerminateLineInput", 27 },
{ "RubyLex::SyntaxError", 20 },
{ "RubyLex::TkReading2TokenDuplicateError", 38 },
{ "RubyLex::TkSymbol2TokenNoKey", 28 },
{ "RubyLex::TkReading2TokenNoKey", 29 },
{ "RubyLex::AlreadyDefinedToken", 28 },
{ "IRB::FileInputMethod", 20 },
{ "IRB::StdioInputMethod", 21 },
{ "IRB::InputMethod", 16 },
{ "IRB::ReadlineInputMethod", 24 },
{ "IRB::Context", 12 },
{ "IRB::Inspector", 14 },
{ "IRB::WorkSpace", 14 },
{ 0, 0 }
};
static uint64_t
micro_time() {
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec;
}
static void
perf() {
StrLen d;
VALUE v;
VALUE *slot = 0;
uint64_t dt, start;
int i, iter = 1000000;
int dataCnt = sizeof(data) / sizeof(*data);
oj_hash_init();
start = micro_time();
for (i = iter; 0 < i; i--) {
for (d = data; 0 != d->str; d++) {
v = oj_class_hash_get(d->str, d->len, &slot);
if (Qundef == v) {
if (0 != slot) {
v = ID2SYM(rb_intern(d->str));
*slot = v;
}
}
}
}
dt = micro_time() - start;
#if IS_WINDOWS
printf("%d iterations took %ld msecs, %ld gets/msec\n", iter, (long)(dt / 1000), (long)(iter * dataCnt / (dt / 1000)));
#else
printf("%d iterations took %"PRIu64" msecs, %ld gets/msec\n", iter, dt / 1000, (long)(iter * dataCnt / (dt / 1000)));
#endif
}
void
oj_hash_test() {
StrLen d;
VALUE v;
VALUE *slot = 0;;
oj_hash_init();
for (d = data; 0 != d->str; d++) {
char *s = oj_strndup(d->str, d->len);
v = oj_class_hash_get(d->str, d->len, &slot);
if (Qnil == v) {
if (0 == slot) {
printf("*** failed to get a slot for %s\n", s);
} else {
v = ID2SYM(rb_intern(d->str));
*slot = v;
}
} else {
VALUE rs = rb_funcall2(v, rb_intern("to_s"), 0, 0);
printf("*** get on '%s' returned '%s' (%s)\n", s, StringValuePtr(rs), rb_class2name(rb_obj_class(v)));
}
/*oj_hash_print(c);*/
}
printf("*** ---------- hash table ------------\n");
oj_hash_print();
perf();
}
#endif
oj-2.5.3/ext/oj/odd.c 0000644 0000041 0000041 00000010132 12263716750 014311 0 ustar www-data www-data /* odd.c
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "odd.h"
struct _Odd odds[5]; // bump up if new Odd classes are added
static void
set_class(Odd odd, const char *classname) {
const char **np;
ID *idp;
odd->classname = classname;
odd->clen = strlen(classname);
odd->clas = rb_const_get(rb_cObject, rb_intern(classname));
odd->create_obj = odd->clas;
odd->create_op = rb_intern("new");
for (np = odd->attr_names, idp = odd->attrs; 0 != *np; np++, idp++) {
*idp = rb_intern(*np);
}
*idp = 0;
}
void
oj_odd_init() {
Odd odd;
const char **np;
odd = odds;
// Rational
np = odd->attr_names;
*np++ = "numerator";
*np++ = "denominator";
*np = 0;
set_class(odd, "Rational");
odd->create_obj = rb_cObject;
odd->create_op = rb_intern("Rational");
odd->attr_cnt = 2;
// Date
odd++;
np = odd->attr_names;
*np++ = "year";
*np++ = "month";
*np++ = "day";
*np++ = "start";
*np++ = 0;
set_class(odd, "Date");
odd->attr_cnt = 4;
// DateTime
odd++;
np = odd->attr_names;
*np++ = "year";
*np++ = "month";
*np++ = "day";
*np++ = "hour";
*np++ = "min";
*np++ = "sec";
*np++ = "offset";
*np++ = "start";
*np++ = 0;
set_class(odd, "DateTime");
odd->attr_cnt = 8;
// Range
odd++;
np = odd->attr_names;
*np++ = "begin";
*np++ = "end";
*np++ = "exclude_end?";
*np++ = 0;
set_class(odd, "Range");
odd->attr_cnt = 3;
// The end. bump up the size of odds if a new class is added.
odd++;
odd->clas = Qundef;
}
Odd
oj_get_odd(VALUE clas) {
Odd odd = odds;
for (; Qundef != odd->clas; odd++) {
if (clas == odd->clas) {
return odd;
}
}
return 0;
}
Odd
oj_get_oddc(const char *classname, size_t len) {
Odd odd = odds;
for (; Qundef != odd->clas; odd++) {
if (len == odd->clen && 0 == strncmp(classname, odd->classname, len)) {
return odd;
}
}
return 0;
}
OddArgs
oj_odd_alloc_args(Odd odd) {
OddArgs oa = ALLOC_N(struct _OddArgs, 1);
VALUE *a;
int i;
oa->odd = odd;
for (i = odd->attr_cnt, a = oa->args; 0 < i; i--, a++) {
*a = Qnil;
}
return oa;
}
void
oj_odd_free(OddArgs args) {
xfree(args);
}
int
oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value) {
const char **np;
VALUE *vp;
int i;
for (i = args->odd->attr_cnt, np = args->odd->attr_names, vp = args->args; 0 < i; i--, np++, vp++) {
if (0 == strncmp(key, *np, klen) && '\0' == *((*np) + klen)) {
*vp = value;
return 0;
}
}
return -1;
}
oj-2.5.3/ext/oj/scp.c 0000644 0000041 0000041 00000020765 12263716750 014345 0 ustar www-data www-data /* scp.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include "oj.h"
#include "parse.h"
#include "encode.h"
inline static int
respond_to(VALUE obj, ID method) {
#ifdef JRUBY_RUBY
/* There is a bug in JRuby where rb_respond_to() returns true (1) even if
* a method is private. */
{
VALUE args[1];
*args = ID2SYM(method);
return (Qtrue == rb_funcall2(obj, rb_intern("respond_to?"), 1, args));
}
#else
return rb_respond_to(obj, method);
#endif
}
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 void
noop_hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) {
}
static void
noop_hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) {
}
static void
noop_hash_set_value(ParseInfo pi, const char *key, size_t klen, 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((VALUE)pi->cbc, 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((VALUE)pi->cbc, oj_add_value_id, 1, rstr);
}
static void
add_num(ParseInfo pi, NumInfo ni) {
rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, oj_num_as_value(ni));
}
static VALUE
start_hash(ParseInfo pi) {
return rb_funcall((VALUE)pi->cbc, oj_hash_start_id, 0);
}
static void
end_hash(ParseInfo pi) {
rb_funcall((VALUE)pi->cbc, oj_hash_end_id, 0);
}
static VALUE
start_array(ParseInfo pi) {
return rb_funcall((VALUE)pi->cbc, oj_array_start_id, 0);
}
static void
end_array(ParseInfo pi) {
rb_funcall((VALUE)pi->cbc, oj_array_end_id, 0);
}
static VALUE
hash_key(ParseInfo pi, const char *key, size_t klen) {
volatile VALUE rkey = rb_str_new(key, klen);
rkey = oj_encode(rkey);
if (Yes == pi->options.sym_key) {
rkey = rb_str_intern(rkey);
}
return rkey;
}
static void
hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) {
volatile VALUE rstr = rb_str_new(str, len);
rstr = oj_encode(rstr);
rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), rstr);
}
static void
hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) {
rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), oj_num_as_value(ni));
}
static void
hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) {
rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), 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((VALUE)pi->cbc, oj_array_append_id, 2, stack_peek(&pi->stack)->val, rstr);
}
static void
array_append_num(ParseInfo pi, NumInfo ni) {
rb_funcall((VALUE)pi->cbc, 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((VALUE)pi->cbc, oj_array_append_id, 2, stack_peek(&pi->stack)->val, value);
}
static VALUE
protect_parse(VALUE pip) {
oj_parse2((ParseInfo)pip);
return Qnil;
}
VALUE
oj_sc_parse(int argc, VALUE *argv, VALUE self) {
struct _ParseInfo pi;
char *buf = 0;
VALUE input;
VALUE handler;
volatile VALUE wrapped_stack;
int line = 0;
if (argc < 2) {
rb_raise(rb_eArgError, "Wrong number of arguments to saj_parse.");
}
handler = *argv;;
input = argv[1];
pi.json = 0;
pi.options = oj_default_options;
if (3 == argc) {
oj_parse_options(argv[2], &pi.options);
}
pi.cbc = (void*)handler;
pi.start_hash = respond_to(handler, oj_hash_start_id) ? start_hash : noop_start;
pi.end_hash = respond_to(handler, oj_hash_end_id) ? end_hash : noop_end;
pi.start_array = respond_to(handler, oj_array_start_id) ? start_array : noop_start;
pi.end_array = respond_to(handler, oj_array_end_id) ? end_array : noop_end;
if (respond_to(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 (respond_to(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 (respond_to(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;
}
if (rb_type(input) == T_STRING) {
pi.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);
pi.json = StringValuePtr(input);
#ifndef JRUBY_RUBY
#if !IS_WINDOWS
// JRuby gets confused with what is the real fileno.
} else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) {
int fd = FIX2INT(s);
ssize_t cnt;
size_t len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
buf = ALLOC_N(char, len + 1);
pi.json = buf;
if (0 >= (cnt = read(fd, (char*)pi.json, len)) || cnt != (ssize_t)len) {
if (0 != buf) {
xfree(buf);
}
rb_raise(rb_eIOError, "failed to read from IO Object.");
}
((char*)pi.json)[len] = '\0';
#endif
#endif
} else if (rb_respond_to(input, oj_read_id)) {
s = rb_funcall2(input, oj_read_id, 0, 0);
pi.json = rb_string_value_cstr((VALUE*)&s);
} else {
rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object.");
}
}
wrapped_stack = oj_stack_init(&pi.stack);
rb_protect(protect_parse, (VALUE)&pi, &line);
DATA_PTR(wrapped_stack) = NULL;
if (0 != buf) {
xfree(buf);
}
stack_cleanup(&pi.stack);
if (0 != line) {
rb_jump_tag(line);
}
if (err_has(&pi.err)) {
oj_err_raise(&pi.err);
}
return Qnil;
}
oj-2.5.3/ext/oj/val_stack.c 0000644 0000041 0000041 00000006217 12263716750 015523 0 ustar www-data www-data /* val_stack.c
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "oj.h"
#include "val_stack.h"
static void
mark(void *ptr) {
ValStack stack = (ValStack)ptr;
Val v;
if (0 == ptr) {
return;
}
#if USE_PTHREAD_MUTEX
pthread_mutex_lock(&stack->mutex);
#elif USE_RB_MUTEX
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 USE_PTHREAD_MUTEX
pthread_mutex_unlock(&stack->mutex);
#elif USE_RB_MUTEX
rb_mutex_unlock(stack->mutex);
#endif
}
VALUE
oj_stack_init(ValStack stack) {
#if USE_PTHREAD_MUTEX
pthread_mutex_init(&stack->mutex, 0);
#elif USE_RB_MUTEX
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 = 0;
stack->head->classname = 0;
stack->head->klen = 0;
stack->head->clen = 0;
stack->head->next = NEXT_NONE;
return Data_Wrap_Struct(oj_cstack_class, mark, 0, stack);
}
const char*
oj_stack_next_string(ValNext n) {
switch (n) {
case NEXT_ARRAY_NEW: return "array element or close";
case NEXT_ARRAY_ELEMENT: return "array element";
case NEXT_ARRAY_COMMA: return "comma";
case NEXT_HASH_NEW: return "hash pair or close";
case NEXT_HASH_KEY: return "hash key";
case NEXT_HASH_COLON: return "colon";
case NEXT_HASH_VALUE: return "hash value";
case NEXT_HASH_COMMA: return "comma";
case NEXT_NONE: break;
default: break;
}
return "nothing";
}
oj-2.5.3/ext/oj/compat.c 0000644 0000041 0000041 00000006773 12263716750 015046 0 ustar www-data www-data /* compat.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include "oj.h"
#include "err.h"
#include "parse.h"
#include "resolve.h"
#include "hash.h"
#include "encode.h"
static void
hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) {
Val parent = stack_peek(&pi->stack);
if (0 != pi->options.create_id &&
*pi->options.create_id == *key &&
pi->options.create_id_len == klen &&
0 == strncmp(pi->options.create_id, key, klen)) {
if (str < pi->json || pi->cur < str) {
parent->classname = oj_strndup(str, len);
} else {
parent->classname = str;
}
parent->clen = len;
} else {
volatile VALUE rstr = rb_str_new(str, len);
volatile VALUE rkey = rb_str_new(key, klen);
rstr = oj_encode(rstr);
rkey = oj_encode(rkey);
if (Yes == pi->options.sym_key) {
rkey = rb_str_intern(rkey);
}
rb_hash_aset(parent->val, rkey, rstr);
}
}
static void
end_hash(struct _ParseInfo *pi) {
Val parent = stack_peek(&pi->stack);
if (0 != parent->classname) {
VALUE clas;
clas = oj_name2class(pi, parent->classname, parent->clen, 0);
if (Qundef != clas) { // else an error
parent->val = rb_funcall(clas, oj_json_create_id, 1, parent->val);
}
if (parent->classname < pi->json || pi->end < parent->classname) {
xfree((char*)parent->classname);
parent->classname = 0;
}
}
}
void
oj_set_compat_callbacks(ParseInfo pi) {
oj_set_strict_callbacks(pi);
pi->end_hash = end_hash;
pi->hash_set_cstr = hash_set_cstr;
}
VALUE
oj_compat_parse(int argc, VALUE *argv, VALUE self) {
struct _ParseInfo pi;
pi.options = oj_default_options;
oj_set_compat_callbacks(&pi);
return oj_pi_parse(argc, argv, &pi, 0, 0);
}
VALUE
oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len) {
struct _ParseInfo pi;
pi.options = oj_default_options;
oj_set_strict_callbacks(&pi);
pi.end_hash = end_hash;
pi.hash_set_cstr = hash_set_cstr;
return oj_pi_parse(argc, argv, &pi, json, len);
}
oj-2.5.3/ext/oj/circarray.h 0000644 0000041 0000041 00000004065 12263716750 015537 0 ustar www-data www-data /* circarray.h
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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__ */
oj-2.5.3/ext/oj/cache8.h 0000644 0000041 0000041 00000003702 12263716750 014710 0 ustar www-data www-data /* cache8.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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-2.5.3/ext/oj/dump.c 0000644 0000041 0000041 00000151724 12263716750 014525 0 ustar www-data www-data /* dump.c
* Copyright (c) 2012, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#if !IS_WINDOWS
#include
#endif
#include
#include
#include
#include
#include
#include
#include "oj.h"
#include "cache8.h"
#include "odd.h"
#if !HAS_ENCODING_SUPPORT || defined(RUBINIUS_RUBY)
#define rb_eEncodingError rb_eException
#endif
// Workaround in case INFINITY is not defined in math.h or if the OS is CentOS
#define OJ_INFINITY (1.0/0.0)
// Extra padding at end of buffer.
#define BUFFER_EXTRA 10
typedef unsigned long ulong;
static void raise_strict(VALUE obj);
static void dump_val(VALUE obj, int depth, Out out);
static void dump_nil(Out out);
static void dump_true(Out out);
static void dump_false(Out out);
static void dump_fixnum(VALUE obj, Out out);
static void dump_bignum(VALUE obj, Out out);
static void dump_float(VALUE obj, Out out);
static void dump_raw(const char *str, size_t cnt, Out out);
static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out);
static void dump_hex(uint8_t c, Out out);
static void dump_str_comp(VALUE obj, Out out);
static void dump_str_obj(VALUE obj, Out out);
static void dump_sym_comp(VALUE obj, Out out);
static void dump_sym_obj(VALUE obj, Out out);
static void dump_class_comp(VALUE obj, Out out);
static void dump_class_obj(VALUE obj, Out out);
static void dump_array(VALUE obj, int depth, Out out);
static int hash_cb_strict(VALUE key, VALUE value, Out out);
static int hash_cb_compat(VALUE key, VALUE value, Out out);
static int hash_cb_object(VALUE key, VALUE value, Out out);
static void dump_hash(VALUE obj, int depth, int mode, Out out);
static void dump_time(VALUE obj, Out out);
static void dump_ruby_time(VALUE obj, Out out);
static void dump_xml_time(VALUE obj, Out out);
static void dump_data_strict(VALUE obj, Out out);
static void dump_data_null(VALUE obj, Out out);
static void dump_data_comp(VALUE obj, int depth, Out out);
static void dump_data_obj(VALUE obj, int depth, Out out);
static void dump_obj_comp(VALUE obj, int depth, Out out);
static void dump_obj_obj(VALUE obj, int depth, Out out);
static void dump_struct_comp(VALUE obj, int depth, Out out);
static void dump_struct_obj(VALUE obj, int depth, Out out);
#if HAS_IVAR_HELPERS
static int dump_attr_cb(ID key, VALUE value, Out out);
#endif
static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out);
static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out);
static void grow(Out out, size_t len);
static size_t hibit_friendly_size(const uint8_t *str, size_t len);
static size_t xss_friendly_size(const uint8_t *str, size_t len);
static size_t ascii_friendly_size(const uint8_t *str, size_t len);
static void dump_leaf(Leaf leaf, int depth, Out out);
static void dump_leaf_str(Leaf leaf, Out out);
static void dump_leaf_fixnum(Leaf leaf, Out out);
static void dump_leaf_float(Leaf leaf, Out out);
static void dump_leaf_array(Leaf leaf, int depth, Out out);
static void dump_leaf_hash(Leaf leaf, int depth, Out out);
// These are used to detect rails re-call of Oj.dump() inside the to_json()
// method. It is not thread safe.
static VALUE last_obj = Qundef;
static int oj_rails_hack = -1;
static const char hex_chars[17] = "0123456789abcdef";
static char hibit_friendly_chars[256] = "\
66666666222622666666666666666666\
11211111111111111111111111111111\
11111111111111111111111111112111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111";
// High bit set characters are always encoded as unicode. Worse case is 3
// bytes per character in the output. That makes this conservative.
static char ascii_friendly_chars[256] = "\
66666666222622666666666666666666\
11211111111111121111111111111111\
11111111111111111111111111112111\
11111111111111111111111111111116\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333";
static char xss_friendly_chars[256] = "\
66666666222622666666666666666666\
11211161111111121111111111116161\
11111111111111111111111111112111\
11111111111111111111111111111116\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333";
inline static size_t
hibit_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += hibit_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static size_t
ascii_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += ascii_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static size_t
xss_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += xss_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static void
fill_indent(Out out, int cnt) {
if (0 < out->indent) {
cnt *= out->indent;
*out->cur++ = '\n';
for (; 0 < cnt; cnt--) {
*out->cur++ = ' ';
}
}
}
inline static const char*
ulong2str(uint32_t num, char *end) {
char *b;
*end-- = '\0';
for (b = end; 0 < num || b == end; num /= 10, b--) {
*b = (num % 10) + '0';
}
b++;
return b;
}
inline static void
dump_ulong(unsigned long num, Out out) {
char buf[32];
char *b = buf + sizeof(buf) - 1;
*b-- = '\0';
if (0 < num) {
for (; 0 < num; num /= 10, b--) {
*b = (num % 10) + '0';
}
b++;
} else {
*b = '0';
}
for (; '\0' != *b; b++) {
*out->cur++ = *b;
}
*out->cur = '\0';
}
static void
grow(Out out, size_t len) {
size_t size = out->end - out->buf;
long pos = out->cur - out->buf;
char *buf;
size *= 2;
if (size <= len * 2 + pos) {
size += len;
}
if (out->allocated) {
buf = REALLOC_N(out->buf, char, (size + BUFFER_EXTRA));
} else {
buf = ALLOC_N(char, (size + BUFFER_EXTRA));
out->allocated = 1;
memcpy(buf, out->buf, out->end - out->buf + BUFFER_EXTRA);
}
if (0 == buf) {
rb_raise(rb_eNoMemError, "Failed to create string. [%d:%s]\n", ENOSPC, strerror(ENOSPC));
}
out->buf = buf;
out->end = buf + size;
out->cur = out->buf + pos;
}
inline static void
dump_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
dump_raw(const char *str, size_t cnt, Out out) {
if (out->end - out->cur <= (long)cnt + 10) {
grow(out, cnt + 10);
}
memcpy(out->cur, str, cnt);
out->cur += cnt;
*out->cur = '\0';
}
const char*
dump_unicode(const char *str, const char *end, Out out) {
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;
rb_raise(rb_eEncodingError, "Invalid Unicode\n");
}
str++;
for (; 0 < cnt; cnt--, str++) {
b = *(uint8_t*)str;
if (end <= str || 0x80 != (0xC0 & b)) {
rb_raise(rb_eEncodingError, "Invalid Unicode\n");
}
code = (code << 6) | (b & 0x0000003F);
}
if (0x0000FFFF < code) {
uint32_t c1;
code -= 0x00010000;
c1 = ((code >> 10) & 0x000003FF) + 0x0000D800;
code = (code & 0x000003FF) + 0x0000DC00;
*out->cur++ = '\\';
*out->cur++ = 'u';
for (i = 3; 0 <= i; i--) {
*out->cur++ = hex_chars[(uint8_t)(c1 >> (i * 4)) & 0x0F];
}
}
*out->cur++ = '\\';
*out->cur++ = 'u';
for (i = 3; 0 <= i; i--) {
*out->cur++ = hex_chars[(uint8_t)(code >> (i * 4)) & 0x0F];
}
return str - 1;
}
// returns 0 if not using circular references, -1 if not further writing is
// needed (duplicate), and a positive value if the object was added to the cache.
static long
check_circular(VALUE obj, Out out) {
slot_t id = 0;
slot_t *slot;
if (ObjectMode == out->opts->mode && 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 (out->end - out->cur <= 18) {
grow(out, 18);
}
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'r';
dump_ulong(id, out);
*out->cur++ = '"';
return -1;
}
}
return (long)id;
}
static void
dump_nil(Out out) {
size_t size = 4;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = 'n';
*out->cur++ = 'u';
*out->cur++ = 'l';
*out->cur++ = 'l';
*out->cur = '\0';
}
static void
dump_true(Out out) {
size_t size = 4;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = 't';
*out->cur++ = 'r';
*out->cur++ = 'u';
*out->cur++ = 'e';
*out->cur = '\0';
}
static void
dump_false(Out out) {
size_t size = 5;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = 'f';
*out->cur++ = 'a';
*out->cur++ = 'l';
*out->cur++ = 's';
*out->cur++ = 'e';
*out->cur = '\0';
}
static void
dump_fixnum(VALUE obj, Out out) {
char buf[32];
char *b = buf + sizeof(buf) - 1;
long num = NUM2LONG(obj);
int neg = 0;
if (0 > num) {
neg = 1;
num = -num;
}
*b-- = '\0';
if (0 < num) {
for (; 0 < num; num /= 10, b--) {
*b = (num % 10) + '0';
}
if (neg) {
*b = '-';
} else {
b++;
}
} else {
*b = '0';
}
if (out->end - out->cur <= (long)(sizeof(buf) - (b - buf))) {
grow(out, sizeof(buf) - (b - buf));
}
for (; '\0' != *b; b++) {
*out->cur++ = *b;
}
*out->cur = '\0';
}
static void
dump_bignum(VALUE obj, Out out) {
volatile VALUE rs = rb_big2str(obj, 10);
int cnt = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= (long)cnt) {
grow(out, cnt);
}
memcpy(out->cur, rb_string_value_ptr((VALUE*)&rs), cnt);
out->cur += cnt;
*out->cur = '\0';
}
// Removed dependencies on math due to problems with CentOS 5.4.
static void
dump_float(VALUE obj, Out out) {
char buf[64];
char *b;
double d = rb_num2dbl(obj);
int cnt;
if (0.0 == d) {
b = buf;
*b++ = '0';
*b++ = '.';
*b++ = '0';
*b++ = '\0';
cnt = 3;
} else if (OJ_INFINITY == d) {
strcpy(buf, "Infinity");
cnt = 8;
} else if (-OJ_INFINITY == d) {
strcpy(buf, "-Infinity");
cnt = 9;
} else if (isnan(d)) {
strcpy(buf, "NaN");
cnt = 3;
} else if (d == (double)(long long int)d) {
cnt = sprintf(buf, "%.1f", d); // used sprintf due to bug in snprintf
} else {
cnt = sprintf(buf, "%0.15g", d); // used sprintf due to bug in snprintf
}
if (out->end - out->cur <= (long)cnt) {
grow(out, cnt);
}
for (b = buf; '\0' != *b; b++) {
*out->cur++ = *b;
}
*out->cur = '\0';
}
static void
dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out) {
size_t size;
char *cmap;
switch (out->opts->escape_mode) {
case ASCIIEsc:
cmap = ascii_friendly_chars;
size = ascii_friendly_size((uint8_t*)str, cnt);
break;
case XSSEsc:
cmap = xss_friendly_chars;
size = xss_friendly_size((uint8_t*)str, cnt);
break;
case JSONEsc:
default:
cmap = hibit_friendly_chars;
size = hibit_friendly_size((uint8_t*)str, cnt);
}
if (out->end - out->cur <= (long)size + BUFFER_EXTRA) { // extra 10 for escaped first char, quotes, and sym
grow(out, size + BUFFER_EXTRA);
}
*out->cur++ = '"';
if (escape1) {
*out->cur++ = '\\';
*out->cur++ = 'u';
*out->cur++ = '0';
*out->cur++ = '0';
dump_hex((uint8_t)*str, out);
cnt--;
size--;
str++;
is_sym = 0; // just to make sure
}
if (cnt == size) {
if (is_sym) {
*out->cur++ = ':';
}
for (; '\0' != *str; str++) {
*out->cur++ = *str;
}
*out->cur++ = '"';
} else {
const char *end = str + cnt;
if (is_sym) {
*out->cur++ = ':';
}
for (; str < end; str++) {
switch (cmap[(uint8_t)*str]) {
case '1':
*out->cur++ = *str;
break;
case '2':
*out->cur++ = '\\';
switch (*str) {
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
str = dump_unicode(str, end, out);
break;
case '6': // control characters
*out->cur++ = '\\';
*out->cur++ = 'u';
*out->cur++ = '0';
*out->cur++ = '0';
dump_hex((uint8_t)*str, out);
break;
default:
break; // ignore, should never happen if the table is correct
}
}
*out->cur++ = '"';
}
*out->cur = '\0';
}
static void
dump_str_comp(VALUE obj, Out out) {
dump_cstr(rb_string_value_ptr((VALUE*)&obj), RSTRING_LEN(obj), 0, 0, out);
}
static void
dump_str_obj(VALUE obj, Out out) {
const char *s = rb_string_value_ptr((VALUE*)&obj);
size_t len = RSTRING_LEN(obj);
char s1 = s[1];
dump_cstr(s, len, 0, (':' == *s || ('^' == *s && ('r' == s1 || 'i' == s1))), out);
}
static void
dump_sym_comp(VALUE obj, Out out) {
const char *sym = rb_id2name(SYM2ID(obj));
dump_cstr(sym, strlen(sym), 0, 0, out);
}
static void
dump_sym_obj(VALUE obj, Out out) {
const char *sym = rb_id2name(SYM2ID(obj));
size_t len = strlen(sym);
dump_cstr(sym, len, 1, 0, out);
}
static void
dump_class_comp(VALUE obj, Out out) {
const char *s = rb_class2name(obj);
dump_cstr(s, strlen(s), 0, 0, out);
}
static void
dump_class_obj(VALUE obj, Out out) {
const char *s = rb_class2name(obj);
size_t len = strlen(s);
if (out->end - out->cur <= 6) {
grow(out, 6);
}
*out->cur++ = '{';
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'c';
*out->cur++ = '"';
*out->cur++ = ':';
dump_cstr(s, len, 0, 0, out);
*out->cur++ = '}';
*out->cur = '\0';
}
static void
dump_array(VALUE a, int depth, Out out) {
size_t size;
int i, cnt;
int d2 = depth + 1;
long id = check_circular(a, out);
if (id < 0) {
return;
}
cnt = (int)RARRAY_LEN(a);
*out->cur++ = '[';
if (0 < id) {
size = d2 * out->indent + 16;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'i';
dump_ulong(id, out);
*out->cur++ = '"';
}
size = 2;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 == cnt) {
*out->cur++ = ']';
} else {
if (0 < id) {
*out->cur++ = ',';
}
if (0 == out->opts->dump_opts) {
size = d2 * out->indent + 2;
} else {
size = d2 * out->opts->dump_opts->indent_size + out->opts->dump_opts->array_size + 1;
}
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 == out->opts->dump_opts) {
fill_indent(out, d2);
} else {
if (0 < out->opts->dump_opts->array_size) {
strcpy(out->cur, out->opts->dump_opts->array_nl);
out->cur += out->opts->dump_opts->array_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = d2; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
dump_val(rb_ary_entry(a, i), d2, out);
if (i < cnt) {
*out->cur++ = ',';
}
}
size = depth * out->indent + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 == out->opts->dump_opts) {
fill_indent(out, depth);
} else {
//printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, out->opts->dump_opts->indent);
if (0 < out->opts->dump_opts->array_size) {
strcpy(out->cur, out->opts->dump_opts->array_nl);
out->cur += out->opts->dump_opts->array_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
*out->cur++ = ']';
}
*out->cur = '\0';
}
static int
hash_cb_strict(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size;
if (rb_type(key) != T_STRING) {
rb_raise(rb_eTypeError, "In :strict mode all Hash keys must be Strings, not %s.\n", rb_class2name(rb_obj_class(key)));
}
if (0 == out->opts->dump_opts) {
size = depth * out->indent + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
fill_indent(out, depth);
dump_str_comp(key, out);
*out->cur++ = ':';
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
dump_str_comp(key, out);
size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->before_size) {
strcpy(out->cur, out->opts->dump_opts->before_sep);
out->cur += out->opts->dump_opts->before_size;
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts->after_size) {
strcpy(out->cur, out->opts->dump_opts->after_sep);
out->cur += out->opts->dump_opts->after_size;
}
}
dump_val(value, depth, out);
out->depth = depth;
*out->cur++ = ',';
return ST_CONTINUE;
}
static int
hash_cb_compat(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size;
if (0 == out->opts->dump_opts) {
size = depth * out->indent + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
fill_indent(out, depth);
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
switch (rb_type(key)) {
case T_STRING:
dump_str_comp(key, out);
break;
case T_SYMBOL:
dump_sym_comp(key, out);
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)));*/
dump_str_comp(rb_funcall(key, oj_to_s_id, 0), out);
break;
}
if (0 == out->opts->dump_opts) {
*out->cur++ = ':';
} else {
size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->before_size) {
strcpy(out->cur, out->opts->dump_opts->before_sep);
out->cur += out->opts->dump_opts->before_size;
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts->after_size) {
strcpy(out->cur, out->opts->dump_opts->after_sep);
out->cur += out->opts->dump_opts->after_size;
}
}
dump_val(value, depth, out);
out->depth = depth;
*out->cur++ = ',';
return ST_CONTINUE;
}
static int
hash_cb_object(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size = depth * out->indent + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
fill_indent(out, depth);
if (rb_type(key) == T_STRING) {
dump_str_obj(key, out);
*out->cur++ = ':';
dump_val(value, depth, out);
} else if (rb_type(key) == T_SYMBOL) {
dump_sym_obj(key, out);
*out->cur++ = ':';
dump_val(value, depth, out);
} else {
int d2 = depth + 1;
long s2 = size + out->indent + 1;
int i;
int started = 0;
uint8_t b;
if (out->end - out->cur <= s2 + 15) {
grow(out, s2 + 15);
}
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = '#';
out->hash_cnt++;
for (i = 28; 0 <= i; i -= 4) {
b = (uint8_t)((out->hash_cnt >> i) & 0x0000000F);
if ('\0' != b) {
started = 1;
}
if (started) {
*out->cur++ = hex_chars[b];
}
}
*out->cur++ = '"';
*out->cur++ = ':';
*out->cur++ = '[';
fill_indent(out, d2);
dump_val(key, d2, out);
if (out->end - out->cur <= (long)s2) {
grow(out, s2);
}
*out->cur++ = ',';
fill_indent(out, d2);
dump_val(value, d2, out);
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
*out->cur++ = ']';
}
out->depth = depth;
*out->cur++ = ',';
return ST_CONTINUE;
}
static void
dump_hash(VALUE obj, int depth, int mode, Out out) {
int cnt = (int)RHASH_SIZE(obj);
size_t size = depth * out->indent + 2;
if (out->end - out->cur <= 2) {
grow(out, 2);
}
if (0 == cnt) {
*out->cur++ = '{';
*out->cur++ = '}';
} else {
long id = check_circular(obj, out);
if (0 > id) {
return;
}
*out->cur++ = '{';
if (0 < id) {
if (out->end - out->cur <= (long)size + 16) {
grow(out, size + 16);
}
fill_indent(out, depth + 1);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'i';
*out->cur++ = '"';
*out->cur++ = ':';
dump_ulong(id, out);
*out->cur++ = ',';
}
out->depth = depth + 1;
if (ObjectMode == mode) {
rb_hash_foreach(obj, hash_cb_object, (VALUE)out);
} else if (CompatMode == mode) {
rb_hash_foreach(obj, hash_cb_compat, (VALUE)out);
} else {
rb_hash_foreach(obj, hash_cb_strict, (VALUE)out);
}
if (',' == *(out->cur - 1)) {
out->cur--; // backup to overwrite last comma
}
if (0 == out->opts->dump_opts) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
*out->cur++ = '}';
}
*out->cur = '\0';
}
static void
dump_time(VALUE obj, Out out) {
char buf[64];
char *b = buf + sizeof(buf) - 1;
long size;
char *dot = b - 10;
int neg = 0;
long one = 1000000000;
#if HAS_RB_TIME_TIMESPEC
struct timespec ts = rb_time_timespec(obj);
time_t sec = ts.tv_sec;
long nsec = ts.tv_nsec;
#else
time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
#if HAS_NANO_TIME
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
#else
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000;
#endif
#endif
if (0 > sec) {
neg = 1;
sec = -sec;
if (0 < nsec) {
nsec = 1000000000 - nsec;
#ifndef JRUBY_RUBY
sec--;
#endif
}
}
*b-- = '\0';
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;
if (out->end - out->cur <= size) {
grow(out, size);
}
memcpy(out->cur, b, size);
out->cur += size;
*out->cur = '\0';
}
static void
dump_ruby_time(VALUE obj, Out out) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
static void
dump_xml_time(VALUE obj, Out out) {
char buf[64];
struct tm *tm;
long one = 1000000000;
#if HAS_RB_TIME_TIMESPEC
struct timespec ts = rb_time_timespec(obj);
time_t sec = ts.tv_sec;
long nsec = ts.tv_nsec;
#else
time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
#if HAS_NANO_TIME
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
#else
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000;
#endif
#endif
long tz_secs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0));
int tzhour, tzmin;
char tzsign = '+';
if (out->end - out->cur <= 36) {
grow(out, 36);
}
if (9 > out->opts->sec_prec) {
int i;
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 += tz_secs;
tm = gmtime(&sec);
#if 1
if (0 > tz_secs) {
tzsign = '-';
tzhour = (int)(tz_secs / -3600);
tzmin = (int)(tz_secs / -60) - (tzhour * 60);
} else {
tzhour = (int)(tz_secs / 3600);
tzmin = (int)(tz_secs / 60) - (tzhour * 60);
}
#else
if (0 > tm->tm_gmtoff) {
tzsign = '-';
tzhour = (int)(tm->tm_gmtoff / -3600);
tzmin = (int)(tm->tm_gmtoff / -60) - (tzhour * 60);
} else {
tzhour = (int)(tm->tm_gmtoff / 3600);
tzmin = (int)(tm->tm_gmtoff / 60) - (tzhour * 60);
}
#endif
if (0 == nsec || 0 == out->opts->sec_prec) {
if (0 == tzhour && 0 == tzmin) {
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
dump_cstr(buf, 20, 0, 0, out);
} else {
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
tzsign, tzhour, tzmin);
dump_cstr(buf, 25, 0, 0, out);
}
} else {
char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d";
int len = 35;
if (9 > out->opts->sec_prec) {
format[32] = '0' + out->opts->sec_prec;
len -= 9 - out->opts->sec_prec;
}
sprintf(buf, format,
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec, nsec,
tzsign, tzhour, tzmin);
dump_cstr(buf, len, 0, 0, out);
}
}
static void
dump_data_strict(VALUE obj, Out out) {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
raise_strict(obj);
}
}
static void
dump_data_null(VALUE obj, Out out) {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_nil(out);
}
}
static void
dump_data_comp(VALUE obj, int depth, Out out) {
volatile VALUE o2;
if (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)) {
rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
}
dump_hash(h, depth, out->opts->mode, out);
} else if (rb_respond_to(obj, oj_as_json_id) && obj != (o2 = rb_funcall(obj, oj_as_json_id, 0))) {
dump_val(o2, depth, out);
} else if (rb_respond_to(obj, oj_to_json_id) && (!oj_rails_hack || last_obj != obj)) {
volatile VALUE rs;
const char *s;
int len;
last_obj = obj;
rs = rb_funcall(obj, oj_to_json_id, 0);
last_obj = Qundef;
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len + 1) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
*out->cur = '\0';
} else {
VALUE clas = rb_obj_class(obj);
if (rb_cTime == clas) {
switch (out->opts->time_format) {
case RubyTime: dump_ruby_time(obj, out); break;
case XmlTime: dump_xml_time(obj, out); break;
case UnixTime:
default: dump_time(obj, out); break;
}
} else if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
if (Yes == out->opts->bigdec_as_num) {
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
} else {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
}
}
static void
dump_data_obj(VALUE obj, int depth, Out out) {
VALUE clas = rb_obj_class(obj);
if (rb_cTime == clas) {
if (out->end - out->cur <= 6) {
grow(out, 6);
}
*out->cur++ = '{';
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 't';
*out->cur++ = '"';
*out->cur++ = ':';
dump_time(obj, out);
*out->cur++ = '}';
*out->cur = '\0';
} else {
Odd odd = oj_get_odd(clas);
if (0 == odd) {
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
if (Yes == out->opts->bigdec_as_num) {
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
} else {
dump_nil(out);
}
} else {
dump_odd(obj, odd, clas, depth + 1, out);
}
}
}
static void
dump_obj_comp(VALUE obj, int depth, Out out) {
if (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)) {
rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
}
dump_hash(h, depth, out->opts->mode, out);
} else if (rb_respond_to(obj, oj_as_json_id)) {
dump_val(rb_funcall(obj, oj_as_json_id, 0), depth, out);
} else if (rb_respond_to(obj, oj_to_json_id) && (!oj_rails_hack || last_obj != obj)) {
volatile VALUE rs;
const char *s;
int len;
last_obj = obj;
rs = rb_funcall(obj, oj_to_json_id, 0);
last_obj = Qundef;
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len + 1) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
*out->cur = '\0';
} else {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
if (Yes == out->opts->bigdec_as_num) {
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
} else if (oj_datetime_class == clas || oj_date_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
Odd odd = oj_get_odd(clas);
if (0 == odd) {
dump_obj_attrs(obj, 0, 0, depth, out);
} else {
dump_odd(obj, odd, 0, depth + 1, out);
}
}
}
*out->cur = '\0';
}
inline static void
dump_obj_obj(VALUE obj, int depth, Out out) {
long id = check_circular(obj, out);
if (0 <= id) {
VALUE clas = rb_obj_class(obj);
Odd odd = oj_get_odd(clas);
if (0 == odd) {
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_obj_attrs(obj, clas, id, depth, out);
}
} else {
dump_odd(obj, odd, clas, depth + 1, out);
}
}
}
#if HAS_IVAR_HELPERS
static int
dump_attr_cb(ID key, VALUE value, Out out) {
int depth = out->depth;
size_t size = depth * out->indent + 1;
const char *attr = rb_id2name(key);
#if HAS_EXCEPTION_MAGIC
if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
return ST_CONTINUE;
}
#endif
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
if ('@' == *attr) {
attr++;
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';
dump_cstr(buf, strlen(buf), 0, 0, out);
}
*out->cur++ = ':';
dump_val(value, depth, out);
out->depth = depth;
*out->cur++ = ',';
return ST_CONTINUE;
}
#endif
static void
dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
size_t size = 0;
int d2 = depth + 1;
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = '{';
if (0 != clas) {
const char *class_name = rb_class2name(clas);
int clen = (int)strlen(class_name);
size = d2 * out->indent + clen + 10;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'o';
*out->cur++ = '"';
*out->cur++ = ':';
dump_cstr(class_name, clen, 0, 0, out);
}
if (0 < id) {
size = d2 * out->indent + 16;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = ',';
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'i';
*out->cur++ = '"';
*out->cur++ = ':';
dump_ulong(id, out);
}
{
int cnt;
#if HAS_IVAR_HELPERS
cnt = (int)rb_ivar_count(obj);
#else
volatile VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0);
VALUE *np = RARRAY_PTR(vars);
ID vid;
const char *attr;
int i;
cnt = (int)RARRAY_LEN(vars);
#endif
if (0 != clas && 0 < cnt) {
*out->cur++ = ',';
}
out->depth = depth + 1;
#if HAS_IVAR_HELPERS
rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out);
if (',' == *(out->cur - 1)) {
out->cur--; // backup to overwrite last comma
}
#else
size = d2 * out->indent + 1;
for (i = cnt; 0 < i; i--, np++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
vid = rb_to_id(*np);
fill_indent(out, d2);
attr = rb_id2name(vid);
if ('@' == *attr) {
attr++;
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';
dump_cstr(buf, strlen(attr) + 1, 0, 0, out);
}
*out->cur++ = ':';
dump_val(rb_ivar_get(obj, vid), d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
if (1 < i) {
*out->cur++ = ',';
}
}
#endif
#if HAS_EXCEPTION_MAGIC
if (Qtrue == rb_obj_is_kind_of(obj, rb_eException)) {
volatile VALUE rv;
if (',' != *(out->cur - 1)) {
*out->cur++ = ',';
}
// message
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr("~mesg", 5, 0, 0, out);
*out->cur++ = ':';
rv = rb_funcall2(obj, rb_intern("message"), 0, 0);
dump_val(rv, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = ',';
// backtrace
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr("~bt", 3, 0, 0, out);
*out->cur++ = ':';
rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0);
dump_val(rv, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
}
#endif
out->depth = depth;
}
fill_indent(out, depth);
*out->cur++ = '}';
*out->cur = '\0';
}
static void
dump_struct_comp(VALUE obj, int depth, Out out) {
if (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)) {
rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
}
dump_hash(h, depth, out->opts->mode, out);
} else if (rb_respond_to(obj, oj_to_json_id)) {
volatile VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
const char *s;
int len;
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
} else {
dump_struct_obj(obj, depth, out);
}
}
static void
dump_struct_obj(VALUE obj, int depth, Out out) {
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;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = '{';
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'u';
*out->cur++ = '"';
*out->cur++ = ':';
*out->cur++ = '[';
fill_indent(out, d3);
*out->cur++ = '"';
memcpy(out->cur, class_name, len);
out->cur += len;
*out->cur++ = '"';
*out->cur++ = ',';
size = d3 * out->indent + 2;
#ifdef RSTRUCT_LEN
{
VALUE *vp;
for (i = (int)RSTRUCT_LEN(obj), vp = RSTRUCT_PTR(obj); 0 < i; i--, vp++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d3);
dump_val(*vp, 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++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d3);
dump_val(rb_struct_aref(obj, INT2FIX(i)), d3, out);
*out->cur++ = ',';
}
}
#endif
out->cur--;
*out->cur++ = ']';
*out->cur++ = '}';
*out->cur = '\0';
}
static void
dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) {
ID *idp;
volatile VALUE v;
const char *name;
size_t size;
int d2 = depth + 1;
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = '{';
if (0 != clas) {
const char *class_name = rb_class2name(clas);
int clen = (int)strlen(class_name);
size = d2 * out->indent + clen + 10;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = '^';
*out->cur++ = 'O';
*out->cur++ = '"';
*out->cur++ = ':';
dump_cstr(class_name, clen, 0, 0, out);
*out->cur++ = ',';
}
size = d2 * out->indent + 1;
for (idp = odd->attrs; 0 != *idp; idp++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
name = rb_id2name(*idp);
v = rb_funcall(obj, *idp, 0);
fill_indent(out, d2);
dump_cstr(name, strlen(name), 0, 0, out);
*out->cur++ = ':';
dump_val(v, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = ',';
}
out->cur--;
*out->cur++ = '}';
*out->cur = '\0';
}
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)));
}
static void
dump_val(VALUE obj, int depth, Out out) {
switch (rb_type(obj)) {
case T_NIL: dump_nil(out); break;
case T_TRUE: dump_true(out); break;
case T_FALSE: dump_false(out); break;
case T_FIXNUM: dump_fixnum(obj, out); break;
case T_FLOAT: dump_float(obj, out); break;
case T_BIGNUM: dump_bignum(obj, out); break;
case T_STRING:
switch (out->opts->mode) {
case StrictMode:
case NullMode:
case CompatMode: dump_str_comp(obj, out); break;
case ObjectMode:
default: dump_str_obj(obj, out); break;
}
break;
case T_SYMBOL:
switch (out->opts->mode) {
case StrictMode: raise_strict(obj); break;
case NullMode: dump_nil(out); break;
case CompatMode: dump_sym_comp(obj, out); break;
case ObjectMode:
default: dump_sym_obj(obj, out); break;
}
break;
case T_ARRAY: dump_array(obj, depth, out); break;
case T_HASH: dump_hash(obj, depth, out->opts->mode, out); break;
case T_CLASS:
switch (out->opts->mode) {
case StrictMode: raise_strict(obj); break;
case NullMode: dump_nil(out); break;
case CompatMode: dump_class_comp(obj, out); break;
case ObjectMode:
default: dump_class_obj(obj, out); break;
}
break;
#if (defined T_RATIONAL && defined RRATIONAL)
case T_RATIONAL:
#endif
case T_OBJECT:
switch (out->opts->mode) {
case StrictMode: dump_data_strict(obj, out); break;
case NullMode: dump_data_null(obj, out); break;
case CompatMode: dump_obj_comp(obj, depth, out); break;
case ObjectMode:
default: dump_obj_obj(obj, depth, out); break;
}
break;
case T_DATA:
switch (out->opts->mode) {
case StrictMode: dump_data_strict(obj, out); break;
case NullMode: dump_data_null(obj, out); break;
case CompatMode: dump_data_comp(obj, depth, out);break;
case ObjectMode:
default: dump_data_obj(obj, depth, out); break;
}
break;
case T_STRUCT: // for Range
switch (out->opts->mode) {
case StrictMode: raise_strict(obj); break;
case NullMode: dump_nil(out); break;
case CompatMode: dump_struct_comp(obj, depth, out); break;
case ObjectMode:
default: dump_struct_obj(obj, depth, out); break;
}
break;
#if (defined T_COMPLEX && defined RCOMPLEX)
case T_COMPLEX:
#endif
case T_REGEXP:
switch (out->opts->mode) {
case StrictMode: raise_strict(obj); break;
case NullMode: dump_nil(out); break;
case CompatMode:
case ObjectMode:
default: dump_obj_comp(obj, depth, out); break;
}
break;
default:
rb_raise(rb_eNotImpError, "Failed to dump '%s' Object (%02x)\n",
rb_class2name(rb_obj_class(obj)), rb_type(obj));
break;
}
}
void
oj_dump_obj_to_json(VALUE obj, Options copts, Out out) {
if (0 == out->buf) {
out->buf = ALLOC_N(char, 4096);
out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors
out->allocated = 1;
}
out->cur = out->buf;
out->circ_cnt = 0;
out->opts = copts;
out->hash_cnt = 0;
if (Yes == copts->circular) {
oj_cache8_new(&out->circ_cache);
}
out->indent = copts->indent;
if (0 > oj_rails_hack) {
oj_rails_hack = (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport")));
}
dump_val(obj, 0, out);
if (Yes == copts->circular) {
oj_cache8_delete(out->circ_cache);
}
}
void
oj_write_obj_to_file(VALUE obj, const char *path, Options copts) {
char buf[4096];
struct _Out out;
size_t size;
FILE *f;
int ok;
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
oj_dump_obj_to_json(obj, copts, &out);
size = out.cur - out.buf;
if (0 == (f = fopen(path, "w"))) {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eIOError, "%s\n", strerror(errno));
}
ok = (size == fwrite(out.buf, 1, size, f));
if (out.allocated) {
xfree(out.buf);
}
fclose(f);
if (!ok) {
int err = ferror(f);
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err));
}
}
void
oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts) {
char buf[4096];
struct _Out out;
ssize_t size;
VALUE clas = rb_obj_class(stream);
#if !IS_WINDOWS
VALUE s;
#endif
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
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))) {
int fd = FIX2INT(s);
if (size != write(fd, out.buf, size)) {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", errno, strerror(errno));
}
#endif
} else if (rb_respond_to(stream, oj_write_id)) {
rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size));
} else {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eArgError, "to_stream() expected an IO Object.");
}
if (out.allocated) {
xfree(out.buf);
}
}
// dump leaf functions
inline static void
dump_chars(const char *s, size_t size, Out out) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
memcpy(out->cur, s, size);
out->cur += size;
*out->cur = '\0';
}
static void
dump_leaf_str(Leaf leaf, Out out) {
switch (leaf->value_type) {
case STR_VAL:
dump_cstr(leaf->str, strlen(leaf->str), 0, 0, out);
break;
case RUBY_VAL:
dump_cstr(rb_string_value_cstr(&leaf->value), 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)) {
dump_bignum(leaf->value, out);
} else {
dump_fixnum(leaf->value, out);
}
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:
dump_float(leaf->value, out);
break;
case COL_VAL:
default:
rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type);
break;
}
}
static void
dump_leaf_array(Leaf leaf, int depth, Out out) {
size_t size;
int d2 = depth + 1;
size = 2;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = '[';
if (0 == leaf->elements) {
*out->cur++ = ']';
} else {
Leaf first = leaf->elements->next;
Leaf e = first;
size = d2 * out->indent + 2;
do {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_leaf(e, d2, out);
if (e->next != first) {
*out->cur++ = ',';
}
e = e->next;
} while (e != first);
size = depth * out->indent + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
*out->cur++ = ']';
}
*out->cur = '\0';
}
static void
dump_leaf_hash(Leaf leaf, int depth, Out out) {
size_t size;
int d2 = depth + 1;
size = 2;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = '{';
if (0 == leaf->elements) {
*out->cur++ = '}';
} else {
Leaf first = leaf->elements->next;
Leaf e = first;
size = d2 * out->indent + 2;
do {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr(e->key, strlen(e->key), 0, 0, out);
*out->cur++ = ':';
dump_leaf(e, d2, out);
if (e->next != first) {
*out->cur++ = ',';
}
e = e->next;
} while (e != first);
size = depth * out->indent + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
*out->cur++ = '}';
}
*out->cur = '\0';
}
static void
dump_leaf(Leaf leaf, int depth, Out out) {
switch (leaf->type) {
case T_NIL:
dump_nil(out);
break;
case T_TRUE:
dump_true(out);
break;
case T_FALSE:
dump_false(out);
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->type);
break;
}
}
void
oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out) {
if (0 == out->buf) {
out->buf = ALLOC_N(char, 4096);
out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors
out->allocated = 1;
}
out->cur = out->buf;
out->circ_cnt = 0;
out->opts = copts;
out->hash_cnt = 0;
out->indent = copts->indent;
dump_leaf(leaf, 0, out);
}
void
oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts) {
char buf[4096];
struct _Out out;
size_t size;
FILE *f;
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
oj_dump_leaf_to_json(leaf, copts, &out);
size = out.cur - out.buf;
if (0 == (f = fopen(path, "w"))) {
rb_raise(rb_eIOError, "%s\n", strerror(errno));
}
if (size != fwrite(out.buf, 1, size, f)) {
int err = ferror(f);
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err));
}
if (out.allocated) {
xfree(out.buf);
}
fclose(f);
}
// string writer functions
static void
key_check(StrWriter sw, const char *key) {
DumpType type = sw->types[sw->depth];
if (0 == key && (ObjectNew == type || ObjectType == type)) {
rb_raise(rb_eStandardError, "Can not push onto an Object without a key.");
}
}
static void
push_type(StrWriter sw, DumpType type) {
if (sw->types_end <= sw->types + sw->depth + 1) {
size_t size = (sw->types_end - sw->types) * 2;
REALLOC_N(sw->types, char, size);
sw->types_end = sw->types + size;
}
sw->depth++;
sw->types[sw->depth] = type;
}
static void
maybe_comma(StrWriter sw) {
switch (sw->types[sw->depth]) {
case ObjectNew:
sw->types[sw->depth] = ObjectType;
break;
case ArrayNew:
sw->types[sw->depth] = ArrayType;
break;
case ObjectType:
case ArrayType:
// Always have a few characters available in the out.buf.
*sw->out.cur++ = ',';
break;
}
}
void
oj_str_writer_push_object(StrWriter sw, const char *key) {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= (long)size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
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) {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
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) {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
dump_val(val, sw->depth, &sw->out);
}
void
oj_str_writer_push_json(StrWriter sw, const char *json, const char *key) {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
dump_raw(json, strlen(json), &sw->out);
}
void
oj_str_writer_pop(StrWriter sw) {
long size;
DumpType type = sw->types[sw->depth];
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;
if (sw->out.end - sw->out.cur <= size) {
grow(&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);
}
}
oj-2.5.3/ext/oj/oj.h 0000644 0000041 0000041 00000016717 12263716750 014177 0 ustar www-data www-data /* oj.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_H__
#define __OJ_H__
#if defined(__cplusplus)
extern "C" {
#if 0
} /* satisfy cc-mode */
#endif
#endif
#define RSTRING_NOT_MODIFIED
#include "ruby.h"
#if HAS_ENCODING_SUPPORT
#include "ruby/encoding.h"
#endif
#include "stdint.h"
#if USE_PTHREAD_MUTEX
#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
#if HAS_TOP_LEVEL_ST_H
// Only on travis, local is where it is for all others. Seems to vary depending on the travis machine picked up.
#include "st.h"
#else
#include "ruby/st.h"
#endif
#endif
#include "err.h"
typedef enum {
Yes = 'y',
No = 'n',
NotSet = 0
} YesNo;
typedef enum {
StrictMode = 's',
ObjectMode = 'o',
NullMode = 'n',
CompatMode = 'c'
} Mode;
typedef enum {
UnixTime = 'u',
XmlTime = 'x',
RubyTime = 'r'
} TimeFormat;
typedef enum {
JSONEsc = 'j',
XSSEsc = 'x',
ASCIIEsc = 'a'
} Encoding;
typedef enum {
BigDec = 'b',
FloatDec = 'f',
AutoDec = 'a'
} BigLoad;
typedef enum {
ArrayNew = 'A',
ArrayType = 'a',
ObjectNew = 'O',
ObjectType = 'o',
} DumpType;
typedef enum {
STRING_IO = 'c',
STREAM_IO = 's',
FILE_IO = 'f',
} StreamWriterType;
typedef struct _DumpOpts {
const char *indent;
const char *before_sep;
const char *after_sep;
const char *hash_nl;
const char *array_nl;
uint8_t indent_size;
uint8_t before_size;
uint8_t after_size;
uint8_t hash_size;
uint8_t array_size;
} *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
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 allow_gc; // allow GC during parse
DumpOpts dump_opts;
} *Options;
typedef struct _Out {
char *buf;
char *end;
char *cur;
Cache8 circ_cache;
slot_t circ_cnt;
int indent;
int depth; // used by dump_hash
Options opts;
uint32_t hash_cnt;
int allocated;
} *Out;
typedef struct _StrWriter {
struct _Out out;
struct _Options opts;
int depth;
char *types; // DumpType
char *types_end;
} *StrWriter;
typedef struct _StreamWriter {
struct _StrWriter sw;
StreamWriterType type;
VALUE stream;
int fd;
} *StreamWriter;
enum {
STR_VAL = 0x00,
COL_VAL = 0x01,
RUBY_VAL = 0x02
};
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
struct _Leaf *elements; // array and hash elements
VALUE value;
};
uint8_t type;
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_compat_parse(int argc, VALUE *argv, VALUE self);
extern VALUE oj_object_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 void oj_parse_options(VALUE ropts, Options copts);
extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
extern void oj_write_obj_to_file(VALUE obj, const char *path, Options copts);
extern void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts);
extern void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out);
extern void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts);
extern void oj_str_writer_push_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 VALUE Oj;
extern struct _Options oj_default_options;
#if HAS_ENCODING_SUPPORT
extern rb_encoding *oj_utf8_encoding;
#else
extern VALUE oj_utf8_encoding;
#endif
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_stream_writer_class;
extern VALUE oj_string_writer_class;
extern VALUE oj_stringio_class;
extern VALUE oj_struct_class;
extern VALUE oj_time_class;
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_error_id;
extern ID oj_fileno_id;
extern ID oj_hash_end_id;
extern ID oj_hash_set_id;
extern ID oj_hash_start_id;
extern ID oj_iconv_id;
extern ID oj_instance_variables_id;
extern ID oj_json_create_id;
extern ID oj_length_id;
extern ID oj_new_id;
extern ID oj_read_id;
extern ID oj_string_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_offset_id;
extern ID oj_write_id;
#if USE_PTHREAD_MUTEX
extern pthread_mutex_t oj_cache_mutex;
#elif USE_RB_MUTEX
extern VALUE oj_cache_mutex;
#endif
#if defined(__cplusplus)
#if 0
{ /* satisfy cc-mode */
#endif
} /* extern "C" { */
#endif
#endif /* __OJ_H__ */
oj-2.5.3/ext/oj/oj.c 0000644 0000041 0000041 00000177211 12263716750 014167 0 ustar www-data www-data /* oj.c
* Copyright (c) 2012, Peter Ohler
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include "oj.h"
#include "parse.h"
#include "hash.h"
#include "odd.h"
#include "encode.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_error_id;
ID oj_fileno_id;
ID oj_hash_end_id;
ID oj_hash_set_id;
ID oj_hash_start_id;
ID oj_iconv_id;
ID oj_instance_variables_id;
ID oj_json_create_id;
ID oj_length_id;
ID oj_new_id;
ID oj_read_id;
ID oj_string_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_offset_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_parse_error_class;
VALUE oj_stream_writer_class;
VALUE oj_string_writer_class;
VALUE oj_stringio_class;
VALUE oj_struct_class;
VALUE oj_time_class;
VALUE oj_slash_string;
static VALUE allow_gc_sym;
static VALUE ascii_only_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 circular_sym;
static VALUE class_cache_sym;
static VALUE compat_sym;
static VALUE create_id_sym;
static VALUE escape_mode_sym;
static VALUE float_sym;
static VALUE indent_sym;
static VALUE json_sym;
static VALUE mode_sym;
static VALUE null_sym;
static VALUE object_sym;
static VALUE ruby_sym;
static VALUE sec_prec_sym;
static VALUE strict_sym;
static VALUE symbol_keys_sym;
static VALUE time_format_sym;
static VALUE unix_sym;
static VALUE xmlschema_sym;
static VALUE xss_safe_sym;
static VALUE array_nl_sym;
static VALUE create_additions_sym;
static VALUE object_nl_sym;
static VALUE space_before_sym;
static VALUE space_sym;
static VALUE symbolize_names_sym;
static VALUE mimic = Qnil;
#if HAS_ENCODING_SUPPORT
rb_encoding *oj_utf8_encoding = 0;
#else
VALUE oj_utf8_encoding = Qnil;
#endif
#if USE_PTHREAD_MUTEX
pthread_mutex_t oj_cache_mutex;
#elif USE_RB_MUTEX
VALUE oj_cache_mutex = Qnil;
#endif
static const char 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
Yes, // bigdec_as_num
AutoDec, // bigdec_load
json_class, // create_id
10, // create_id_len
9, // sec_prec
Yes, // allow_gc
0, // dump_opts
};
static VALUE define_mimic_json(int argc, VALUE *argv, VALUE self);
/* call-seq: default_options() => Hash
*
* Returns the default load and dump options as a Hash. The options are
* - indent: [Fixnum] number of spaces to indent each element in an JSON document
* - circular: [true|false|nil] support circular references while dumping
* - auto_define: [true|false|nil] automatically define classes if they do not exist
* - symbol_keys: [true|false|nil] use symbols instead of strings for hash keys
* - escape_mode: [:json|:xss_safe|:ascii|nil] use symbols instead of strings for hash keys
* - class_cache: [true|false|nil] cache classes for faster parsing (if dynamically modifying classes or reloading classes then don't use this)
* - mode: [:object|:strict|:compat|:null] load and dump modes to use for JSON
* - time_format: [:unix|:xmlschema|:ruby] time format when dumping in :compat mode
* - bigdecimal_as_decimal: [true|false|nil] dump BigDecimal as a decimal number or as a String
* - bigdecimal_load: [:bigdecimal|:float|:auto] load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits.
* - create_id: [String|nil] create id for json compatible object encoding, default is 'json_create'
* - second_precision: [Fixnum|nil] number of digits after the decimal when dumping the seconds portion of time
* - allow_gc: [true|false|nil] allow or prohibit GC during parsing, default is true (allow)
* @return [Hash] all current option settings.
*/
static VALUE
get_def_opts(VALUE self) {
VALUE opts = rb_hash_new();
rb_hash_aset(opts, indent_sym, INT2FIX(oj_default_options.indent));
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, allow_gc_sym, (Yes == oj_default_options.allow_gc) ? Qtrue : ((No == oj_default_options.allow_gc) ? 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:
default: rb_hash_aset(opts, mode_sym, object_sym); break;
}
switch (oj_default_options.escape_mode) {
case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break;
case XSSEsc: rb_hash_aset(opts, escape_mode_sym, xss_safe_sym); break;
case ASCIIEsc: rb_hash_aset(opts, escape_mode_sym, ascii_sym); break;
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 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 AutoDec:
default: rb_hash_aset(opts, bigdecimal_load_sym, auto_sym); break;
}
rb_hash_aset(opts, create_id_sym, (0 == oj_default_options.create_id) ? Qnil : rb_str_new2(oj_default_options.create_id));
return opts;
}
/* call-seq: default_options=(opts)
*
* Sets the default options for load and dump.
* @param [Hash] opts options to change
* @param [Fixnum] :indent number of spaces to indent each element in an JSON document
* @param [true|false|nil] :circular support circular references while dumping
* @param [true|false|nil] :auto_define automatically define classes if they do not exist
* @param [true|false|nil] :symbol_keys convert hash keys to symbols
* @param [true|false|nil] :class_cache cache classes for faster parsing
* @param [:json|:xss_safe|:ascii|nil] :escape mode encodes all high-bit characters as
* escaped sequences if :ascii, :json is standand UTF-8 JSON encoding,
* and :xss_safe escapes &, <, and >, and some others.
* @param [true|false|nil] :bigdecimal_as_decimal dump BigDecimal as a decimal number or as a String
* @param [:bigdecimal|:float|:auto|nil] :bigdecimal_load load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits.
* @param [:object|:strict|:compat|:null] 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.
* @param [:unix|:xmlschema|:ruby] time format when dumping in :compat mode
* :unix decimal number denoting the number of seconds since 1/1/1970,
* :xmlschema date-time format taken from XML Schema as a String,
* :ruby Time.to_s formatted String
* @param [String|nil] :create_id create id for json compatible object encoding
* @param [Fixnum|nil] :second_precision number of digits after the decimal when dumping the seconds portion of time
* @param [true|false|nil] :allow_gc allow or prohibit GC during parsing, default is true (allow)
* @return [nil]
*/
static VALUE
set_def_opts(VALUE self, VALUE opts) {
struct _YesNoOpt ynos[] = {
{ circular_sym, &oj_default_options.circular },
{ auto_define_sym, &oj_default_options.auto_define },
{ symbol_keys_sym, &oj_default_options.sym_key },
{ class_cache_sym, &oj_default_options.class_cache },
{ bigdecimal_as_decimal_sym, &oj_default_options.bigdec_as_num },
{ allow_gc_sym, &oj_default_options.allow_gc },
{ Qnil, 0 }
};
YesNoOpt o;
VALUE v;
Check_Type(opts, T_HASH);
v = rb_hash_aref(opts, indent_sym);
if (Qnil != v) {
Check_Type(v, T_FIXNUM);
oj_default_options.indent = FIX2INT(v);
}
v = rb_hash_aref(opts, sec_prec_sym);
if (Qnil != v) {
int n;
Check_Type(v, T_FIXNUM);
n = FIX2INT(v);
if (0 > n) {
n = 0;
} else if (9 < n) {
n = 9;
}
oj_default_options.sec_prec = n;
}
v = rb_hash_lookup(opts, mode_sym);
if (Qnil == v) {
// ignore
} else if (object_sym == v) {
oj_default_options.mode = ObjectMode;
} else if (strict_sym == v) {
oj_default_options.mode = StrictMode;
} else if (compat_sym == v) {
oj_default_options.mode = CompatMode;
} else if (null_sym == v) {
oj_default_options.mode = NullMode;
} else {
rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null.");
}
v = rb_hash_lookup(opts, time_format_sym);
if (Qnil == v) {
// ignore
} else if (unix_sym == v) {
oj_default_options.time_format = UnixTime;
} else if (xmlschema_sym == v) {
oj_default_options.time_format = XmlTime;
} else if (ruby_sym == v) {
oj_default_options.time_format = RubyTime;
} else {
rb_raise(rb_eArgError, ":time_format must be :unix, :xmlschema, or :ruby.");
}
v = rb_hash_lookup(opts, escape_mode_sym);
if (Qnil == v) {
// ignore
} else if (json_sym == v) {
oj_default_options.escape_mode = JSONEsc;
} else if (xss_safe_sym == v) {
oj_default_options.escape_mode = XSSEsc;
} else if (ascii_sym == v) {
oj_default_options.escape_mode = ASCIIEsc;
} else {
rb_raise(rb_eArgError, ":encoding must be :json, :rails, or :ascii.");
}
v = rb_hash_lookup(opts, bigdecimal_load_sym);
if (Qnil == v) {
// ignore
} else if (bigdecimal_sym == v || Qtrue == v) {
oj_default_options.bigdec_load = BigDec;
} else if (float_sym == v) {
oj_default_options.bigdec_load = FloatDec;
} else if (auto_sym == v || Qfalse == v) {
oj_default_options.bigdec_load = AutoDec;
} else {
rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto.");
}
if (Qtrue == rb_funcall(opts, rb_intern("has_key?"), 1, create_id_sym)) {
if (0 != oj_default_options.create_id) {
if (json_class != oj_default_options.create_id) {
xfree((char*)oj_default_options.create_id);
}
oj_default_options.create_id = 0;
oj_default_options.create_id_len = 0;
}
v = rb_hash_lookup(opts, create_id_sym);
if (Qnil != v) {
size_t len = RSTRING_LEN(v) + 1;
oj_default_options.create_id = ALLOC_N(char, len);
strcpy((char*)oj_default_options.create_id, StringValuePtr(v));
oj_default_options.create_id_len = len - 1;
}
}
for (o = ynos; 0 != o->attr; o++) {
if (Qtrue != rb_funcall(opts, rb_intern("has_key?"), 1, o->sym)) {
continue;
}
if (Qnil != (v = rb_hash_lookup(opts, o->sym))) {
if (Qtrue == v) {
*o->attr = Yes;
} else if (Qfalse == v) {
*o->attr = No;
} else {
rb_raise(rb_eArgError, "%s must be true, false, or nil.", rb_id2name(SYM2ID(o->sym)));
}
}
}
// This is here only for backwards compatibility with the original Oj.
v = rb_hash_lookup(opts, ascii_only_sym);
if (Qtrue == v) {
oj_default_options.escape_mode = ASCIIEsc;
} else if (Qfalse == v) {
oj_default_options.escape_mode = JSONEsc;
}
return Qnil;
}
void
oj_parse_options(VALUE ropts, 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 },
{ allow_gc_sym, &copts->allow_gc },
{ Qnil, 0 }
};
YesNoOpt o;
if (rb_cHash == rb_obj_class(ropts)) {
VALUE v;
if (Qnil != (v = rb_hash_lookup(ropts, indent_sym))) {
if (rb_cFixnum != rb_obj_class(v)) {
rb_raise(rb_eArgError, ":indent must be a Fixnum.");
}
copts->indent = NUM2INT(v);
}
if (Qnil != (v = rb_hash_lookup(ropts, sec_prec_sym))) {
int n;
if (rb_cFixnum != rb_obj_class(v)) {
rb_raise(rb_eArgError, ":second_precision must be a Fixnum.");
}
n = NUM2INT(v);
if (0 > n) {
n = 0;
} else if (9 < n) {
n = 9;
}
copts->sec_prec = n;
}
if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) {
if (object_sym == v) {
copts->mode = ObjectMode;
} else if (strict_sym == v) {
copts->mode = StrictMode;
} else if (compat_sym == v) {
copts->mode = CompatMode;
} else if (null_sym == v) {
copts->mode = NullMode;
} else {
rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null.");
}
}
if (Qnil != (v = rb_hash_lookup(ropts, time_format_sym))) {
if (unix_sym == v) {
copts->time_format = UnixTime;
} 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, :xmlschema, or :ruby.");
}
}
if (Qnil != (v = rb_hash_lookup(ropts, escape_mode_sym))) {
if (json_sym == v) {
copts->escape_mode = JSONEsc;
} else if (xss_safe_sym == v) {
copts->escape_mode = XSSEsc;
} else if (ascii_sym == v) {
copts->escape_mode = ASCIIEsc;
} else {
rb_raise(rb_eArgError, ":encoding must be :json, :rails, or :ascii.");
}
}
if (Qnil != (v = rb_hash_lookup(ropts, bigdecimal_load_sym))) {
if (bigdecimal_sym == v || Qtrue == v) {
copts->bigdec_load = BigDec;
} else if (float_sym == v) {
copts->bigdec_load = FloatDec;
} else if (auto_sym == v || Qfalse == v) {
copts->bigdec_load = AutoDec;
} else {
rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto.");
}
}
if (Qtrue == rb_funcall(ropts, rb_intern("has_key?"), 1, create_id_sym)) {
v = rb_hash_lookup(ropts, create_id_sym);
if (Qnil == v) {
if (json_class != oj_default_options.create_id) {
xfree((char*)oj_default_options.create_id);
}
copts->create_id = 0;
copts->create_id_len = 0;
} else if (T_STRING == rb_type(v)) {
size_t len = RSTRING_LEN(v);
const char *str = StringValuePtr(v);
if (len != copts->create_id_len ||
0 != strcmp(copts->create_id, str)) {
copts->create_id = ALLOC_N(char, len + 1);
strcpy((char*)copts->create_id, str);
copts->create_id_len = len;
}
} else {
rb_raise(rb_eArgError, ":create_id must be string.");
}
}
for (o = ynos; 0 != o->attr; o++) {
if (Qnil != (v = rb_hash_lookup(ropts, o->sym))) {
if (Qtrue == v) {
*o->attr = Yes;
} else if (Qfalse == v) {
*o->attr = No;
} else {
rb_raise(rb_eArgError, "%s must be true or false.", rb_id2name(SYM2ID(o->sym)));
}
}
}
// This is here only for backwards compatibility with the original Oj.
v = rb_hash_lookup(ropts, ascii_only_sym);
if (Qtrue == v) {
copts->escape_mode = ASCIIEsc;
} else if (Qfalse == v) {
copts->escape_mode = JSONEsc;
}
}
}
/* Document-method: strict_load
* call-seq: strict_load(json, options) => Hash, Array, String, Fixnum, Float, true, false, or nil
*
* 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 teh JSON Object type can have
* repeating entries with the same key and Ruby Hash can not.
*
* 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.
*
* @param [String|IO] json JSON String or an Object that responds to read()
* @param [Hash] options load options (same as default_options)
*/
/* Document-method: compat_load
* call-seq: compat_load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil
*
* 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.
*
* 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.
*
* @param [String|IO] json JSON String or an Object that responds to read()
* @param [Hash] options load options (same as default_options)
*/
/* Document-method: object_load
* call-seq: object_load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil
*
* 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.
*
* Note: Oj is not able to automatically deserialize all classes that are a
* subclass of a Ruby Exception. Only exception that take one required string
* argument in the initialize() method are supported. This is an example of how
* to write an Exception subclass that supports both a single string intializer
* and an Exception as an argument. Additional optional arguments can be added
* as well.
*
* The reason for this restriction has to do with a design decision on the part
* of the Ruby developers. Exceptions are special Objects. They do not follow the
* rules of other Objects. Exceptions have 'mesg' and a 'bt' attribute. Note that
* these are not '@mesg' and '@bt'. They can not be set using the normal C or
* Ruby calls. The only way I have found to set the 'mesg' attribute is through
* the initializer. Unfortunately that means any subclass that provides a
* different initializer can not be automatically decoded. A way around this is
* to use a create function but this example shows an alternative.
*
* @param [String|IO] json JSON String or an Object that responds to read()
* @param [Hash] options load options (same as default_options)
*/
/* call-seq: load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil
*
* 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.
*
* @param [String|IO] json JSON String or an Object that responds to read()
* @param [Hash] options load options (same as default_options)
*/
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 != (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) {
mode = CompatMode;
} else if (null_sym == v) {
mode = NullMode;
} else {
rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null.");
}
}
}
switch (mode) {
case StrictMode:
return oj_strict_parse(argc, argv, self);
case NullMode:
case CompatMode:
return oj_compat_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) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil
*
* 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.
*
* If the input file is not a valid JSON document (an empty file is not a valid
* JSON document) an exception is raised.
*
* @param [String] path path to a file containing a JSON document
* @param [Hash] options load options (same as default_options)
*/
static VALUE
load_file(int argc, VALUE *argv, VALUE self) {
char *path;
char *json;
FILE *f;
unsigned long len;
Mode mode = oj_default_options.mode;
Check_Type(*argv, T_STRING);
path = StringValuePtr(*argv);
if (0 == (f = fopen(path, "r"))) {
rb_raise(rb_eIOError, "%s", strerror(errno));
}
fseek(f, 0, SEEK_END);
len = ftell(f);
json = ALLOC_N(char, len + 1);
fseek(f, 0, SEEK_SET);
if (len != fread(json, 1, len, f)) {
xfree(json);
fclose(f);
rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")), "Failed to read %ld bytes from %s.", len, path);
}
fclose(f);
json[len] = '\0';
if (1 > argc) {
rb_raise(rb_eArgError, "Wrong number of arguments to load().");
}
if (2 <= argc) {
VALUE ropts = argv[1];
VALUE v;
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) {
mode = CompatMode;
} else if (null_sym == v) {
mode = NullMode;
} else {
rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null.");
}
}
}
// The json string is freed in the parser when it is finished with it.
switch (mode) {
case StrictMode:
return oj_strict_parse_cstr(argc, argv, json, len);
case NullMode:
case CompatMode:
return oj_compat_parse_cstr(argc, argv, json, len);
case ObjectMode:
default:
break;
}
return oj_object_parse_cstr(argc, argv, json, len);
}
/* 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.
*
* @param [String|IO] doc JSON String or IO to load
* @return [Hash|Array|String|Fixnum|Bignum|BigDecimal|nil|True|False]
*/
static VALUE
safe_load(VALUE self, VALUE doc) {
struct _ParseInfo pi;
VALUE args[1];
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);
}
/* 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.
*
* @param [Oj::Saj] handler responds to Oj::Saj methods
* @param [IO|String] io IO Object to read from
*/
/* 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.
*
* @param [Oj::ScHandler] handler responds to Oj::ScHandler methods
* @param [IO|String] io IO Object to read from
*/
/* call-seq: dump(obj, options) => json-string
*
* Dumps an Object (obj) to a string.
* @param [Object] obj Object to serialize as an JSON document String
* @param [Hash] options same as default_options
*/
static VALUE
dump(int argc, VALUE *argv, VALUE self) {
char buf[4096];
struct _Out out;
struct _Options copts = oj_default_options;
VALUE rstr;
if (2 == argc) {
oj_parse_options(argv[1], &copts);
}
out.buf = buf;
out.end = buf + sizeof(buf) - 10;
out.allocated = 0;
oj_dump_obj_to_json(*argv, &copts, &out);
if (0 == out.buf) {
rb_raise(rb_eNoMemError, "Not enough memory.");
}
rstr = rb_str_new2(out.buf);
rstr = oj_encode(rstr);
if (out.allocated) {
xfree(out.buf);
}
return rstr;
}
/* call-seq: to_file(file_path, obj, options)
*
* Dumps an Object to the specified file.
* @param [String] file_path file path to write the JSON document to
* @param [Object] obj Object to serialize as an JSON document String
* @param [Hash] options formating options
* @param [Fixnum] :indent format expected
* @param [true|false] :circular allow circular references, default: false
*/
static VALUE
to_file(int argc, VALUE *argv, VALUE self) {
struct _Options copts = oj_default_options;
if (3 == argc) {
oj_parse_options(argv[2], &copts);
}
Check_Type(*argv, T_STRING);
oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
return Qnil;
}
/* call-seq: to_stream(io, obj, options)
*
* Dumps an Object to the specified IO stream.
* @param [IO] io IO stream to write the JSON document to
* @param [Object] obj Object to serialize as an JSON document String
* @param [Hash] options formating options
* @param [Fixnum] :indent format expected
* @param [true|false] :circular 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;
}
static void
str_writer_free(void *ptr) {
StrWriter sw;
if (0 == ptr) {
return;
}
sw = (StrWriter)ptr;
xfree(sw->out.buf);
xfree(sw->types);
xfree(ptr);
}
/* Document-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 tha calling to_s() before
* construction is complete will return the document in it's current state.
*/
static void
str_writer_init(StrWriter sw) {
sw->opts = oj_default_options;
sw->depth = 0;
sw->types = ALLOC_N(char, 256);
sw->types_end = sw->types + 256;
*sw->types = '\0';
sw->out.buf = ALLOC_N(char, 4096);
sw->out.end = sw->out.buf + 4086;
sw->out.allocated = 1;
sw->out.cur = sw->out.buf;
*sw->out.cur = '\0';
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;
}
/* call-seq: new(options)
*
* Creates a new StringWriter.
* @param [Hash] options formating options
*/
static VALUE
str_writer_new(int argc, VALUE *argv, VALUE self) {
StrWriter sw = ALLOC(struct _StrWriter);
str_writer_init(sw);
if (1 == argc) {
oj_parse_options(argv[0], &sw->opts);
}
sw->out.indent = sw->opts.indent;
return Data_Wrap_Struct(oj_string_writer_class, 0, str_writer_free, sw);
}
/* 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.
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
str_writer_push_object(int argc, VALUE *argv, VALUE self) {
StrWriter sw = (StrWriter)DATA_PTR(self);
switch (argc) {
case 0:
oj_str_writer_push_object(sw, 0);
break;
case 1:
if (Qnil == argv[0]) {
oj_str_writer_push_object(sw, 0);
} else {
rb_check_type(argv[0], T_STRING);
oj_str_writer_push_object(sw, StringValuePtr(argv[0]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
break;
}
if (rb_block_given_p()) {
rb_yield(Qnil);
oj_str_writer_pop(sw);
}
return Qnil;
}
/* 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.
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
str_writer_push_array(int argc, VALUE *argv, VALUE self) {
StrWriter sw = (StrWriter)DATA_PTR(self);
switch (argc) {
case 0:
oj_str_writer_push_array(sw, 0);
break;
case 1:
if (Qnil == argv[0]) {
oj_str_writer_push_array(sw, 0);
} else {
rb_check_type(argv[0], T_STRING);
oj_str_writer_push_array(sw, StringValuePtr(argv[0]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
break;
}
if (rb_block_given_p()) {
rb_yield(Qnil);
oj_str_writer_pop(sw);
}
return Qnil;
}
/* call-seq: push_value(value, key=nil)
*
* Pushes a value onto the JSON document.
* @param [Object] value value to add to the JSON document
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
str_writer_push_value(int argc, VALUE *argv, VALUE self) {
switch (argc) {
case 1:
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0);
break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0);
} else {
rb_check_type(argv[1], T_STRING);
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'.");
break;
}
return Qnil;
}
/* 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.
* @param [String] value JSON document to add to the JSON document
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
str_writer_push_json(int argc, VALUE *argv, VALUE self) {
rb_check_type(argv[0], T_STRING);
switch (argc) {
case 1:
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0);
break;
case 2:
if (Qnil == argv[1]) {
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0);
} else {
rb_check_type(argv[1], T_STRING);
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'.");
break;
}
return Qnil;
}
/* call-seq: pop()
*
* Pops up a level in the JSON document closing the array or object that is
* currently open.
*/
static VALUE
str_writer_pop(VALUE self) {
oj_str_writer_pop((StrWriter)DATA_PTR(self));
return Qnil;
}
/* call-seq: pop_all()
*
* Pops all level in the JSON document closing all the array or object that is
* currently open.
*/
static VALUE
str_writer_pop_all(VALUE self) {
oj_str_writer_pop_all((StrWriter)DATA_PTR(self));
return Qnil;
}
/* call-seq: reset()
*
* Reset the writer back to the empty state.
*/
static VALUE
str_writer_reset(VALUE self) {
StrWriter sw = (StrWriter)DATA_PTR(self);
sw->depth = 0;
*sw->types = '\0';
sw->out.cur = sw->out.buf;
*sw->out.cur = '\0';
return Qnil;
}
/* call-seq: to_s()
*
* Returns the JSON document string in what ever state the construction is at.
*/
static VALUE
str_writer_to_s(VALUE self) {
StrWriter sw = (StrWriter)DATA_PTR(self);
VALUE rstr = rb_str_new(sw->out.buf, sw->out.cur - sw->out.buf);
return oj_encode(rstr);
}
// StreamWriter
static void
stream_writer_free(void *ptr) {
StreamWriter sw;
if (0 == ptr) {
return;
}
sw = (StreamWriter)ptr;
xfree(sw->sw.out.buf);
xfree(sw->sw.types);
xfree(ptr);
}
static void
stream_writer_write(StreamWriter sw) {
ssize_t size = sw->sw.out.cur - sw->sw.out.buf;
switch (sw->type) {
case STRING_IO:
rb_funcall(sw->stream, oj_write_id, 1, rb_str_new(sw->sw.out.buf, size));
break;
case STREAM_IO:
rb_funcall(sw->stream, oj_write_id, 1, rb_str_new(sw->sw.out.buf, size));
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.");
}
}
static void
stream_writer_reset_buf(StreamWriter sw) {
sw->sw.out.cur = sw->sw.out.buf;
*sw->sw.out.cur = '\0';
}
/* call-seq: new(options)
*
* Creates a new StreamWriter.
* @param [Hash] options formating options
*/
/* call-seq: new(options)
*
* Creates a new StreamWriter.
* @param [Hash] options formating 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))) {
type = FILE_IO;
fd = FIX2INT(s);
#endif
} else if (rb_respond_to(stream, oj_write_id)) {
type = STREAM_IO;
} else {
rb_raise(rb_eArgError, "expected an IO Object.");
}
sw = ALLOC(struct _StreamWriter);
str_writer_init(&sw->sw);
if (1 == argc) {
oj_parse_options(argv[0], &sw->sw.opts);
}
sw->stream = stream;
sw->type = type;
sw->fd = fd;
return Data_Wrap_Struct(oj_stream_writer_class, 0, stream_writer_free, sw);
}
/* 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.
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
stream_writer_push_object(int argc, VALUE *argv, VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
stream_writer_reset_buf(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 {
rb_check_type(argv[0], T_STRING);
oj_str_writer_push_object(&sw->sw, StringValuePtr(argv[0]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
break;
}
stream_writer_write(sw);
return Qnil;
}
/* 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.
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
stream_writer_push_array(int argc, VALUE *argv, VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
stream_writer_reset_buf(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 {
rb_check_type(argv[0], T_STRING);
oj_str_writer_push_array(&sw->sw, StringValuePtr(argv[0]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'.");
break;
}
stream_writer_write(sw);
return Qnil;
}
/* call-seq: push_value(value, key=nil)
*
* Pushes a value onto the JSON document.
* @param [Object] value value to add to the JSON document
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
stream_writer_push_value(int argc, VALUE *argv, VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
stream_writer_reset_buf(sw);
switch (argc) {
case 1:
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0);
break;
case 2:
if (Qnil == argv[0]) {
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0);
} else {
rb_check_type(argv[1], T_STRING);
oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'.");
break;
}
stream_writer_write(sw);
return Qnil;
}
/* call-seq: push_value(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.
* @param [Object] value value to add to the JSON document
* @param [String] key the key if adding to an object in the JSON document
*/
static VALUE
stream_writer_push_json(int argc, VALUE *argv, VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
rb_check_type(argv[0], T_STRING);
stream_writer_reset_buf(sw);
switch (argc) {
case 1:
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0);
break;
case 2:
if (Qnil == argv[0]) {
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0);
} else {
rb_check_type(argv[1], T_STRING);
oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1]));
}
break;
default:
rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'.");
break;
}
stream_writer_write(sw);
return Qnil;
}
/* call-seq: pop()
*
* Pops up a level in the JSON document closing the array or object that is
* currently open.
*/
static VALUE
stream_writer_pop(VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
stream_writer_reset_buf(sw);
oj_str_writer_pop(&sw->sw);
stream_writer_write(sw);
return Qnil;
}
/* call-seq: pop_all()
*
* Pops all level in the JSON document closing all the array or object that is
* currently open.
*/
static VALUE
stream_writer_pop_all(VALUE self) {
StreamWriter sw = (StreamWriter)DATA_PTR(self);
stream_writer_reset_buf(sw);
oj_str_writer_pop_all(&sw->sw);
stream_writer_write(sw);
return Qnil;
}
// Mimic JSON section
static VALUE
mimic_dump(int argc, VALUE *argv, VALUE self) {
char buf[4096];
struct _Out out;
struct _Options copts = oj_default_options;
VALUE rstr;
out.buf = buf;
out.end = buf + sizeof(buf) - 10;
out.allocated = 0;
oj_dump_obj_to_json(*argv, &copts, &out);
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]) {
VALUE io = argv[1];
VALUE args[1];
*args = rstr;
rb_funcall2(io, oj_write_id, 1, args);
rstr = io;
}
if (out.allocated) {
xfree(out.buf);
}
return rstr;
}
// This is the signature for the hash_foreach callback also.
static int
mimic_walk(VALUE key, VALUE obj, VALUE proc) {
switch (rb_type(obj)) {
case T_HASH:
rb_hash_foreach(obj, mimic_walk, proc);
break;
case T_ARRAY:
{
VALUE *np = RARRAY_PTR(obj);
size_t cnt = RARRAY_LEN(obj);
for (; 0 < cnt; cnt--, np++) {
mimic_walk(Qnil, *np, proc);
}
break;
}
default:
break;
}
if (Qnil == proc) {
if (rb_block_given_p()) {
rb_yield(obj);
}
} else {
#if HAS_PROC_WITH_BLOCK
VALUE args[1];
*args = obj;
rb_proc_call_with_block(proc, 1, args, Qnil);
#else
rb_raise(rb_eNotImpError, "Calling a Proc with a block not supported in this version. Use func() {|x| } syntax instead.");
#endif
}
return ST_CONTINUE;
}
static VALUE
mimic_load(int argc, VALUE *argv, VALUE self) {
VALUE obj = load(1, argv, self);
VALUE p = Qnil;
if (2 <= argc) {
p = argv[1];
}
mimic_walk(Qnil, obj, p);
return obj;
}
static VALUE
mimic_dump_load(int argc, VALUE *argv, VALUE self) {
if (1 > argc) {
rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)");
} else if (T_STRING == rb_type(*argv)) {
return mimic_load(argc, argv, self);
} else {
return mimic_dump(argc, argv, self);
}
return Qnil;
}
static VALUE
mimic_generate_core(int argc, VALUE *argv, Options copts) {
char buf[4096];
struct _Out out;
VALUE rstr;
out.buf = buf;
out.end = buf + sizeof(buf) - 10;
out.allocated = 0;
if (2 == argc && Qnil != argv[1]) {
struct _DumpOpts dump_opts;
VALUE ropts = argv[1];
VALUE v;
memset(&dump_opts, 0, sizeof(dump_opts)); // may not be needed
if (T_HASH != rb_type(ropts)) {
rb_raise(rb_eArgError, "options must be a hash.");
}
if (Qnil != (v = rb_hash_lookup(ropts, indent_sym))) {
rb_check_type(v, T_STRING);
if (0 == copts->dump_opts) {
copts->dump_opts = &dump_opts;
}
copts->dump_opts->indent = StringValuePtr(v);
copts->dump_opts->indent_size = (uint8_t)strlen(copts->dump_opts->indent);
}
if (Qnil != (v = rb_hash_lookup(ropts, space_sym))) {
rb_check_type(v, T_STRING);
if (0 == copts->dump_opts) {
copts->dump_opts = &dump_opts;
}
copts->dump_opts->after_sep = StringValuePtr(v);
copts->dump_opts->after_size = (uint8_t)strlen(copts->dump_opts->after_sep);
}
if (Qnil != (v = rb_hash_lookup(ropts, space_before_sym))) {
rb_check_type(v, T_STRING);
if (0 == copts->dump_opts) {
copts->dump_opts = &dump_opts;
}
copts->dump_opts->before_sep = StringValuePtr(v);
copts->dump_opts->before_size = (uint8_t)strlen(copts->dump_opts->before_sep);
}
if (Qnil != (v = rb_hash_lookup(ropts, object_nl_sym))) {
rb_check_type(v, T_STRING);
if (0 == copts->dump_opts) {
copts->dump_opts = &dump_opts;
}
copts->dump_opts->hash_nl = StringValuePtr(v);
copts->dump_opts->hash_size = (uint8_t)strlen(copts->dump_opts->hash_nl);
}
if (Qnil != (v = rb_hash_lookup(ropts, array_nl_sym))) {
rb_check_type(v, T_STRING);
if (0 == copts->dump_opts) {
copts->dump_opts = &dump_opts;
}
copts->dump_opts->array_nl = StringValuePtr(v);
copts->dump_opts->array_size = (uint8_t)strlen(copts->dump_opts->array_nl);
}
// :allow_nan is not supported as Oj always allows_nan
// :max_nesting is always set to 100
}
oj_dump_obj_to_json(*argv, copts, &out);
if (0 == out.buf) {
rb_raise(rb_eNoMemError, "Not enough memory.");
}
rstr = rb_str_new2(out.buf);
rstr = oj_encode(rstr);
if (out.allocated) {
xfree(out.buf);
}
return rstr;
}
static VALUE
mimic_generate(int argc, VALUE *argv, VALUE self) {
struct _Options copts = oj_default_options;
return mimic_generate_core(argc, argv, &copts);
}
static VALUE
mimic_pretty_generate(int argc, VALUE *argv, VALUE self) {
struct _Options copts = oj_default_options;
struct _DumpOpts dump_opts;
dump_opts.indent = " ";
dump_opts.indent_size = (uint8_t)strlen(dump_opts.indent);
dump_opts.before_sep = " ";
dump_opts.before_size = (uint8_t)strlen(dump_opts.before_sep);
dump_opts.after_sep = " ";
dump_opts.after_size = (uint8_t)strlen(dump_opts.after_sep);
dump_opts.hash_nl = "\n";
dump_opts.hash_size = (uint8_t)strlen(dump_opts.hash_nl);
dump_opts.array_nl = "\n";
dump_opts.array_size = (uint8_t)strlen(dump_opts.array_nl);
copts.dump_opts = &dump_opts;
return mimic_generate_core(argc, argv, &copts);
}
static VALUE
mimic_parse(int argc, VALUE *argv, VALUE self) {
struct _ParseInfo pi;
VALUE args[1];
if (argc < 1) {
rb_raise(rb_eArgError, "Wrong number of arguments to parse.");
}
oj_set_compat_callbacks(&pi);
pi.options = oj_default_options;
pi.options.auto_define = No;
if (2 <= argc) {
VALUE ropts = argv[1];
VALUE v;
if (T_HASH != rb_type(ropts)) {
rb_raise(rb_eArgError, "options must be a hash.");
}
if (Qnil != (v = rb_hash_lookup(ropts, symbolize_names_sym))) {
pi.options.sym_key = (Qtrue == v) ? Yes : No;
}
if (Qnil != (v = rb_hash_lookup(ropts, create_additions_sym))) {
if (Qfalse == v) {
oj_set_strict_callbacks(&pi);
}
}
// :allow_nan is not supported as Oj always allows nan
// :max_nesting is ignored as Oj has not nesting limit
// :object_class is always Hash
// :array_class is always Array
}
*args = *argv;
return oj_pi_parse(1, args, &pi, 0, 0);
}
static VALUE
mimic_recurse_proc(VALUE self, VALUE obj) {
rb_need_block();
mimic_walk(Qnil, obj, Qnil);
return Qnil;
}
static VALUE
no_op1(VALUE self, VALUE obj) {
return Qnil;
}
static VALUE
mimic_create_id(VALUE self, VALUE id) {
Check_Type(id, T_STRING);
if (0 != oj_default_options.create_id) {
if (json_class != oj_default_options.create_id) {
xfree((char*)oj_default_options.create_id);
}
oj_default_options.create_id = 0;
oj_default_options.create_id_len = 0;
}
if (Qnil != id) {
size_t len = RSTRING_LEN(id) + 1;
oj_default_options.create_id = ALLOC_N(char, len);
strcpy((char*)oj_default_options.create_id, StringValuePtr(id));
oj_default_options.create_id_len = len - 1;
}
return id;
}
/* Document-method: mimic_JSON
* call-seq: mimic_JSON() => Module
*
* 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 :ascii.
*/
static VALUE
define_mimic_json(int argc, VALUE *argv, VALUE self) {
VALUE ext;
VALUE dummy;
// 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"))) {
mimic = rb_const_get_at(rb_cObject, rb_intern("JSON"));
} else {
mimic = rb_define_module("JSON");
}
if (rb_const_defined_at(mimic, rb_intern("Ext"))) {
ext = rb_const_get_at(mimic, rb_intern("Ext"));
} else {
ext = rb_define_module_under(mimic, "Ext");
}
if (!rb_const_defined_at(ext, rb_intern("Parser"))) {
dummy = rb_define_class_under(ext, "Parser", rb_cObject);
}
if (!rb_const_defined_at(ext, rb_intern("Generator"))) {
dummy = rb_define_class_under(ext, "Generator", rb_cObject);
}
// convince Ruby that the json gem has already been loaded
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);
}
}
dummy = rb_gv_get("$VERBOSE");
rb_gv_set("$VERBOSE", Qfalse);
rb_define_module_function(mimic, "parser=", no_op1, 1);
rb_define_module_function(mimic, "generator=", no_op1, 1);
rb_define_module_function(mimic, "create_id=", mimic_create_id, 1);
rb_define_module_function(mimic, "dump", mimic_dump, -1);
rb_define_module_function(mimic, "load", mimic_load, -1);
rb_define_module_function(mimic, "restore", mimic_load, -1);
rb_define_module_function(mimic, "recurse_proc", mimic_recurse_proc, 1);
rb_define_module_function(mimic, "[]", mimic_dump_load, -1);
rb_define_module_function(mimic, "generate", mimic_generate, -1);
rb_define_module_function(mimic, "fast_generate", mimic_generate, -1);
rb_define_module_function(mimic, "pretty_generate", mimic_pretty_generate, -1);
/* for older versions of JSON, the deprecated unparse methods */
rb_define_module_function(mimic, "unparse", mimic_generate, -1);
rb_define_module_function(mimic, "fast_unparse", mimic_generate, -1);
rb_define_module_function(mimic, "pretty_unparse", mimic_pretty_generate, -1);
rb_define_module_function(mimic, "parse", mimic_parse, -1);
rb_define_module_function(mimic, "parse!", mimic_parse, -1);
rb_gv_set("$VERBOSE", dummy);
array_nl_sym = ID2SYM(rb_intern("array_nl")); rb_gc_register_address(&array_nl_sym);
create_additions_sym = ID2SYM(rb_intern("create_additions")); rb_gc_register_address(&create_additions_sym);
object_nl_sym = ID2SYM(rb_intern("object_nl")); rb_gc_register_address(&object_nl_sym);
space_before_sym = ID2SYM(rb_intern("space_before")); rb_gc_register_address(&space_before_sym);
space_sym = ID2SYM(rb_intern("space")); rb_gc_register_address(&space_sym);
symbolize_names_sym = ID2SYM(rb_intern("symbolize_names")); rb_gc_register_address(&symbolize_names_sym);
oj_default_options.mode = CompatMode;
oj_default_options.escape_mode = ASCIIEsc;
return mimic;
}
/*
extern void oj_hash_test();
static VALUE
hash_test(VALUE self) {
oj_hash_test();
return Qnil;
}
*/
#if !HAS_ENCODING_SUPPORT
static VALUE
iconv_encoder(VALUE x) {
VALUE iconv;
rb_require("iconv");
iconv = rb_const_get(rb_cObject, rb_intern("Iconv"));
return rb_funcall(iconv, rb_intern("new"), 2, rb_str_new2("ASCII//TRANSLIT"), rb_str_new2("UTF-8"));
}
static VALUE
iconv_rescue(VALUE x) {
return Qnil;
}
#endif
static VALUE
protect_require(VALUE x) {
rb_require("bigdecimal");
return Qnil;
}
void Init_oj() {
int err = 0;
Oj = rb_define_module("Oj");
oj_cstack_class = rb_define_class_under(Oj, "CStack", rb_cObject);
oj_string_writer_class = rb_define_class_under(Oj, "StringWriter", rb_cObject);
rb_define_module_function(oj_string_writer_class, "new", str_writer_new, -1);
rb_define_method(oj_string_writer_class, "push_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);
oj_stream_writer_class = rb_define_class_under(Oj, "StreamWriter", rb_cObject);
rb_define_module_function(oj_stream_writer_class, "new", stream_writer_new, -1);
rb_define_method(oj_stream_writer_class, "push_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_require("time");
rb_require("date");
// On Rubinius the require fails but can be done from a ruby file.
rb_protect(protect_require, Qnil, &err);
#if NEEDS_RATIONAL
rb_require("rational");
#endif
rb_require("stringio");
#if HAS_ENCODING_SUPPORT
oj_utf8_encoding = rb_enc_find("UTF-8");
#else
// need an option to turn this on
oj_utf8_encoding = rb_rescue(iconv_encoder, Qnil, iconv_rescue, Qnil);
oj_utf8_encoding = Qnil;
#endif
//rb_define_module_function(Oj, "hash_test", hash_test, 0);
rb_define_module_function(Oj, "default_options", get_def_opts, 0);
rb_define_module_function(Oj, "default_options=", set_def_opts, 1);
rb_define_module_function(Oj, "mimic_JSON", 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, "dump", dump, -1);
rb_define_module_function(Oj, "to_file", to_file, -1);
rb_define_module_function(Oj, "to_stream", to_stream, -1);
rb_define_module_function(Oj, "saj_parse", oj_saj_parse, -1);
rb_define_module_function(Oj, "sc_parse", oj_sc_parse, -1);
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_error_id = rb_intern("error");
oj_fileno_id = rb_intern("fileno");
oj_hash_end_id = rb_intern("hash_end");
oj_hash_set_id = rb_intern("hash_set");
oj_hash_start_id = rb_intern("hash_start");
oj_iconv_id = rb_intern("iconv");
oj_instance_variables_id = rb_intern("instance_variables");
oj_json_create_id = rb_intern("json_create");
oj_length_id = rb_intern("length");
oj_new_id = rb_intern("new");
oj_read_id = rb_intern("read");
oj_string_id = rb_intern("string");
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_offset_id = rb_intern("utc_offset");
oj_write_id = rb_intern("write");
oj_bag_class = rb_const_get_at(Oj, rb_intern("Bag"));
oj_bigdecimal_class = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
oj_date_class = rb_const_get(rb_cObject, rb_intern("Date"));
oj_datetime_class = rb_const_get(rb_cObject, rb_intern("DateTime"));
oj_parse_error_class = rb_const_get_at(Oj, rb_intern("ParseError"));
oj_stringio_class = rb_const_get(rb_cObject, rb_intern("StringIO"));
oj_struct_class = rb_const_get(rb_cObject, rb_intern("Struct"));
oj_time_class = rb_const_get(rb_cObject, rb_intern("Time"));
allow_gc_sym = ID2SYM(rb_intern("allow_gc")); rb_gc_register_address(&allow_gc_sym);
ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&ascii_only_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);
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_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);
escape_mode_sym = ID2SYM(rb_intern("escape_mode")); rb_gc_register_address(&escape_mode_sym);
float_sym = ID2SYM(rb_intern("float")); rb_gc_register_address(&float_sym);
indent_sym = ID2SYM(rb_intern("indent")); rb_gc_register_address(&indent_sym);
json_sym = ID2SYM(rb_intern("json")); rb_gc_register_address(&json_sym);
mode_sym = ID2SYM(rb_intern("mode")); rb_gc_register_address(&mode_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);
ruby_sym = ID2SYM(rb_intern("ruby")); rb_gc_register_address(&ruby_sym);
sec_prec_sym = ID2SYM(rb_intern("second_precision"));rb_gc_register_address(&sec_prec_sym);
strict_sym = ID2SYM(rb_intern("strict")); rb_gc_register_address(&strict_sym);
symbol_keys_sym = ID2SYM(rb_intern("symbol_keys")); rb_gc_register_address(&symbol_keys_sym);
time_format_sym = ID2SYM(rb_intern("time_format")); rb_gc_register_address(&time_format_sym);
unix_sym = ID2SYM(rb_intern("unix")); rb_gc_register_address(&unix_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);
oj_default_options.mode = ObjectMode;
oj_hash_init();
oj_odd_init();
#if USE_PTHREAD_MUTEX
pthread_mutex_init(&oj_cache_mutex, 0);
#elif USE_RB_MUTEX
oj_cache_mutex = rb_mutex_new();
rb_gc_register_address(&oj_cache_mutex);
#endif
oj_init_doc();
}
// mimic JSON documentation
/* Document-module: JSON
*
* JSON is a JSON parser. This module when defined by the Oj module is a
* faster replacement for the original.
*/
/* 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: create_id=
* call-seq: create_id=(id) -> String
*
* Sets the create_id tag to look for in JSON document. That key triggers the
* creation of a class with the same name.
*
* @param [nil|String] id new create_id
* @return the id
*/
/* Document-method: parser=
* call-seq: parser=(parser) -> nil
*
* Does nothing other than provide compatibiltiy.
* @param [Object] parser ignored
*/
/* Document-method: generator=
* call-seq: generator=(generator) -> nil
*
* Does nothing other than provide compatibiltiy.
* @param [Object] generator ignored
*/
/* Document-method: dump
* call-seq: dump(obj, anIO=nil, limit = nil) -> String
*
* Encodes an object as a JSON String.
*
* @param [Object] obj object to convert to encode as JSON
* @param [IO] anIO an IO that allows writing
* @param [Fixnum] limit ignored
*/
/* Document-method: load
* call-seq: load(source, proc=nil) -> Object
*
* 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 providedit is called with each nested
* element of the loaded Object.
*
* @param [String|IO] source JSON source
* @param [Proc] proc to yield to on each element or nil
*/
/* Document-method: restore
* call-seq: restore(source, proc=nil) -> Object
*
* 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 providedit is called with each nested
* element of the loaded Object.
*
* @param [String|IO] source JSON source
* @param [Proc] proc to yield to on each element or nil
*/
/* Document-method: recurse_proc
* call-seq: recurse_proc(obj, &proc) -> nil
*
* Yields to the proc for every element in the obj recursivly.
*
* @param [Hash|Array] obj object to walk
* @param [Proc] proc to yield to on each element
*/
/* Document-method: []
* call-seq: [](obj, opts={}) -> Object
*
* 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.
*
* @param [String|Hash|Array] obj object to convert
* @param [Hash] opts same options as either generate or parse
*/
/* Document-method: generate
* call-seq: generate(obj, opts=nil) -> String
*
* 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.
*
* @param [Object|Hash|Array] obj object to convert to a JSON String
* @param [Hash] opts options
* @param [String] :indent String to use for indentation
* @param [String] :space String placed after a , or : delimiter
* @param [String] :space_before String placed before a : delimiter
* @param [String] :object_nl String placed after a JSON object
* @param [String] :array_nl String placed after a JSON array
*/
/* Document-method: fast_generate
* call-seq: fast_generate(obj, opts=nil) -> String
* Same as generate().
* @see generate
*/
/* Document-method: pretty_generate
* call-seq: pretty_generate(obj, opts=nil) -> String
* Same as generate() but with different defaults for the spacing options.
* @see generate
*/
/* Document-method: parse
* call-seq: parse(source, opts=nil) -> Object
*
* 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.
*
* @param [String|IO] source source to parse
* @param [Hash] opts options
* @param [true|false] :symbolize_names flag indicating JSON object keys should be Symbols instead of Strings
* @param [true|false] :create_additions flag indicating a key matching +create_id+ in a JSON object should trigger the creation of Ruby Object
* @see create_id=
*/
/* Document-method: parse!
* call-seq: parse!(source, opts=nil) -> Object
* Same as parse().
* @see parse
*/
oj-2.5.3/ext/oj/odd.h 0000644 0000041 0000041 00000004467 12263716750 014334 0 ustar www-data www-data /* odd.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_ODD_H__
#define __OJ_ODD_H__
#include "ruby.h"
#define MAX_ODD_ARGS 10
typedef struct _Odd {
const char *classname;
size_t clen;
VALUE clas; // Ruby class
VALUE create_obj;
ID create_op;
int attr_cnt;
const char *attr_names[MAX_ODD_ARGS]; // 0 terminated attr IDs
ID attrs[MAX_ODD_ARGS]; // 0 terminated attr IDs
} *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);
#endif /* __OJ_ODD_H__ */
oj-2.5.3/ext/oj/encode.h 0000644 0000041 0000041 00000003710 12263716750 015011 0 ustar www-data www-data /* encode.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_ENCODE_H__
#define __OJ_ENCODE_H__
#include "ruby.h"
#if HAS_ENCODING_SUPPORT
#include "ruby/encoding.h"
#endif
static inline VALUE
oj_encode(VALUE rstr) {
#if HAS_ENCODING_SUPPORT
rb_enc_associate(rstr, oj_utf8_encoding);
#else
if (Qnil != oj_utf8_encoding) {
rstr = rb_funcall(oj_utf8_encoding, oj_iconv_id, 1, rstr);
}
#endif
return rstr;
}
#endif /* __OJ_ENCODE_H__ */
oj-2.5.3/ext/oj/parse.c 0000644 0000041 0000041 00000051204 12263716750 014662 0 ustar www-data www-data /* parse.c
* Copyright (c) 2013, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include "oj.h"
#include "parse.h"
#include "buf.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 1023
#define DEC_MAX 14
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 (; '\0' != *pi->cur; pi->cur++) {
if ('*' == *pi->cur && '/' == *(pi->cur + 1)) {
pi->cur += 2;
return;
} else if ('\0' == *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->key, parent->klen, rval);
if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) {
xfree((char*)parent->key);
parent->key = 0;
}
parent->next = NEXT_HASH_COMMA;
break;
case NEXT_HASH_NEW:
case NEXT_HASH_KEY:
case NEXT_HASH_COMMA:
case NEXT_NONE:
case NEXT_ARRAY_COMMA:
case NEXT_HASH_COLON:
default:
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next));
break;
}
}
}
static void
read_null(ParseInfo pi) {
if ('u' == *pi->cur++ && 'l' == *pi->cur++ && 'l' == *pi->cur++) {
add_value(pi, Qnil);
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected null");
}
}
static void
read_true(ParseInfo pi) {
if ('r' == *pi->cur++ && 'u' == *pi->cur++ && 'e' == *pi->cur++) {
add_value(pi, Qtrue);
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected true");
}
}
static void
read_false(ParseInfo pi) {
if ('a' == *pi->cur++ && 'l' == *pi->cur++ && 's' == *pi->cur++ && 'e' == *pi->cur++) {
add_value(pi, Qfalse);
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected false");
}
}
static uint32_t
read_hex(ParseInfo pi, const char *h) {
uint32_t b = 0;
int i;
for (i = 0; i < 4; i++, h++) {
b = b << 4;
if ('0' <= *h && *h <= '9') {
b += *h - '0';
} else if ('A' <= *h && *h <= 'F') {
b += *h - 'A' + 10;
} else if ('a' <= *h && *h <= 'f') {
b += *h - 'a' + 10;
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hex character");
return 0;
}
}
return b;
}
static void
unicode_to_chars(ParseInfo pi, Buf buf, uint32_t code) {
if (0x0000007F >= code) {
buf_append(buf, (char)code);
} else if (0x000007FF >= code) {
buf_append(buf, 0xC0 | (code >> 6));
buf_append(buf, 0x80 | (0x3F & code));
} else if (0x0000FFFF >= code) {
buf_append(buf, 0xE0 | (code >> 12));
buf_append(buf, 0x80 | ((code >> 6) & 0x3F));
buf_append(buf, 0x80 | (0x3F & code));
} else if (0x001FFFFF >= code) {
buf_append(buf, 0xF0 | (code >> 18));
buf_append(buf, 0x80 | ((code >> 12) & 0x3F));
buf_append(buf, 0x80 | ((code >> 6) & 0x3F));
buf_append(buf, 0x80 | (0x3F & code));
} else if (0x03FFFFFF >= code) {
buf_append(buf, 0xF8 | (code >> 24));
buf_append(buf, 0x80 | ((code >> 18) & 0x3F));
buf_append(buf, 0x80 | ((code >> 12) & 0x3F));
buf_append(buf, 0x80 | ((code >> 6) & 0x3F));
buf_append(buf, 0x80 | (0x3F & code));
} else if (0x7FFFFFFF >= code) {
buf_append(buf, 0xFC | (code >> 30));
buf_append(buf, 0x80 | ((code >> 24) & 0x3F));
buf_append(buf, 0x80 | ((code >> 18) & 0x3F));
buf_append(buf, 0x80 | ((code >> 12) & 0x3F));
buf_append(buf, 0x80 | ((code >> 6) & 0x3F));
buf_append(buf, 0x80 | (0x3F & code));
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid Unicode character");
}
}
// entered at /
static void
read_escaped_str(ParseInfo pi, const char *start) {
struct _Buf buf;
const char *s;
int cnt = (int)(pi->cur - start);
uint32_t code;
Val parent = stack_peek(&pi->stack);
buf_init(&buf);
if (0 < cnt) {
buf_append_string(&buf, start, cnt);
}
for (s = pi->cur; '"' != *s; s++) {
if ('\0' == *s) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated");
buf_cleanup(&buf);
return;
} else if ('\\' == *s) {
s++;
switch (*s) {
case 'n': buf_append(&buf, '\n'); break;
case 'r': buf_append(&buf, '\r'); break;
case 't': buf_append(&buf, '\t'); break;
case 'f': buf_append(&buf, '\f'); break;
case 'b': buf_append(&buf, '\b'); break;
case '"': buf_append(&buf, '"'); break;
case '/': buf_append(&buf, '/'); break;
case '\\': buf_append(&buf, '\\'); break;
case 'u':
s++;
if (0 == (code = read_hex(pi, s)) && err_has(&pi->err)) {
buf_cleanup(&buf);
return;
}
s += 3;
if (0x0000D800 <= code && code <= 0x0000DFFF) {
uint32_t c1 = (code - 0x0000D800) & 0x000003FF;
uint32_t c2;
s++;
if ('\\' != *s || 'u' != *(s + 1)) {
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:
pi->cur = s;
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character");
buf_cleanup(&buf);
return;
}
} else {
buf_append(&buf, *s);
}
}
if (0 == parent) {
pi->add_cstr(pi, buf.head, buf_len(&buf), start);
} else {
switch (parent->next) {
case NEXT_ARRAY_NEW:
case NEXT_ARRAY_ELEMENT:
pi->array_append_cstr(pi, buf.head, buf_len(&buf), start);
parent->next = NEXT_ARRAY_COMMA;
break;
case NEXT_HASH_NEW:
case NEXT_HASH_KEY:
// key will not be between pi->json and pi->cur.
parent->key = strdup(buf.head);
parent->klen = buf_len(&buf);
parent->k1 = *start;
parent->next = NEXT_HASH_COLON;
break;
case NEXT_HASH_VALUE:
pi->hash_set_cstr(pi, parent->key, parent->klen, buf.head, buf_len(&buf), start);
if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) {
xfree((char*)parent->key);
parent->key = 0;
}
parent->next = NEXT_HASH_COMMA;
break;
case NEXT_HASH_COMMA:
case NEXT_NONE:
case NEXT_ARRAY_COMMA:
case NEXT_HASH_COLON:
default:
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next));
break;
}
}
pi->cur = s + 1;
buf_cleanup(&buf);
}
static void
read_str(ParseInfo pi) {
const char *str = pi->cur;
Val parent = stack_peek(&pi->stack);
for (; '"' != *pi->cur; pi->cur++) {
if ('\0' == *pi->cur) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated");
return;
} else if ('\\' == *pi->cur) {
read_escaped_str(pi, str);
return;
}
}
if (0 == parent) { // simple add
pi->add_cstr(pi, str, pi->cur - str, str);
} else {
switch (parent->next) {
case NEXT_ARRAY_NEW:
case NEXT_ARRAY_ELEMENT:
pi->array_append_cstr(pi, str, pi->cur - str, str);
parent->next = NEXT_ARRAY_COMMA;
break;
case NEXT_HASH_NEW:
case NEXT_HASH_KEY:
parent->key = str;
parent->klen = pi->cur - str;
parent->k1 = *str;
parent->next = NEXT_HASH_COLON;
break;
case NEXT_HASH_VALUE:
pi->hash_set_cstr(pi, parent->key, parent->klen, str, pi->cur - str, str);
if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) {
xfree((char*)parent->key);
parent->key = 0;
}
parent->next = NEXT_HASH_COMMA;
break;
case NEXT_HASH_COMMA:
case NEXT_NONE:
case NEXT_ARRAY_COMMA:
case NEXT_HASH_COLON:
default:
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next));
break;
}
}
pi->cur++; // move past "
}
static void
read_num(ParseInfo pi) {
struct _NumInfo ni;
Val parent = stack_peek(&pi->stack);
int zero_cnt = 0;
ni.str = pi->cur;
ni.i = 0;
ni.num = 0;
ni.div = 1;
ni.len = 0;
ni.exp = 0;
ni.dec_cnt = 0;
ni.big = 0;
ni.infinity = 0;
ni.nan = 0;
ni.neg = 0;
ni.no_big = (FloatDec == pi->options.bigdec_load);
if ('-' == *pi->cur) {
pi->cur++;
ni.neg = 1;
} else if ('+' == *pi->cur) {
pi->cur++;
}
if ('I' == *pi->cur) {
if (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 {
for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) {
ni.dec_cnt++;
if (ni.big) {
ni.big++;
} else {
int d = (*pi->cur - '0');
if (0 == d) {
zero_cnt++;
} else {
zero_cnt = 0;
}
ni.i = ni.i * 10 + d;
if (LONG_MAX <= ni.i || DEC_MAX < ni.dec_cnt - zero_cnt) {
ni.big = 1;
}
}
}
if ('.' == *pi->cur) {
pi->cur++;
for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) {
int d = (*pi->cur - '0');
if (0 == d) {
zero_cnt++;
} else {
zero_cnt = 0;
}
ni.dec_cnt++;
ni.num = ni.num * 10 + d;
ni.div *= 10;
if (LONG_MAX <= ni.div || DEC_MAX < ni.dec_cnt - zero_cnt) {
ni.big = 1;
}
}
}
if ('e' == *pi->cur || 'E' == *pi->cur) {
int eneg = 0;
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 = 1;
}
}
if (eneg) {
ni.exp = -ni.exp;
}
}
ni.dec_cnt -= zero_cnt;
ni.len = pi->cur - ni.str;
}
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->key, parent->klen, &ni);
if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) {
xfree((char*)parent->key);
parent->key = 0;
}
parent->next = NEXT_HASH_COMMA;
break;
default:
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next));
break;
}
}
}
static void
array_start(ParseInfo pi) {
VALUE v = pi->start_array(pi);
stack_push(&pi->stack, v, NEXT_ARRAY_NEW);
}
static void
array_end(ParseInfo pi) {
Val array = stack_pop(&pi->stack);
if (0 == array) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected array close");
} else if (NEXT_ARRAY_COMMA != array->next && NEXT_ARRAY_NEW != array->next) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not an array close", oj_stack_next_string(array->next));
} else {
pi->end_array(pi);
add_value(pi, array->val);
}
}
static void
hash_start(ParseInfo pi) {
volatile VALUE v = pi->start_hash(pi);
stack_push(&pi->stack, v, NEXT_HASH_NEW);
}
static void
hash_end(ParseInfo pi) {
volatile Val hash = stack_peek(&pi->stack);
// leave hash on stack until just before
if (0 == hash) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected hash close");
} else if (NEXT_HASH_COMMA != hash->next && NEXT_HASH_NEW != hash->next) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a hash close", oj_stack_next_string(hash->next));
} else {
pi->end_hash(pi);
stack_pop(&pi->stack);
add_value(pi, hash->val);
}
}
static void
comma(ParseInfo pi) {
Val parent = stack_peek(&pi->stack);
if (0 == parent) {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma");
} else if (NEXT_ARRAY_COMMA == parent->next) {
parent->next = NEXT_ARRAY_ELEMENT;
} else if (NEXT_HASH_COMMA == parent->next) {
parent->next = NEXT_HASH_KEY;
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma");
}
}
static void
colon(ParseInfo pi) {
Val parent = stack_peek(&pi->stack);
if (0 != parent && NEXT_HASH_COLON == parent->next) {
parent->next = NEXT_HASH_VALUE;
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected colon");
}
}
void
oj_parse2(ParseInfo pi) {
pi->cur = pi->json;
err_init(&pi->err);
while (1) {
next_non_white(pi);
switch (*pi->cur++) {
case '{':
hash_start(pi);
break;
case '}':
hash_end(pi);
break;
case ':':
colon(pi);
break;
case '[':
array_start(pi);
break;
case ']':
array_end(pi);
break;
case ',':
comma(pi);
break;
case '"':
read_str(pi);
break;
case '+':
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'I':
case 'N':
pi->cur--;
read_num(pi);
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);
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;
}
}
}
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) { // fixnum
if (ni->big) {
if (256 > ni->len) {
char buf[256];
memcpy(buf, ni->str, ni->len);
buf[ni->len] = '\0';
rnum = rb_cstr_to_inum(buf, 10, 0);
} else {
char *buf = ALLOC_N(char, ni->len + 1);
memcpy(buf, ni->str, ni->len);
buf[ni->len] = '\0';
rnum = rb_cstr_to_inum(buf, 10, 0);
xfree(buf);
}
} else {
if (ni->neg) {
rnum = LONG2NUM(-ni->i);
} else {
rnum = LONG2NUM(ni->i);
}
}
} else { // decimal
if (ni->big) {
rnum = rb_funcall(oj_bigdecimal_class, oj_new_id, 1, rb_str_new(ni->str, ni->len));
if (ni->no_big) {
rnum = rb_funcall(rnum, rb_intern("to_f"), 0);
}
} else {
double d = (double)ni->i + (double)ni->num / (double)ni->div;
if (ni->neg) {
d = -d;
}
if (0 != ni->exp) {
d *= pow(10.0, ni->exp);
}
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[128];
va_start(ap, format);
vsnprintf(msg, sizeof(msg) - 1, format, ap);
va_end(ap);
pi->err.clas = err_clas;
_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;
}
VALUE
oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len) {
char *buf = 0;
volatile VALUE input;
volatile 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) {
oj_parse_options(argv[1], &pi->options);
}
pi->cbc = (void*)0;
if (0 != json) {
pi->json = json;
pi->end = json + len;
free_json = 1;
} else if (rb_type(input) == T_STRING) {
pi->json = rb_string_value_cstr((VALUE*)&input);
pi->end = pi->json + RSTRING_LEN(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);
pi->json = rb_string_value_cstr((VALUE*)&s);
pi->end = pi->json + RSTRING_LEN(s);
#if !IS_WINDOWS
} else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) {
int fd = FIX2INT(s);
ssize_t cnt;
size_t len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
buf = ALLOC_N(char, len + 1);
pi->json = buf;
pi->end = buf + len;
if (0 >= (cnt = read(fd, (char*)pi->json, len)) || cnt != (ssize_t)len) {
if (0 != buf) {
xfree(buf);
}
rb_raise(rb_eIOError, "failed to read from IO Object.");
}
((char*)pi->json)[len] = '\0';
/* skip UTF-8 BOM if present */
if (0xEF == (uint8_t)*pi->json && 0xBB == (uint8_t)pi->json[1] && 0xBF == (uint8_t)pi->json[2]) {
pi->json += 3;
}
#endif
} else if (rb_respond_to(input, oj_read_id)) {
s = rb_funcall2(input, oj_read_id, 0, 0);
pi->json = rb_string_value_cstr((VALUE*)&s);
pi->end = pi->json + RSTRING_LEN(s);
} else {
rb_raise(rb_eArgError, "strict_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);
result = stack_head_val(&pi->stack);
DATA_PTR(wrapped_stack) = 0;
if (No == pi->options.allow_gc) {
rb_gc_enable();
}
// proceed with cleanup
if (0 != pi->circ_array) {
oj_circ_array_free(pi->circ_array);
}
if (0 != buf) {
xfree(buf);
} else if (free_json) {
xfree(json);
}
stack_cleanup(&pi->stack);
if (0 != line) {
rb_jump_tag(line);
}
if (err_has(&pi->err)) {
oj_err_raise(&pi->err);
}
return result;
}
oj-2.5.3/ext/oj/parse.h 0000644 0000041 0000041 00000007005 12263716750 014667 0 ustar www-data www-data /* parse.h
* Copyright (c) 2011, Peter Ohler
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Peter Ohler nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __OJ_PARSE_H__
#define __OJ_PARSE_H__
#include
#include "ruby.h"
#include "oj.h"
#include "val_stack.h"
#include "circarray.h"
typedef struct _NumInfo {
int64_t i;
int64_t num;
int64_t div;
const char *str;
size_t len;
long exp;
int dec_cnt;
int big;
int infinity;
int nan;
int neg;
int no_big;
} *NumInfo;
typedef struct _ParseInfo {
const char *json;
const char *cur;
const char *end;
struct _Err err;
struct _Options options;
void *cbc;
struct _ValStack stack;
CircArray circ_array;
int expect_value;
VALUE (*start_hash)(struct _ParseInfo *pi);
void (*end_hash)(struct _ParseInfo *pi);
void (*hash_set_cstr)(struct _ParseInfo *pi, const char *key, size_t klen, const char *str, size_t len, const char *orig);
void (*hash_set_num)(struct _ParseInfo *pi, const char *key, size_t klen, NumInfo ni);
void (*hash_set_value)(struct _ParseInfo *pi, const char *key, size_t klen, 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);
} *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);
extern VALUE oj_num_as_value(NumInfo ni);
extern void oj_set_strict_callbacks(ParseInfo pi);
extern void oj_set_compat_callbacks(ParseInfo pi);
#endif /* __OJ_PARSE_H__ */
oj-2.5.3/README.md 0000644 0000041 0000041 00000017420 12263716750 013455 0 ustar www-data www-data # Oj gem
A fast JSON parser and Object marshaller as a Ruby gem.
## Installation
gem install oj
## Documentation
*Documentation*: http://www.ohler.com/oj
## Source
*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.
## Build Status
[](http://travis-ci.org/ohler55/oj)
### Current Release 2.5.3
- Added support for blocks with StringWriter
### Current Release 2.5.2
- Fixed indent problem with StringWriter so it now indents properly
### Current Release 2.5.1
- Added push_json() to the StringWriter and StreamWriter to allow raw JSON to be added to a JSON document being constructed.
[Older release notes](http://www.ohler.com/dev/oj_misc/release_notes.html).
## Description
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 the
common Ruby JSON parsers. So far is has achieved that at about 2 time faster
than any other Ruby JSON parser and 3 or more times faster writing JSON.
Oj has several dump or serialization modes which control how Objects 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. The default mode
is the :object mode.
- :strict mode will only allow the 7 basic JSON types to be serialized. Any
other Object will raise and Exception.
- :null mode replaces any Object that is not one of the JSON 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 mode is is the compatible with other systems. It will serialize any
Object but will check to see if the Object implements a to_hash() or to_json()
method. If either exists that method is used for serializing the Object. The
to_hash() is more flexible and produces more consistent output so it has a
preference over the to_json() method. If neither the to_json() or to_hash()
methods exist then the Oj internal Object variable encoding is used.
Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, 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.
Oj is also compatible with Rails. Just make sure the Oj gem is installed and
[multi_json](https://github.com/intridea/multi_json) will pick it up and use it.
Oj offers a few alternative APIs for processing JSON. The fastest one 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.
Other parsers, the Oj::Saj and the Oj::ScHandler API 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 are
possible. The API is simple to use but does require a different approach than
the conventional parse followed by access approach used by conventional JSON
parsing.
## Proper Use
Two settings in Oj are useful for parsing but do expose a vunerability if used from an untrusted source. Symbolizing
keys can be used to cause memory to be filled up since Ruby does not garbage collect Symbols. The same is true for auto
defining classes. Memory can be exhausted if too many classes are automatically defined. Auto defining is a useful
feature during development and from trusted sources but it does allow too many classes to be created in the object load
mode and auto defined is used with an untrusted source. The Oj.strict_load() method sets 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 the Objects created. 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 Strings. Always check inputs from untrusted sources.
## Release Notes
### Simple JSON Writing and Parsing:
```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
```
# Links
## 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
[Oj Object Encoding Format](http://www.ohler.com/dev/oj_misc/encoding_format.html) describes the OJ Object JSON encoding format.
[Need for Speed](http://www.ohler.com/dev/need_for_speed/need_for_speed.html) for an overview of how Oj::Doc was designed.
### License:
Copyright (c) 2012, Peter Ohler
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Peter Ohler nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.