From 801bef0f3a8847fee0dda0b5bab432ec6999e2b0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 May 2023 01:01:06 -0700 Subject: [PATCH 1/5] test additions in a separate run, avoid loading these in for all tests --- Rakefile | 32 +++++++++++++++++++-- tests/{ => additions}/json_addition_test.rb | 0 2 files changed, 29 insertions(+), 3 deletions(-) rename tests/{ => additions}/json_addition_test.rb (100%) diff --git a/Rakefile b/Rakefile index 8de6dedf..c2e4b0ca 100644 --- a/Rakefile +++ b/Rakefile @@ -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| @@ -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 ] @@ -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| @@ -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[ @@ -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' @@ -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 diff --git a/tests/json_addition_test.rb b/tests/additions/json_addition_test.rb similarity index 100% rename from tests/json_addition_test.rb rename to tests/additions/json_addition_test.rb From f60cbea5cfac2b6565519e51e99f3f17185dbb01 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 May 2023 01:06:33 -0700 Subject: [PATCH 2/5] generate BigDecimal as a JSON number rather than string --- lib/json/pure/generator.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/json/pure/generator.rb b/lib/json/pure/generator.rb index 2257ee34..0e18ffc2 100644 --- a/lib/json/pure/generator.rb +++ b/lib/json/pure/generator.rb @@ -1,4 +1,5 @@ #frozen_string_literal: false +require "bigdecimal" module JSON MAP = { "\x0" => '\u0000', @@ -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 From 654d2b6645d0b172d9b096d7906150afcac46794 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 May 2023 01:08:21 -0700 Subject: [PATCH 3/5] test generate BigDecimal --- tests/json_generator_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/json_generator_test.rb b/tests/json_generator_test.rb index f31b6b29..1f6f043d 100644 --- a/tests/json_generator_test.rb +++ b/tests/json_generator_test.rb @@ -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 => "", :object_nl => "\n", :array_nl => "") json = generate({1=>{2=>3,4=>[5,6]}}, state) From 86050be9117338b33c5c19236e263b75b3ad8943 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 May 2023 13:38:16 -0700 Subject: [PATCH 4/5] document decimal_class on c ext Parser#initialize and top JSON module --- ext/json/ext/parser/parser.c | 3 +++ lib/json.rb | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index 8b860c41..8a29ae83 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -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) { diff --git a/lib/json.rb b/lib/json.rb index 1e64bfcb..008b4794 100644 --- a/lib/json.rb +++ b/lib/json.rb @@ -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]. # From bd307227ed08cc459b30e6504c17d6639a097865 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 May 2023 15:53:05 -0700 Subject: [PATCH 5/5] c ext generate_json_bigdecimal --- ext/json/ext/generator/generator.c | 16 ++++++++++++++++ ext/json/ext/generator/generator.h | 1 + 2 files changed, 17 insertions(+) diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index e3a83472..249fe525 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -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; @@ -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); diff --git a/ext/json/ext/generator/generator.h b/ext/json/ext/generator/generator.h index 3ebd6225..e60414f8 100644 --- a/ext/json/ext/generator/generator.h +++ b/ext/json/ext/generator/generator.h @@ -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);