oj 3.7.4 → 3.13.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1352 -0
  3. data/README.md +29 -8
  4. data/RELEASE_NOTES.md +61 -0
  5. data/ext/oj/buf.h +53 -72
  6. data/ext/oj/cache.c +326 -0
  7. data/ext/oj/cache.h +21 -0
  8. data/ext/oj/cache8.c +61 -64
  9. data/ext/oj/cache8.h +12 -39
  10. data/ext/oj/circarray.c +37 -43
  11. data/ext/oj/circarray.h +16 -17
  12. data/ext/oj/code.c +165 -179
  13. data/ext/oj/code.h +27 -29
  14. data/ext/oj/compat.c +174 -194
  15. data/ext/oj/custom.c +809 -866
  16. data/ext/oj/debug.c +132 -0
  17. data/ext/oj/dump.c +848 -863
  18. data/ext/oj/dump.h +81 -67
  19. data/ext/oj/dump_compat.c +85 -123
  20. data/ext/oj/dump_leaf.c +100 -188
  21. data/ext/oj/dump_object.c +527 -656
  22. data/ext/oj/dump_strict.c +315 -338
  23. data/ext/oj/encode.h +7 -34
  24. data/ext/oj/encoder.c +43 -0
  25. data/ext/oj/err.c +40 -29
  26. data/ext/oj/err.h +48 -48
  27. data/ext/oj/extconf.rb +17 -4
  28. data/ext/oj/fast.c +1070 -1087
  29. data/ext/oj/intern.c +301 -0
  30. data/ext/oj/intern.h +26 -0
  31. data/ext/oj/mimic_json.c +469 -436
  32. data/ext/oj/object.c +525 -593
  33. data/ext/oj/odd.c +154 -138
  34. data/ext/oj/odd.h +37 -38
  35. data/ext/oj/oj.c +1325 -986
  36. data/ext/oj/oj.h +333 -316
  37. data/ext/oj/parse.c +1002 -846
  38. data/ext/oj/parse.h +92 -87
  39. data/ext/oj/parser.c +1557 -0
  40. data/ext/oj/parser.h +91 -0
  41. data/ext/oj/rails.c +888 -878
  42. data/ext/oj/rails.h +11 -14
  43. data/ext/oj/reader.c +141 -147
  44. data/ext/oj/reader.h +73 -89
  45. data/ext/oj/resolve.c +41 -62
  46. data/ext/oj/resolve.h +7 -9
  47. data/ext/oj/rxclass.c +71 -75
  48. data/ext/oj/rxclass.h +18 -19
  49. data/ext/oj/saj.c +443 -486
  50. data/ext/oj/saj2.c +602 -0
  51. data/ext/oj/scp.c +88 -113
  52. data/ext/oj/sparse.c +787 -709
  53. data/ext/oj/stream_writer.c +133 -159
  54. data/ext/oj/strict.c +127 -118
  55. data/ext/oj/string_writer.c +230 -249
  56. data/ext/oj/trace.c +34 -41
  57. data/ext/oj/trace.h +19 -19
  58. data/ext/oj/usual.c +1254 -0
  59. data/ext/oj/util.c +136 -0
  60. data/ext/oj/util.h +20 -0
  61. data/ext/oj/val_stack.c +59 -67
  62. data/ext/oj/val_stack.h +91 -129
  63. data/ext/oj/validate.c +46 -0
  64. data/ext/oj/wab.c +342 -353
  65. data/lib/oj/bag.rb +1 -0
  66. data/lib/oj/easy_hash.rb +5 -4
  67. data/lib/oj/error.rb +1 -1
  68. data/lib/oj/json.rb +1 -1
  69. data/lib/oj/mimic.rb +48 -14
  70. data/lib/oj/saj.rb +20 -6
  71. data/lib/oj/state.rb +8 -7
  72. data/lib/oj/version.rb +2 -2
  73. data/lib/oj.rb +0 -8
  74. data/pages/Compatibility.md +1 -1
  75. data/pages/JsonGem.md +15 -0
  76. data/pages/Modes.md +53 -46
  77. data/pages/Options.md +72 -11
  78. data/pages/Parser.md +309 -0
  79. data/pages/Rails.md +73 -22
  80. data/pages/Security.md +1 -1
  81. data/test/activerecord/result_test.rb +7 -2
  82. data/test/activesupport5/abstract_unit.rb +45 -0
  83. data/test/activesupport5/decoding_test.rb +68 -60
  84. data/test/activesupport5/encoding_test.rb +111 -96
  85. data/test/activesupport5/encoding_test_cases.rb +33 -25
  86. data/test/activesupport5/test_helper.rb +43 -21
  87. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  88. data/test/activesupport6/abstract_unit.rb +44 -0
  89. data/test/activesupport6/decoding_test.rb +133 -0
  90. data/test/activesupport6/encoding_test.rb +507 -0
  91. data/test/activesupport6/encoding_test_cases.rb +98 -0
  92. data/test/activesupport6/test_common.rb +17 -0
  93. data/test/activesupport6/test_helper.rb +163 -0
  94. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  95. data/test/activesupport7/abstract_unit.rb +49 -0
  96. data/test/activesupport7/decoding_test.rb +125 -0
  97. data/test/activesupport7/encoding_test.rb +486 -0
  98. data/test/activesupport7/encoding_test_cases.rb +104 -0
  99. data/test/activesupport7/time_zone_test_helpers.rb +47 -0
  100. data/test/bar.rb +6 -12
  101. data/test/baz.rb +16 -0
  102. data/test/bug.rb +16 -0
  103. data/test/foo.rb +69 -75
  104. data/test/helper.rb +16 -0
  105. data/test/json_gem/json_common_interface_test.rb +8 -3
  106. data/test/json_gem/json_generator_test.rb +18 -4
  107. data/test/json_gem/json_parser_test.rb +9 -0
  108. data/test/json_gem/test_helper.rb +12 -0
  109. data/test/mem.rb +33 -0
  110. data/test/perf.rb +1 -1
  111. data/test/perf_dump.rb +50 -0
  112. data/test/perf_once.rb +58 -0
  113. data/test/perf_parser.rb +189 -0
  114. data/test/perf_scp.rb +11 -10
  115. data/test/perf_strict.rb +17 -23
  116. data/test/prec.rb +23 -0
  117. data/test/sample_json.rb +1 -1
  118. data/test/test_compat.rb +46 -10
  119. data/test/test_custom.rb +147 -8
  120. data/test/test_fast.rb +62 -2
  121. data/test/test_file.rb +25 -2
  122. data/test/test_gc.rb +13 -0
  123. data/test/test_generate.rb +21 -0
  124. data/test/test_hash.rb +11 -1
  125. data/test/test_integer_range.rb +7 -2
  126. data/test/test_object.rb +85 -9
  127. data/test/test_parser.rb +27 -0
  128. data/test/test_parser_saj.rb +335 -0
  129. data/test/test_parser_usual.rb +217 -0
  130. data/test/test_rails.rb +35 -0
  131. data/test/test_saj.rb +1 -1
  132. data/test/test_scp.rb +5 -5
  133. data/test/test_strict.rb +26 -1
  134. data/test/test_various.rb +87 -65
  135. data/test/test_wab.rb +2 -0
  136. data/test/test_writer.rb +19 -2
  137. data/test/tests.rb +1 -1
  138. data/test/zoo.rb +13 -0
  139. metadata +60 -110
  140. data/ext/oj/hash.c +0 -163
  141. data/ext/oj/hash.h +0 -46
  142. data/ext/oj/hash_test.c +0 -512
