Skip to content

Commit e274bf6

Browse files
committed
Add json_ready? method
I'd hoped this could be done efficiently in pure Ruby, but the C version is unfortunately still an order of magnitude faster. An efficient implementation of json_ready? is necessary because it allowed skipping a pass of deep-duping the hash structure passed in with calls to as_json when the argument is already in a "json ready" format.
1 parent 2ebf53f commit e274bf6

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

benchmark/json_ready.rb

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
require "benchmark/ips"
2+
require "rapidjson"
3+
4+
if ENV["ONLY"]
5+
RUN = ENV["ONLY"].split(/[,: ]/).map{|x| [x.to_sym, true] }.to_h
6+
RUN.default = false
7+
elsif ENV["EXCEPT"]
8+
RUN = ENV["EXCEPT"].split(/[,: ]/).map{|x| [x.to_sym, false] }.to_h
9+
RUN.default = true
10+
else
11+
RUN = Hash.new(true)
12+
end
13+
14+
class TrueClass; def as_json(options=nil) = self; end
15+
class NilClass; def as_json(options=nil) = self; end
16+
class FalseClass; def as_json(options=nil) = self; end
17+
class Numeric; def as_json(options=nil) = self; end
18+
class String; def as_json(options=nil) = self; end
19+
class Symbol; def as_json(options=nil) = self; end
20+
class Array
21+
def as_json(options=nil) = map(&:as_json)
22+
end
23+
class Hash
24+
def as_json(options=nil) = transform_values(&:as_json)
25+
end
26+
27+
28+
class TrueClass; def json_ready_method(options=nil) = true; end
29+
class NilClass; def json_ready_method(options=nil) = true; end
30+
class FalseClass; def json_ready_method(options=nil) = true; end
31+
class Numeric; def json_ready_method(options=nil) = true; end
32+
class String; def json_ready_method(options=nil) = true; end
33+
class Symbol; def json_ready_method(options=nil) = true; end
34+
class Array
35+
def json_ready_method(options=nil)
36+
each do |v|
37+
return false unless v.json_ready_method
38+
end
39+
true
40+
end
41+
end
42+
class Hash
43+
def json_ready_method(options=nil)
44+
each do |k, v|
45+
return false unless String === k || Symbol === k
46+
return false unless v.json_ready_method
47+
end
48+
true
49+
end
50+
end
51+
52+
class RefinementJsonReady
53+
using Module.new {
54+
55+
[Integer, Float, String, Symbol, NilClass, TrueClass, FalseClass].each do |klass|
56+
refine klass do
57+
def json_ready?; true; end
58+
end
59+
end
60+
61+
refine Array do
62+
def json_ready?
63+
each { |obj| return false unless obj.json_ready? }
64+
true
65+
end
66+
end
67+
68+
refine Hash do
69+
def json_ready?
70+
each do |key, value|
71+
return false unless String === key || Symbol === key
72+
return false unless value.json_ready?
73+
end
74+
true
75+
end
76+
end
77+
78+
refine Object do
79+
def json_ready?
80+
false
81+
end
82+
end
83+
}
84+
85+
def self.json_ready?(obj)
86+
obj.json_ready?
87+
end
88+
end
89+
90+
class PureJsonReady
91+
def self.json_ready?(obj)
92+
case obj
93+
when true, false, nil, Integer, Float, String, Symbol
94+
true
95+
when Array
96+
obj.each { |obj| return false unless json_ready?(obj) }
97+
true
98+
when Hash
99+
obj.each do |key, value|
100+
return false unless String === key || Symbol === key
101+
return false unless json_ready?(value)
102+
end
103+
true
104+
else
105+
false
106+
end
107+
end
108+
end
109+
110+
def benchmark_json_ready(benchmark_name, ruby_obj, check_expected: true)
111+
puts "== json_ready? #{benchmark_name}"
112+
113+
raise "pure not json ready??" unless PureJsonReady.json_ready?(ruby_obj)
114+
raise "refinements not json ready??" unless RefinementJsonReady.json_ready?(ruby_obj)
115+
p RefinementJsonReady.json_ready?(ruby_obj)
116+
raise "RapidJSON not json ready??" unless RapidJSON.json_ready?(ruby_obj)
117+
118+
Benchmark.ips do |x|
119+
x.report "pure" do
120+
PureJsonReady.json_ready?(ruby_obj)
121+
end
122+
123+
x.report "refinements" do
124+
RefinementJsonReady.json_ready?(ruby_obj)
125+
end
126+
127+
x.report "rapidjson" do
128+
RapidJSON.json_ready?(ruby_obj)
129+
end
130+
131+
x.report "json_ready_method" do
132+
ruby_obj.json_ready_method
133+
end
134+
135+
x.report "as_json" do
136+
ruby_obj.as_json
137+
end
138+
139+
x.compare!(order: :baseline)
140+
end
141+
puts
142+
end
143+
144+
benchmark_json_ready "small nested array", [[1,2,3,4,5]]*10
145+
benchmark_json_ready "small hash", { "username" => "jhawthorn", "id" => 123, "event" => "wrote json serializer" }
146+
benchmark_json_ready "twitter.json", JSON.load_file("#{__dir__}/../test/data/twitter.json")
147+
benchmark_json_ready "citm_catalog.json", JSON.load_file("#{__dir__}/../test/data/citm_catalog.json")
148+
benchmark_json_ready "canada.json", JSON.load_file("#{__dir__}/../test/data/canada.json"), check_expected: false

