From 846a93684d2d81ef102717004cd076c1b93544f7 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Fri, 17 Apr 2015 18:05:45 -0700 Subject: add :unique_key_checking flag to parser can be used to error out if keys are duplicated in input rather than silently replacing. --- ext/ffi_yajl/ext/parser/parser.c | 10 +++++++++- ffi-yajl-universal-java.gemspec | 2 +- ffi-yajl.gemspec | 2 ++ ffi-yajl.gemspec.shared | 1 - lib/ffi_yajl/ffi/parser.rb | 3 +++ lib/ffi_yajl/parser.rb | 2 ++ spec/ffi_yajl/parser_spec.rb | 15 +++++++++++++++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ext/ffi_yajl/ext/parser/parser.c b/ext/ffi_yajl/ext/parser/parser.c index a2813af..e846c06 100644 --- a/ext/ffi_yajl/ext/parser/parser.c +++ b/ext/ffi_yajl/ext/parser/parser.c @@ -11,6 +11,7 @@ static VALUE mFFI_Yajl, mExt, mParser, cParseError; typedef struct { VALUE self; int symbolizeKeys; + int uniqueKeyChecking; } CTX; void set_value(CTX *ctx, VALUE val) { @@ -23,6 +24,12 @@ void set_value(CTX *ctx, VALUE val) { rb_ary_push(last, val); break; case T_HASH: + if ( ((CTX *)ctx)->uniqueKeyChecking ) { + ID sym_has_key = rb_intern("has_key?"); + if ( rb_funcall(last, sym_has_key, 1, key) == Qtrue ) { + rb_raise(cParseError, "repeated key: %s", RSTRING_PTR(key)); + } + } rb_hash_aset(last, key, val); break; default: @@ -163,7 +170,7 @@ static yajl_callbacks callbacks = { end_array_callback, }; -int get_opts_key(VALUE self, char *key) { +int get_opts_key(VALUE self, const char *key) { VALUE opts = rb_iv_get(self, "@opts"); if (TYPE(opts) != T_HASH) { rb_raise(rb_eTypeError, "opts is not a valid hash"); @@ -182,6 +189,7 @@ static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE yajl_opts) { ctx.self = self; ctx.symbolizeKeys = get_opts_key(self, "symbolize_keys"); + ctx.uniqueKeyChecking = get_opts_key(self, "unique_key_checking"); hand = yajl_alloc(&callbacks, NULL, (void *)&ctx); diff --git a/ffi-yajl-universal-java.gemspec b/ffi-yajl-universal-java.gemspec index e712372..4df0326 100644 --- a/ffi-yajl-universal-java.gemspec +++ b/ffi-yajl-universal-java.gemspec @@ -6,6 +6,6 @@ gemspec.platform = "universal-java" # extensions so can we simplify the gemspecs now? #gemspec.extensions = %w{ ext/libyajl2/extconf.rb } -gemspec.add_dependency "ffi", "~> 1.5" +gemspec.add_runtime_dependency "ffi", "~> 1.5" gemspec diff --git a/ffi-yajl.gemspec b/ffi-yajl.gemspec index 2801527..03086fd 100644 --- a/ffi-yajl.gemspec +++ b/ffi-yajl.gemspec @@ -3,4 +3,6 @@ gemspec = eval(IO.read(File.expand_path(File.join(File.dirname(__FILE__), "ffi-y gemspec.platform = Gem::Platform::RUBY gemspec.extensions = %w{ ext/ffi_yajl/ext/encoder/extconf.rb ext/ffi_yajl/ext/parser/extconf.rb ext/ffi_yajl/ext/dlopen/extconf.rb } +gemspec.add_development_dependency "ffi", "~> 1.5" + gemspec diff --git a/ffi-yajl.gemspec.shared b/ffi-yajl.gemspec.shared index 62fefa0..d40c0b7 100644 --- a/ffi-yajl.gemspec.shared +++ b/ffi-yajl.gemspec.shared @@ -18,7 +18,6 @@ Gem::Specification.new do |s| s.add_development_dependency "rake-compiler", "~> 0.8.3" # pin mime-types in order to work on ruby 1.8.7 s.add_development_dependency "mime-types", "~> 1.16" - s.add_development_dependency "ffi", "~> 1.5" s.add_dependency "libyajl2", "~> 1.2" s.bindir = "bin" diff --git a/lib/ffi_yajl/ffi/parser.rb b/lib/ffi_yajl/ffi/parser.rb index b97b2de..8e4dc07 100644 --- a/lib/ffi_yajl/ffi/parser.rb +++ b/lib/ffi_yajl/ffi/parser.rb @@ -30,6 +30,9 @@ module FFI_Yajl case stack.last when Hash raise FFI_Yajl::ParseError.new("internal error: missing key in parse") if key.nil? + if @opts[:unique_key_checking] && stack.last.has_key?(key) + raise FFI_Yajl::ParseError.new("repeated key: #{key}") + end stack.last[key] = val when Array stack.last.push(val) diff --git a/lib/ffi_yajl/parser.rb b/lib/ffi_yajl/parser.rb index d8389d7..5b9a0c9 100644 --- a/lib/ffi_yajl/parser.rb +++ b/lib/ffi_yajl/parser.rb @@ -75,6 +75,8 @@ module FFI_Yajl yajl_opts[:yajl_allow_multiple_values] = @opts[:allow_multiple_values] yajl_opts[:yajl_allow_partial_values] = @opts[:allow_partial_values] + yajl_opts[:unique_key_checking] = @opts[:unique_key_checking] + # XXX: bug-compat with ruby-yajl return nil if str == "" diff --git a/spec/ffi_yajl/parser_spec.rb b/spec/ffi_yajl/parser_spec.rb index 7592f67..ed6ef2a 100644 --- a/spec/ffi_yajl/parser_spec.rb +++ b/spec/ffi_yajl/parser_spec.rb @@ -492,6 +492,21 @@ describe "FFI_Yajl::Parser" do expect{ parser }.not_to raise_error end end + + context "should ignore repeated keys by default" do + let(:json) { '{"foo":"bar","foo":"baz"}' } + it "should replace the first hash key with the second" do + expect(parser).to eql( "foo" => "baz" ) + end + end + + context "should raise an exception for repeated keys" do + let(:json) { '{"foo":"bar","foo":"baz"}' } + let(:options) { { :unique_key_checking => true } } + it "should raise" do + expect{ parser }.to raise_error(FFI_Yajl::ParseError) + end + end end context "when options are set to empty hash" do -- cgit v1.2.1