data/test/foo.rb CHANGED
@@ -1,83 +1,77 @@
1
1
  #!/usr/bin/env ruby
2
- # encoding: UTF-8
3
2
 
4
- $: << File.dirname(__FILE__)
5
- $oj_dir = File.dirname(File.expand_path(File.dirname(__FILE__)))
6
- %w(lib ext).each do |dir|
7
- $: << File.join($oj_dir, dir)
3
+ $: << '.'
4
+ $: << File.join(File.dirname(__FILE__), "../lib")
5
+ $: << File.join(File.dirname(__FILE__), "../ext")
6
+
7
+ require "oj"
8
+ require "socket"
9
+ require 'io/nonblock'
10
+
11
+ #pid = spawn("nc -d 0.1 -l 5000", out: "/dev/null")
12
+ pid = spawn("nc -i 1 -l 7777", out: "/dev/null")
13
+ at_exit { Process.kill 9, pid }
14
+ sleep 0.2
15
+ s = Socket.tcp("localhost", 7777)
16
+ s.nonblock = false
17
+ 1_000_000.times do |x|
18
+ Oj.to_stream(s, { x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]})
8
19
  end
9
20
 
10
- require 'oj'
11
- require 'tracer'
12
-
13
- #Tracer.on
14
-
15
- class MyParser
16
- attr_accessor :enum
17
-
18
- def initialize
19
- @io = StringIO.new
20
- @writer = Oj::StreamWriter.new(@io)
21
-
22
- json_string = Oj.dump({ records: 1.upto(2).map{|i| { id: i, name: "record_#{i}" }} }, mode: :strict)
23
- @test_json = StringIO.new(json_string)
24
-
25
- @enum = Enumerator.new do |yielder|
26
- @yielder = yielder
27
- Oj.sc_parse(self, @test_json)
21
+ =begin
22
+ IO.pipe do |r, w|
23
+ if fork
24
+ r.close
25
+ #w.nonblock = false
26
+ 1_000_000.times do |i|
27
+ begin
28
+ Oj.to_stream(w, { x: i})
29
+ rescue IOError => e
30
+ puts "*** #{i} raised #{e.class}: #{e}"
31
+ IO.select(nil, [w])
32
+ retry
33
+ end
34
+ w.puts
28
35
  end