ext/rapidjson/cext.cc

+52
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,57 @@ valid_json_p(VALUE _self, VALUE string) {
7777
return Qtrue;
7878
}
7979

80+
static bool is_json_ready(VALUE obj);
81+
82+
static int is_json_ready_hash_i(VALUE key, VALUE val, VALUE arg) {
83+
bool *result = (bool *)arg;
84+
85+
if (!RB_TYPE_P(key, T_STRING) && !RB_TYPE_P(key, T_SYMBOL)) {
86+
*result = false;
87+
return ST_STOP;
88+
}
89+
if (!is_json_ready(val)) {
90+
*result = false;
91+
return ST_STOP;
92+
}
93+
return ST_CONTINUE;
94+
}
95+
96+
static bool
97+
is_json_ready(VALUE obj) {
98+
switch(rb_type(obj)) {
99+
case T_NIL:
100+
case T_FALSE:
101+
case T_TRUE:
102+
case T_FIXNUM:
103+
case T_BIGNUM:
104+
case T_FLOAT:
105+
case T_SYMBOL:
106+
case T_STRING:
107+
return true;
108+
case T_HASH:
109+
{
110+
bool result = true;
111+
rb_hash_foreach(obj, is_json_ready_hash_i, (VALUE)&result);
112+
return result;
113+
}
114+
case T_ARRAY:
115+
for (int i = 0; i < RARRAY_LEN(obj); i++) {
116+
if (!is_json_ready(RARRAY_AREF(obj, i))) {
117+
return false;
118+
}
119+
}
120+
return true;
121+
default:
122+
return false;
123+
}
124+
}
125+
126+
static VALUE
127+
json_ready_p(VALUE _self, VALUE obj) {
128+
return is_json_ready(obj) ? Qtrue : Qfalse;
129+
}
130+
80131
extern "C" void
81132
Init_rapidjson(void)
82133
{
@@ -94,4 +145,5 @@ Init_rapidjson(void)
94145
rb_eEncodeError = rb_define_class_under(rb_mRapidJSON, "EncodeError", rb_eRapidJSONError);
95146

96147
rb_define_singleton_method(rb_mRapidJSON, "json_escape", escape_json, 1);
148+
rb_define_singleton_method(rb_mRapidJSON, "json_ready?", json_ready_p, 1);
97149
}

0 commit comments

Comments
 (0)