summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2014-11-22 15:20:17 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2014-11-22 15:20:17 -0800
commitfd2e17e64d6eded4b96fe09bfe376c401f720682 (patch)
tree58ce17c6106e2b547e7106bd0099733d6d8c1b07
parent9ad03a7b89c9fad16f677879da9d91275e997cfb (diff)
downloadffi-yajl-fd2e17e64d6eded4b96fe09bfe376c401f720682.tar.gz
support calling #to_s on all types for keys
compatibility with JSON gem and yajl-ruby closes #14
-rw-r--r--ext/ffi_yajl/ext/encoder/encoder.c128
-rw-r--r--lib/ffi_yajl/ffi/encoder.rb87
-rw-r--r--spec/ffi_yajl/encoder_spec.rb25
3 files changed, 186 insertions, 54 deletions
diff --git a/ext/ffi_yajl/ext/encoder/encoder.c b/ext/ffi_yajl/ext/encoder/encoder.c
index 90b125b..3c22f8d 100644
--- a/ext/ffi_yajl/ext/encoder/encoder.c
+++ b/ext/ffi_yajl/ext/encoder/encoder.c
@@ -78,19 +78,30 @@ static VALUE rb_cHash_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
- extra = rb_hash_new(); /* FIXME: reduce garbage */
+ if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) {
+ ID sym_to_s = rb_intern("to_s");
+ VALUE str = rb_funcall(self, sym_to_s, 0);
+ char *cptr = RSTRING_PTR(str);
+ int len = RSTRING_LEN(str);
+
+ CHECK_STATUS(
+ yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
+ );
+ } else {
+ extra = rb_hash_new(); /* FIXME: reduce garbage */
- rb_hash_aset(extra, rb_str_new2("yajl_gen"), rb_yajl_gen);
+ rb_hash_aset(extra, rb_str_new2("yajl_gen"), rb_yajl_gen);
- rb_hash_aset(extra, rb_str_new2("state"), state);
+ rb_hash_aset(extra, rb_str_new2("state"), state);
- CHECK_STATUS(
- yajl_gen_map_open(yajl_gen)
- );
- rb_hash_foreach(self, rb_cHash_ffi_yajl_callback, extra);
- CHECK_STATUS(
- yajl_gen_map_close(yajl_gen)
- );
+ CHECK_STATUS(
+ yajl_gen_map_open(yajl_gen)
+ );
+ rb_hash_foreach(self, rb_cHash_ffi_yajl_callback, extra);
+ CHECK_STATUS(
+ yajl_gen_map_close(yajl_gen)
+ );
+ }
return Qnil;
}
@@ -103,16 +114,27 @@ static VALUE rb_cArray_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
- CHECK_STATUS(
- yajl_gen_array_open(yajl_gen)
- );
- for(i=0; i<RARRAY_LEN(self); i++) {
- val = rb_ary_entry(self, i);
- rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state);
+ if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) {
+ ID sym_to_s = rb_intern("to_s");
+ VALUE str = rb_funcall(self, sym_to_s, 0);
+ char *cptr = RSTRING_PTR(str);
+ int len = RSTRING_LEN(str);
+
+ CHECK_STATUS(
+ yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
+ );
+ } else {
+ CHECK_STATUS(
+ yajl_gen_array_open(yajl_gen)
+ );
+ for(i=0; i<RARRAY_LEN(self); i++) {
+ val = rb_ary_entry(self, i);
+ rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state);
+ }
+ CHECK_STATUS(
+ yajl_gen_array_close(yajl_gen)
+ );
}
- CHECK_STATUS(
- yajl_gen_array_close(yajl_gen)
- );
return Qnil;
}
@@ -121,9 +143,22 @@ static VALUE rb_cNilClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
yajl_gen_status status;
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
- CHECK_STATUS(
- yajl_gen_null(yajl_gen)
- );
+
+ if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) {
+ ID sym_to_s = rb_intern("to_s");
+ VALUE str = rb_funcall(self, sym_to_s, 0);
+ char *cptr = RSTRING_PTR(str);
+ int len = RSTRING_LEN(str);
+
+ CHECK_STATUS(
+ yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
+ );
+ } else {
+ CHECK_STATUS(
+ yajl_gen_null(yajl_gen)
+ );
+ }
+
return Qnil;
}
@@ -131,9 +166,22 @@ static VALUE rb_cTrueClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state)
yajl_gen_status status;
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
- CHECK_STATUS(
- yajl_gen_bool(yajl_gen, 1)
- );
+
+ if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) {
+ ID sym_to_s = rb_intern("to_s");
+ VALUE str = rb_funcall(self, sym_to_s, 0);
+ char *cptr = RSTRING_PTR(str);
+ int len = RSTRING_LEN(str);
+
+ CHECK_STATUS(
+ yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
+ );
+ } else {
+ CHECK_STATUS(
+ yajl_gen_bool(yajl_gen, 1)
+ );
+ }
+
return Qnil;
}
@@ -141,9 +189,22 @@ static VALUE rb_cFalseClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state)
yajl_gen_status status;
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
- CHECK_STATUS(
- yajl_gen_bool(yajl_gen, 0)
- );
+
+ if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) {
+ ID sym_to_s = rb_intern("to_s");
+ VALUE str = rb_funcall(self, sym_to_s, 0);
+ char *cptr = RSTRING_PTR(str);
+ int len = RSTRING_LEN(str);
+
+ CHECK_STATUS(
+ yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
+ );
+ } else {
+ CHECK_STATUS(
+ yajl_gen_bool(yajl_gen, 0)
+ );
+ }
+
return Qnil;
}
@@ -168,6 +229,7 @@ static VALUE rb_cFixnum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
yajl_gen_number(yajl_gen, cptr, len)
);
}
+
return Qnil;
}
@@ -179,6 +241,7 @@ static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
int len = RSTRING_LEN(str);
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
+
if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) {
rb_raise(cEncodeError, "'%s' is an invalid number", cptr);
}
@@ -191,6 +254,7 @@ static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
yajl_gen_number(yajl_gen, cptr, len)
);
}
+
return Qnil;
}
@@ -202,6 +266,7 @@ static VALUE rb_cFloat_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
int len = RSTRING_LEN(str);
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
+
if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) {
rb_raise(cEncodeError, "'%s' is an invalid number", cptr);
}
@@ -214,6 +279,7 @@ static VALUE rb_cFloat_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
yajl_gen_number(yajl_gen, cptr, len)
);
}
+
return Qnil;
}
@@ -221,9 +287,11 @@ static VALUE rb_cString_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
yajl_gen_status status;
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
+
CHECK_STATUS(
yajl_gen_string(yajl_gen, (unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self))
);
+
return Qnil;
}
@@ -236,9 +304,11 @@ static VALUE object_to_s_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
int len = RSTRING_LEN(str);
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
+
CHECK_STATUS(
yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
);
+
return Qnil;
}
@@ -258,9 +328,11 @@ static VALUE rb_cTime_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) {
int len = RSTRING_LEN(str);
struct yajl_gen_t *yajl_gen;
Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen);
+
CHECK_STATUS(
yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)
);
+
return Qnil;
}
diff --git a/lib/ffi_yajl/ffi/encoder.rb b/lib/ffi_yajl/ffi/encoder.rb
index 8177f5f..add35f6 100644
--- a/lib/ffi_yajl/ffi/encoder.rb
+++ b/lib/ffi_yajl/ffi/encoder.rb
@@ -46,56 +46,91 @@ end
class Hash
def ffi_yajl(yajl_gen, state)
- if ( status = FFI_Yajl.yajl_gen_map_open(yajl_gen) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
- end
- self.each do |key, value|
- # Perf Fix: mutate state hash rather than creating new copy
- state[:processing_key] = true
- key.ffi_yajl(yajl_gen, state)
- state[:processing_key] = false
- value.ffi_yajl(yajl_gen, state)
- end
- if ( status = FFI_Yajl.yajl_gen_map_close(yajl_gen) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
+ if state[:processing_key]
+ str = self.to_s
+ if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ else
+ if ( status = FFI_Yajl.yajl_gen_map_open(yajl_gen) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ self.each do |key, value|
+ # Perf Fix: mutate state hash rather than creating new copy
+ state[:processing_key] = true
+ key.ffi_yajl(yajl_gen, state)
+ state[:processing_key] = false
+ value.ffi_yajl(yajl_gen, state)
+ end
+ if ( status = FFI_Yajl.yajl_gen_map_close(yajl_gen) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
end
end
end
class Array
def ffi_yajl(yajl_gen, state)
- if ( status = FFI_Yajl.yajl_gen_array_open(yajl_gen) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
- end
- self.each do |value|
- value.ffi_yajl(yajl_gen, state)
- end
- if ( status = FFI_Yajl.yajl_gen_array_close(yajl_gen) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
+ if state[:processing_key]
+ str = self.to_s
+ if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ else
+ if ( status = FFI_Yajl.yajl_gen_array_open(yajl_gen) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ self.each do |value|
+ value.ffi_yajl(yajl_gen, state)
+ end
+ if ( status = FFI_Yajl.yajl_gen_array_close(yajl_gen) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
end
end
end
class NilClass
def ffi_yajl(yajl_gen, state)
- if ( status = FFI_Yajl.yajl_gen_null(yajl_gen) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
+ if state[:processing_key]
+ str = self.to_s
+ if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ else
+ if ( status = FFI_Yajl.yajl_gen_null(yajl_gen) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
end
end
end
class TrueClass
def ffi_yajl(yajl_gen, state)
- if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 1) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
+ if state[:processing_key]
+ str = self.to_s
+ if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ else
+ if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 1) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
end
end
end
class FalseClass
def ffi_yajl(yajl_gen, state)
- if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 0) ) != 0
- FFI_Yajl::Encoder.raise_error_for_status(status)
+ if state[:processing_key]
+ str = self.to_s
+ if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
+ else
+ if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 0) ) != 0
+ FFI_Yajl::Encoder.raise_error_for_status(status)
+ end
end
end
end
diff --git a/spec/ffi_yajl/encoder_spec.rb b/spec/ffi_yajl/encoder_spec.rb
index 02bc020..41587e2 100644
--- a/spec/ffi_yajl/encoder_spec.rb
+++ b/spec/ffi_yajl/encoder_spec.rb
@@ -7,6 +7,31 @@ describe "FFI_Yajl::Encoder" do
let(:encoder) { FFI_Yajl::Encoder.new }
+ it "encodes hashes in keys as strings" do
+ ruby = { {'a' => 'b'} => 2 }
+ expect(encoder.encode(ruby)).to eq('{"{\"a\"=>\"b\"}":2}')
+ end
+
+ it "encodes arrays in keys as strings" do
+ ruby = { [0,1] => 2 }
+ expect(encoder.encode(ruby)).to eq('{"[0, 1]":2}')
+ end
+
+ it "encodes nil in keys as strings" do
+ ruby = { nil => 2 }
+ expect(encoder.encode(ruby)).to eq('{"":2}')
+ end
+
+ it "encodes true in keys as strings" do
+ ruby = { true => 2 }
+ expect(encoder.encode(ruby)).to eq('{"true":2}')
+ end
+
+ it "encodes false in keys as strings" do
+ ruby = { false => 2 }
+ expect(encoder.encode(ruby)).to eq('{"false":2}')
+ end
+
it "encodes fixnums in keys as strings" do
ruby = { 1 => 2 }
expect(encoder.encode(ruby)).to eq('{"1":2}')