29
- end
30
-
31
- # Stream parsing methods
32
- def hash_start
33
- @writer.push_object
34
- end
35
-
36
- def hash_end
37
- @writer.pop unless @io.eof
38
- end
39
-
40
- def hash_key(key)
41
- @writer.push_key(key)
42
- end
43
-
44
- def hash_set(h, key, value)
45
- @writer.push_value(value)
46
- end
47
-
48
- def array_start
49
- @writer.push_array
50
- end
51
-
52
- def array_end
53
- @writer.pop
54
- end
55
-
56
- def array_append(a, value)
57
- yield_data
58
- end
59
-
60
- def add_value(value);end
61
-
62
- def yield_data
63
- @writer.pop_all
64
- @yielder << @io.string
65
- @io.reopen("")
66
- array_start
36
+ else
37
+ w.close
38
+ sleep(0.1)
39
+ r.each_line { |b|
40
+ #print b
41
+ }
42
+ r.close
43
+ Process.exit(0)
67
44
  end
68
45
  end
69
-
70
- #puts MyParser.new.enum.to_a
71
-
72
- puts "------------"
73
-
74
- MyError = Class.new(StandardError)
75
-
76
- MyParser.new.enum.each.with_index do |r, i|
77
- puts "========="
78
- raise MyError.new('hello')
79
- #raise StopIteration if i == 0
80
- #break if i >= 4
46
+ =end
47
+
48
+ =begin
49
+ IO.pipe do |r, w|
50
+ if fork
51
+ r.close
52
+ #w.nonblock = false
53
+ a = []
54
+ 10_000.times do |i|
55
+ a << i
56
+ end
57
+ begin
58
+ Oj.to_stream(w, a, indent: 2)
59
+ rescue IOError => e
60
+ puts "*** raised #{e.class}: #{e}"
61
+ puts "*** fileno: #{w.fileno}"
62
+ puts "*** is an IO?: #{w.kind_of?(IO)}"
63
+ IO.select(nil, [w])
64
+ retry
65
+ end
66
+ w.puts
67
+ else
68
+ w.close
69
+ sleep(0.5)
70
+ r.each_line { |b|
71
+ #print b
72
+ }
73
+ r.close
74
+ Process.exit(0)
75
+ end
81
76
  end
