Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements for decimal_class: BigDecimal #526

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 29 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ task :check_env do
end

desc "Testing library (pure ruby)"
task :test_pure => [ :set_env_pure, :check_env, :do_test_pure ]
task :test_pure => [ :set_env_pure, :check_env, :do_test_pure, :do_test_additions_pure ]
task(:set_env_pure) { ENV['JSON'] = 'pure' }

UndocumentedTestTask.new do |t|
Expand All @@ -106,6 +106,15 @@ UndocumentedTestTask.new do |t|
t.options = '-v'
end

UndocumentedTestTask.new do |t|
t.name = 'do_test_additions_pure'
t.libs << 'lib' << 'tests' << 'tests/lib'
t.ruby_opts << "-rhelper"
t.test_files = FileList['tests/additions/*_test.rb']
t.verbose = true
t.options = '-v'
end

desc "Testing library (pure ruby and extension)"
task :test => [ :test_pure, :test_ext ]

Expand Down Expand Up @@ -174,7 +183,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
end

desc "Testing library (jruby)"
task :test_ext => [ :set_env_ext, :create_jar, :check_env, :do_test_ext ]
task :test_ext => [ :set_env_ext, :create_jar, :check_env, :do_test_ext, :do_test_additions_ext ]
task(:set_env_ext) { ENV['JSON'] = 'ext' }

UndocumentedTestTask.new do |t|
Expand All @@ -185,6 +194,14 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
t.options = '-v'
end

UndocumentedTestTask.new do |t|
t.name = 'do_test_additions_ext'
t.libs << 'lib' << 'tests'
t.test_files = FileList['tests/additions/*_test.rb']
t.verbose = true
t.options = '-v'
end

file JRUBY_PARSER_JAR => :compile do
cd 'java/src' do
parser_classes = FileList[
Expand Down Expand Up @@ -249,7 +266,7 @@ else
end

desc "Testing library (extension)"
task :test_ext => [ :check_env, :compile, :do_test_ext ]
task :test_ext => [ :check_env, :compile, :do_test_ext, :do_test_additions_ext ]

UndocumentedTestTask.new do |t|
t.name = 'do_test_ext'
Expand All @@ -260,6 +277,15 @@ else
t.options = '-v'
end

UndocumentedTestTask.new do |t|
t.name = 'do_test_additions_ext'
t.libs << 'lib' << 'tests' << "tests/lib"
t.ruby_opts << '-rhelper'
t.test_files = FileList['tests/additions/*_test.rb']
t.verbose = true
t.options = '-v'
end

desc "Generate parser with ragel"
task :ragel => EXT_PARSER_SRC

Expand Down
16 changes: 16 additions & 0 deletions ext/json/ext/generator/generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,20 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
fbuffer_append_str(buffer, tmp);
}

static void generate_json_bigdecimal(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
char allow_nan = state->allow_nan;
VALUE tmp = rb_funcall(obj, i_to_s, 1, rb_str_new_cstr("F"));
if (!allow_nan) {
if (rb_funcall(obj, rb_intern("infinite?"), 0) == Qtrue || rb_funcall(obj, rb_intern("nan?"), 0) == Qtrue) {
fbuffer_free(buffer);
rb_raise(eGeneratorError, "%u: %"PRIsVALUE" not allowed in JSON", __LINE__, RB_OBJ_STRING(tmp));
}
}
rb_funcall(tmp, rb_intern("force_encoding"), 1, rb_str_new_cstr("ASCII"));
fbuffer_append_str(buffer, tmp);
}

static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
VALUE tmp;
Expand All @@ -1028,6 +1042,8 @@ static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *s
generate_json_bignum(buffer, Vstate, state, obj);
} else if (klass == rb_cFloat) {
generate_json_float(buffer, Vstate, state, obj);
} else if (rb_str_equal(rb_class_name(klass), rb_str_new_cstr("BigDecimal"))) {
generate_json_bigdecimal(buffer, Vstate, state, obj);
} else if (rb_respond_to(obj, i_to_json)) {
tmp = rb_funcall(obj, i_to_json, 1, Vstate);
Check_Type(tmp, T_STRING);
Expand Down
1 change: 1 addition & 0 deletions ext/json/ext/generator/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
static void generate_json_fixnum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static void generate_json_bignum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static void generate_json_bigdecimal(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static VALUE cState_partial_generate(VALUE self, VALUE obj);
static VALUE cState_generate(VALUE self, VALUE obj);
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self);
Expand Down
3 changes: 3 additions & 0 deletions ext/json/ext/parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -2812,6 +2812,9 @@ static VALUE convert_encoding(VALUE source)
* defaults to false.
* * *object_class*: Defaults to Hash
* * *array_class*: Defaults to Array
# * *decimal_class*: Specifies which class to use instead of the default
# (Float) when parsing decimal numbers. This class must accept a single
# string argument in its constructor.
*/
static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
{
Expand Down
13 changes: 13 additions & 0 deletions lib/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@
#
# ---
#
# Option +decimal_class+ (\Class) specifies which class to use when parsing
# decimal numbers. This class must accept a single string argument in its
# constructor. Defaults to Float. BigDecimal will preserve arbitrary precision.
#
# With the default, \Float:
# source = '3.141592653589793238462643383279'
# ruby = JSON.parse(source) # => 3.141592653589793
# With \BigDecimal:
# ruby = JSON.parse(source, {decimal_class: BigDecimal})
# ruby # => 0.3141592653589793238462643383279e1
#
# ---
#
# Option +create_additions+ (boolean) specifies whether to use \JSON additions in parsing.
# See {\JSON Additions}[#module-JSON-label-JSON+Additions].
#
Expand Down
11 changes: 11 additions & 0 deletions lib/json/pure/generator.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#frozen_string_literal: false
require "bigdecimal"
module JSON
MAP = {
"\x0" => '\u0000',
Expand Down Expand Up @@ -407,6 +408,16 @@ def to_json(state = nil, *)
end
end

module BigDecimal
# Returns a JSON string representation for this BigDecimal as a JSON number.
def to_json(state = nil, *)
if (infinite? || nan?) && !State.from_state(state).allow_nan?
raise GeneratorError, "#{self} not allowed in JSON"
end
to_s("F").force_encoding(Encoding::ASCII)
end
end

module String
# This string should be encoded with UTF-8 A call to this method
# returns a JSON string encoded with UTF16 big endian characters as
Expand Down
File renamed without changes.
8 changes: 8 additions & 0 deletions tests/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ def test_generate_pretty
assert_equal '666', pretty_generate(666)
end

def test_generate_bigdecimal
bd_s = '3.141592653589793238462643383279'
bd = BigDecimal(bd_s)
assert_equal(bd_s, generate(bd))
assert_equal(bd_s, pretty_generate(bd))
assert_equal(bd, parse(generate(bd), decimal_class: BigDecimal))
end

def test_generate_custom
state = State.new(:space_before => " ", :space => " ", :indent => "<i>", :object_nl => "\n", :array_nl => "<a_nl>")
json = generate({1=>{2=>3,4=>[5,6]}}, state)
Expand Down