82
-
83
- #{"records":[{"id":1,"name":"record_1"},{"id":2,"name":"record_2"},{"id":3,"name":"record_3"},{"id":4,"name":"record_4"},{"id":5,"name":"record_5"},{"id":6,"name":"record_6"},{"id":7,"name":"record_7"},{"id":8,"name":"record_8"},{"id":9,"name":"record_9"},{"id":10,"name":"record_10"},{"id":11,"name":"record_11"},{"id":12,"name":"record_12"},{"id":13,"name":"record_13"},{"id":14,"name":"record_14"},{"id":15,"name":"record_15"},{"id":16,"name":"record_16"},{"id":17,"name":"record_17"},{"id":18,"name":"record_18"},{"id":19,"name":"record_19"},{"id":20,"name":"record_20"},{"id":21,"name":"record_21"},{"id":22,"name":"record_22"},{"id":23,"name":"record_23"},{"id":24,"name":"record_24"},{"id":25,"name":"record_25"}]}
77
+ =end
data/test/helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+ #
1
3
  # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is
2
4
  # required. That can be set in the RUBYOPT environment variable.
3
5
  # export RUBYOPT=-w
@@ -16,6 +18,20 @@ require 'bigdecimal'
16
18
  require 'pp'
17
19
  require 'oj'
18
20
 
21
+
22
+ def verify_gc_compaction
23
+ # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
24
+ # move objects around, helping to find object movement bugs.
25
+ if defined?(GC.verify_compaction_references) == 'method' && !(RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/)
26
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2.0")
27
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
28
+ else
29
+ GC.verify_compaction_references(double_heap: true, toward: :empty)
30
+ end
31
+ end
32
+ end
33
+
34
+
19
35
  $ruby = RUBY_DESCRIPTION.split(' ')[0]
20
36
  $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition')
21
37
 
@@ -15,7 +15,7 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
15
15
  def setup
16
16
  @hash = {
17
17
  'a' => 2,
18
- 'b' => 3.141,
18
+ #'b' => 5.23683071,
19
19
  'c' => 'c',
20
20
  'd' => [ 1, "b", 3.14 ],
21
21
  'e' => { 'foo' => 'bar' },
@@ -23,8 +23,13 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
23
23
  'h' => 1000.0,
24
24
  'i' => 0.001
25
25
  }
26
- @json = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},'\
27
- '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
26
+ # Tired of chasing floating point rounding and precision. Oj now uses the
27
+ # Ruby float parser in compat mode yet on i386 machines there are issues
28
+ # with this test when the float is included.
29
+ #@json = '{"a":2,"b":5.23683071,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},'\
30
+ #'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
31
+ @json = '{"a":2,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},'\
32
+ '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
28
33
  end
29
34
 
30
35
  def test_index
@@ -72,6 +72,8 @@ EOT
72
72
  parsed_json = JSON.parse(json)
73
73
  assert_equal({"1"=>2}, parsed_json)
74
74
  assert_equal '666', JSON.pretty_generate(666)
75
+ json_nil_opts = JSON.pretty_generate({1=>2}, nil)
76
+ assert_equal json, json_nil_opts
75
77
  end
76
78
 
77
79
  def test_generate_custom
@@ -113,7 +115,7 @@ EOT
113
115
  end
114
116
 
115
117
  # TBD Implement JSON.state to return state class.
116
- # set state attibutes from defaults
118
+ # set state attributes from defaults
117
119
  # implement methods
118
120
  # circular should use circular in defaults or maybe always set to true, allow changes with [:check_circular]=
119
121
  def test_states
@@ -136,6 +138,10 @@ EOT
136
138
 
137
139
  def test_pretty_state
138
140
  state = JSON::PRETTY_STATE_PROTOTYPE.dup
141
+ # In come cases in Ruby 3.0 an :escape_slash is included in the state. It
142
+ # seems to occur on travis but not locally.
143
+ actual = state.to_h
144
+ actual.delete(:escape_slash)
139
145
  assert_equal({
140
146
  :allow_nan => false,
141
147
  :array_nl => "\n",
@@ -147,11 +153,15 @@ EOT
147
153
  :object_nl => "\n",
148
154
  :space => " ",
149
155
  :space_before => "",
150
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
156
+ }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s })
151
157
  end
152
158
 
153
159
  def test_safe_state
154
160
  state = JSON::SAFE_STATE_PROTOTYPE.dup
161
+ # In come cases in Ruby 3.0 an :escape_slash is included in the state. It
162
+ # seems to occur on travis but not locally.
163
+ actual = state.to_h
164
+ actual.delete(:escape_slash)
155
165
  assert_equal({
156
166
  :allow_nan => false,
157
167
  :array_nl => "",
@@ -163,11 +173,15 @@ EOT
163
173
  :object_nl => "",
164
174
  :space => "",
165
175
  :space_before => "",
166
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
176
+ }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s })
167
177
  end
168
178
 
169
179
  def test_fast_state
170
180
  state = JSON::FAST_STATE_PROTOTYPE.dup
181
+ # In come cases in Ruby 3.0 an :escape_slash is included in the state. It
182
+ # seems to occur on travis but not locally.
183
+ actual = state.to_h
184
+ actual.delete(:escape_slash)
171
185
  assert_equal({
172
186
  :allow_nan => false,
173
187
  :array_nl => "",
@@ -179,7 +193,7 @@ EOT
179
193
  :object_nl => "",
180
194
  :space => "",
181
195
  :space_before => "",
182
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
196
+ }.sort_by { |n,| n.to_s }, actual.sort_by { |n,| n.to_s })
183
197
  end
184
198
 
185
199
  def test_allow_nan
@@ -24,6 +24,8 @@ class JSONParserTest < Test::Unit::TestCase
24
24
  end if defined?(Encoding::UTF_16)
25
25
 
26
26
  def test_error_message_encoding
27
+ omit 'TruffleRuby causes NameError(<uninitialized constant JSON::Ext>)' if RUBY_ENGINE == 'truffleruby'
28
+
27
29
  bug10705 = '[ruby-core:67386] [Bug #10705]'
28
30
  json = ".\"\xE2\x88\x9A\"".force_encoding(Encoding::UTF_8)
29
31
  e = assert_raise(JSON::ParserError) {
@@ -269,6 +271,13 @@ EOT
269
271
  assert_equal too_deep_ary, ok
270
272
  ok = JSON.parse too_deep, :max_nesting => 0
271
273
  assert_equal too_deep_ary, ok
274
+
275
+ unless ENV['REAL_JSON_GEM']
276
+ # max_nesting should be reset to 0 if not included in options
277
+ # This behavior is not compatible with Ruby standard JSON gem
278
+ ok = JSON.parse too_deep, {}
279
+ assert_equal too_deep_ary, ok
280
+ end
272
281
  end
273
282
 
274
283
  def test_backslash
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $: << File.dirname(__FILE__)
2
4
  $oj_dir = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__))))
3
5
  %w(lib ext).each do |dir|
@@ -12,6 +14,16 @@ if ENV['REAL_JSON_GEM']
12
14
  else
13
15
  require 'oj'
14
16
  Oj.mimic_JSON
17
+
18
+ # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
19
+ # move objects around, helping to find object movement bugs.
20
+ if defined?(GC.verify_compaction_references) == 'method'
21
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2.0")
22
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
23
+ else
24
+ GC.verify_compaction_references(double_heap: true, toward: :empty)
25
+ end
26
+ end
15
27
  end
16
28
 
17
29
  NaN = JSON::NaN if defined?(JSON::NaN)
data/test/mem.rb ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << '.'
5
+ $: << File.join(File.dirname(__FILE__), "../lib")
6
+ $: << File.join(File.dirname(__FILE__), "../ext")
7
+
8
+ require 'oj'
9
+
10
+ Oj.default_options = { mode: :rails, cache_keys: false, cache_str: -1 }
11
+
12
+ def mem
13
+ `ps -o rss= -p #{$$}`.to_i
14
+ end
15
+
16
+ ('a'..'z').each { |a|
17
+ ('a'..'z').each { |b|
18
+ ('a'..'z').each { |c|
19
+ ('a'..'z').each { |d|
20
+ ('a'..'z').each { |e|
21
+ ('a'..'z').each { |f|
22
+ key = "#{a}#{b}#{c}#{d}#{e}#{f}"
23
+ x = Oj.load(%|{ "#{key}": 101}|)
24
+ #Oj.dump(x)
25
+ }
26
+ }
27
+ }
28
+ }
29
+ puts "#{a}#{b} #{mem}"
30
+ }
31
+ }
32
+
33
+ Oj::Parser.new(:usual)
data/test/perf.rb CHANGED
@@ -17,7 +17,7 @@ class Perf
17
17
  end
18
18
  end
19
19
  end
20
-
20
+
21
21
  def run(iter)
22
22
  base = Item.new(nil, nil) { }
23
23
  base.run(iter, 0.0)
data/test/perf_dump.rb ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << '.'
5
+ $: << File.join(File.dirname(__FILE__), "../lib")
6
+ $: << File.join(File.dirname(__FILE__), "../ext")
7
+
8
+ require 'optparse'
9
+ require 'oj'
10
+
11
+ $verbose = false
12
+ $indent = 2
13
+ $iter = 100_000
14
+ $size = 2
15
+
16
+ opts = OptionParser.new
17
+ opts.on("-v", "verbose") { $verbose = true }
18
+ opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
19
+ opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
20
+ opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
21
+ opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
22
+ files = opts.parse(ARGV)
23
+
24
+ $obj = {
25
+ 'a' => 'Alpha', # string
26
+ 'b' => true, # boolean
27
+ 'c' => 12345, # number
28
+ 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
29
+ 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
30
+ 'f' => nil, # nil
31
+ 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
32
+ 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
33
+ }
34
+
35
+ Oj.default_options = { :indent => $indent, :mode => :strict }
36
+
37
+ if 0 < $size
38
+ o = $obj
39
+ $obj = []
40
+ (4 * $size).times do
41
+ $obj << o
42
+ end
43
+ end
44
+
45
+ $json = Oj.dump($obj)
46
+ GC.start
47
+ start = Time.now
48
+ $iter.times { Oj.dump($obj) }
49
+ duration = Time.now - start
50
+ puts "Dumped #{$json.length} byte JSON #{$iter} times in %0.3f seconds or %0.3f iteration/sec." % [duration, $iter / duration]
data/test/perf_once.rb ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << '.'
5
+ $: << File.join(File.dirname(__FILE__), "../lib")
6
+ $: << File.join(File.dirname(__FILE__), "../ext")
7
+
8
+ require 'oj'
9
+
10
+ filename = 'tmp.json'
11
+ File.open(filename, "w") { |f|
12
+ cnt = 0
13
+ f.puts('{')
14
+ ('a'..'z').each { |a|
15
+ ('a'..'z').each { |b|
16
+ ('a'..'z').each { |c|
17
+ ('a'..'z').each { |d|
18
+ f.puts(%|"#{a}#{b}#{c}#{d}":#{cnt},|)
19
+ cnt += 1
20
+ }
21
+ }
22
+ }
23
+ }
24
+ f.puts('"_last":0}')
25
+ }
26
+
27
+ def mem
28
+ `ps -o rss= -p #{$$}`.to_i
29
+ end
30
+
31
+ Oj.default_options = { mode: :strict, cache_keys: false, cache_str: -1 }
32
+ start = Time.now
33
+ Oj.load_file('tmp.json')
34
+ dur = Time.now - start
35
+ GC.start
36
+ puts "no cache duration: #{dur} @ #{mem}"
37
+
38
+ Oj.default_options = { cache_keys: true }
39
+ start = Time.now
40
+ Oj.load_file('tmp.json')
41
+ dur = Time.now - start
42
+ GC.start
43
+ puts "initial cache duration: #{dur} @ #{mem}"
44
+
45
+ start = Time.now
46
+ Oj.load_file('tmp.json')
47
+ dur = Time.now - start
48
+ GC.start
49
+ puts "second cache duration: #{dur} @ #{mem}"
50
+
51
+ 10.times{ GC.start }
52
+ start = Time.now
53
+ Oj.load_file('tmp.json')
54
+ dur = Time.now - start
55
+ GC.start
56
+ puts "after several GCs cache duration: #{dur} @ #{mem}"
57
+
58
+ # TBD check memory use
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << '.'
5
+ $: << File.join(File.dirname(__FILE__), "../lib")
6
+ $: << File.join(File.dirname(__FILE__), "../ext")
7
+
8
+ require 'optparse'
9
+ require 'perf'
10
+ require 'oj'
11
+ require 'json'
12
+
13
+ $verbose = false
14
+ $iter = 50_000
15
+ $with_bignum = false
16
+ $size = 1
17
+ $cache_keys = true
18
+ $symbol_keys = false
19
+
20
+ opts = OptionParser.new
21
+ opts.on("-v", "verbose") { $verbose = true }
22
+ opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
23
+ opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
24
+ opts.on("-b", "with bignum") { $with_bignum = true }
25
+ opts.on("-k", "no cache") { $cache_keys = false }
26
+ opts.on("-sym", "symbol keys") { $symbol_keys = true }
27
+ opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
28
+ files = opts.parse(ARGV)
29
+
30
+ $obj = {
31
+ 'a' => 'Alpha', # string
32
+ 'b' => true, # boolean
33
+ 'c' => 12345, # number
34
+ 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false, 1, nil], nil]], # mix it up array
35
+ 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
36
+ 'f' => nil, # nil
37
+ 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
38
+ 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
39
+ }
40
+ $obj['g'] = 12345678901234567890123456789 if $with_bignum
41
+
42
+ if 0 < $size
43
+ o = $obj
44
+ $obj = []
45
+ (4 * $size).times do
46
+ $obj << o
47
+ end
48
+ end
49
+
50
+ $json = Oj.dump($obj)
51
+ $failed = {} # key is same as String used in tests later
52
+ Oj.default_options = {create_id: '^', create_additions: true, class_cache: true}
53
+ if $cache_keys
54
+ Oj.default_options = {cache_keys: true, cache_str: 6, symbol_keys: $symbol_keys}
55
+ else
56
+ Oj.default_options = {cache_keys: false, cache_str: -1, symbol_keys: $symbol_keys}
57
+ end
58
+ JSON.parser = JSON::Ext::Parser
59
+
60
+ class AllSaj
61
+ def initialize()
62
+ end
63
+
64
+ def hash_start(key)
65
+ end
66
+
67
+ def hash_end(key)
68
+ end
69
+
70
+ def array_start(key)
71
+ end
72
+
73
+ def array_end(key)
74
+ end
75
+
76
+ def add_value(value, key)
77
+ end
78
+ end # AllSaj
79
+
80
+ class NoSaj
81
+ def initialize()
82
+ end
83
+ end # NoSaj
84
+
85
+ no_handler = NoSaj.new()
86
+ all_handler = AllSaj.new()
87
+
88
+ if $verbose
89
+ puts "json:\n#{$json}\n"
90
+ end
91
+
92
+ ### Validate ######################
93
+ p_val = Oj::Parser.new(:validate)
94
+
95
+ puts '-' * 80
96
+ puts "Validate Performance"
97
+ perf = Perf.new()
98
+ perf.add('Oj::Parser.validate', 'none') { p_val.parse($json) }
99
+ perf.add('Oj::Saj.none', 'none') { Oj.saj_parse(no_handler, $json) }
100
+ perf.run($iter)
101
+
102
+ ### SAJ ######################
103
+ p_all = Oj::Parser.new(:saj)
104
+ p_all.handler = all_handler
105
+ p_all.cache_keys = $cache_keys
106
+ p_all.cache_strings = 6
107
+
108
+ puts '-' * 80
109
+ puts "Parse Callback Performance"
110
+ perf = Perf.new()
111
+ perf.add('Oj::Parser.saj', 'all') { p_all.parse($json) }
112
+ perf.add('Oj::Saj.all', 'all') { Oj.saj_parse(all_handler, $json) }
113
+ perf.run($iter)
114
+
115
+ ### Usual ######################
116
+ p_usual = Oj::Parser.new(:usual)
117
+ p_usual.cache_keys = $cache_keys
118
+ p_usual.cache_strings = ($cache_keys ? 6 : 0)
119
+ p_usual.symbol_keys = $symbol_keys
120
+
121
+ puts '-' * 80
122
+ puts "Parse Usual Performance"
123
+ perf = Perf.new()
124
+ perf.add('Oj::Parser.usual', '') { p_usual.parse($json) }
125
+ perf.add('Oj::strict_load', '') { Oj.strict_load($json) }
126
+ perf.add('JSON::Ext', 'parse') { JSON.load($json) }
127
+ perf.run($iter)
128
+
129
+ ### Usual Objects ######################
130
+
131
+ # Original Oj follows the JSON gem for creating objects which uses the class
132
+ # json_create(arg) method. Oj::Parser in usual mode supprts the same but also
133
+ # handles populating the object variables directly which is faster.
134
+
135
+ class Stuff
136
+ attr_accessor :alpha, :bravo, :charlie, :delta, :echo, :foxtrot, :golf, :hotel, :india, :juliet
137
+ def self.json_create(arg)
138
+ obj = self.new
139
+ obj.alpha = arg["alpha"]
140
+ obj.bravo = arg["bravo"]
141
+ obj.charlie = arg["charlie"]
142
+ obj.delta = arg["delta"]
143
+ obj.echo = arg["echo"]
144
+ obj.foxtrot = arg["foxtrot"]
145
+ obj.golf = arg["golf"]
146
+ obj.hotel = arg["hotel"]
147
+ obj.india = arg["india"]
148
+ obj.juliet = arg["juliet"]
149
+ obj
150
+ end
151
+ end
152
+
153
+ $obj_json = %|{
154
+ "alpha": [0, 1,2,3,4,5,6,7,8,9],
155
+ "bravo": true,
156
+ "charlie": 123,
157
+ "delta": "some string",
158
+ "echo": null,
159
+ "^": "Stuff",
160
+ "foxtrot": false,
161
+ "golf": "gulp",
162
+ "hotel": {"x": true, "y": false},
163
+ "india": [null, true, 123],
164
+ "juliet": "junk"
165
+ }|
166
+
167
+
168
+ p_usual = Oj::Parser.new(:usual)
169
+ p_usual.cache_keys = $cache_keys
170
+ p_usual.cache_strings = ($cache_keys ? 6 : 0)
171
+ p_usual.symbol_keys = $symbol_keys
172
+ p_usual.create_id = '^'
173
+ p_usual.class_cache = true
174
+ p_usual.ignore_json_create = true
175
+
176
+ JSON.create_id = '^'
177
+
178
+ puts '-' * 80
179
+ puts "Parse Usual Object Performance"
180
+ perf = Perf.new()
181
+ perf.add('Oj::Parser.usual', '') { p_usual.parse($obj_json) }
182
+ perf.add('Oj::compat_load', '') { Oj.compat_load($obj_json) }
183
+ perf.add('JSON::Ext', 'parse') { JSON.load($obj_json) }
184
+ perf.run($iter)
185
+
186
+ unless $failed.empty?
187
+ puts "The following packages were not included for the reason listed"
188
+ $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
189
+ end