fast_multi_json 1.0.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 01f64ab00bbaff16abaa9085080e24f47e4ecf7f
4
- data.tar.gz: 66db9ef54f902fa65b9f26988542af4d8ac81c00
2
+ SHA256:
3
+ metadata.gz: 529b67b9c69012bf49a7d98f885f1249cd513465beb91f41a7b39d588f4e806e
4
+ data.tar.gz: d38a9989290dfd0b111e686f27caf1d34eb7267e7777e95372a251b2d279d8df
5
5
  SHA512:
6
- metadata.gz: 6376b403fc5b10b0ec779a3e4b898d20c4b66aa3ec243d1e06adc229e93b5579a544752feb83bceb0dea1163c29d19e2d1235d1a54e2de9bd7785427d0451016
7
- data.tar.gz: c249c9c72032ff1b9f02bb032ef695b93033b7ab8e1d6c0376559ca5113bc01431e2d13bb97c9c82db8d3c6321ff93d456b6ce7f7b004f827f9d2b7db63c433a
6
+ metadata.gz: 1ec9d5a43c50703ef7e801b9284340de4c5bbe55e99def2ae5c4ed9db6ba68acce7c7c7e43f76a1530d0ca4fd5da3dfa7067a0ca22b8106eac90d2f838435c07
7
+ data.tar.gz: 30a34ad18ffbb3eb7aca55eaa790633ce740d7e0cc2db485c3ca36c1994df90baf710403bfaaa75524fd50ac85bd3cd6cbe932e4fa3037cbd5dd9ee070925fd8
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_development_dependency "bundler", "~> 1.16"
33
+ spec.add_development_dependency "bundler"
34
34
  spec.add_development_dependency "rake", "~> 10.0"
35
35
  spec.add_development_dependency "rspec", "~> 3.0"
36
36
  end
@@ -1,3 +1,3 @@
1
1
  module FastMultiJson
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,45 +1,43 @@
1
- require "fast_multi_json/version"
1
+ # frozen_string_literal: true
2
+
3
+ require 'fast_multi_json/version'
2
4
  require 'logger'
3
5
 
4
6
  # Usage:
5
7
  # class Movie
6
8
  # def to_json(payload)
7
- # FastMultiJson.to_json(payload)
9
+ # ::FastMultiJson.to_json(payload)
10
+ # end
11
+ #
12
+ # def from_json(string)
13
+ # ::FastMultiJson.from_json(string)
8
14
  # end
9
15
  # end
10
16
  module FastMultiJson
11
- # Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
12
- # e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
13
- class Result
14
- def initialize
15
- @value = yield
16
- @error = nil
17
- rescue LoadError => e
18
- @error = e
19
- end
20
-
21
- def ok?
22
- @error.nil?
23
- end
17
+ def self.logger(logger=nil)
18
+ return @logger = logger if logger
19
+ @logger ||= Logger.new(IO::NULL)
20
+ end
24
21
 
25
- def value!
26
- if ok?
27
- @value
28
- else
29
- raise @error
30
- end
31
- end
22
+ def self.to_json(object)
23
+ _fast_to_json(object)
24
+ rescue NameError
25
+ define_to_json(::FastMultiJson)
26
+ _fast_to_json(object)
27
+ end
32
28
 
33
- def rescue
34
- return self if ok?
35
- Result.new { yield(@error) }
36
- end
29
+ def self.from_json(string)
30
+ _fast_from_json(string)
31
+ rescue NameError
32
+ define_from_json(::FastMultiJson)
33
+ _fast_from_json(string)
37
34
  end
38
- private_constant :Result
39
35
 
40
- def self.logger(logger=nil)
41
- return @logger = logger if logger
42
- @logger ||= Logger.new(IO::NULL)
36
+ def self.define_to_json(receiver)
37
+ cl = caller_locations[0]
38
+ method_body = to_json_method
39
+ logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
40
+ receiver.instance_eval method_body, cl.absolute_path, cl.lineno
43
41
  end
44
42
 
45
43
  # Encoder-compatible with default MultiJSON adapters and defaults
@@ -47,45 +45,106 @@ module FastMultiJson
47
45
  encode_method = String.new(%(def _fast_to_json(object)\n ))
48
46
  encode_method << Result.new {
49
47
  require 'oj'
50
- %(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true))
48
+ %(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true, indent: 0))
51
49
  }.rescue {
52
50
  require 'yajl'
53
51
  %(::Yajl::Encoder.encode(object))
54
52
  }.rescue {
55
53
  require 'jrjackson' unless defined?(::JrJackson)
56
- %(::JrJackson::Json.dump(object))
54
+ if ::JrJackson::Json.method(:dump).arity == 1
55
+ %(::JrJackson::Json.dump(object))
56
+ else
57
+ %(::JrJackson::Json.dump(object, ::FastMultiJson::EMPTY_OPTIONS.dup))
58
+ end
57
59
  }.rescue {
58
60
  require 'json'
59
- %(::JSON.fast_generate(object, create_additions: false, quirks_mode: true))
61
+ %(::JSON.fast_generate(object, create_additions: false, quirks_mode: false))
60
62
  }.rescue {
61
63
  require 'gson'
62
- %(::Gson::Encoder.new({}).encode(object))
64
+ %(::Gson::Encoder.new(::FastMultiJson::EMPTY_OPTIONS.dup).encode(object))
63
65
  }.rescue {
64
66
  require 'active_support/json/encoding'
65
67
  %(::ActiveSupport::JSON.encode(object))
66
68
  }.rescue {
67
69
  warn "No JSON encoder found. Falling back to `object.to_json`"
68
- %(object.to_json)
70
+ %(object.to_json(::JSON::FAST_STATE_PROTOTYPE.to_h))
69
71
  }.value!
70
72
  encode_method << "\nend"
71
73
  end
72
74
 
73
- def self.to_json(object)
74
- _fast_to_json(object)
75
- rescue NameError
76
- define_to_json(FastMultiJson)
77
- _fast_to_json(object)
75
+ def self.reset_to_json!
76
+ undef :_fast_to_json if method_defined?(:_fast_to_json)
77
+ logger.debug { "Undefining #{receiver}._fast_to_json" }
78
78
  end
79
79
 
80
- def self.define_to_json(receiver)
80
+ def self.define_from_json(receiver)
81
81
  cl = caller_locations[0]
82
- method_body = to_json_method
83
- logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
82
+ method_body = from_json_method
83
+ logger.debug { "Defining #{receiver}._fast_from_json as #{method_body.inspect}" }
84
84
  receiver.instance_eval method_body, cl.absolute_path, cl.lineno
85
85
  end
86
86
 
87
- def self.reset_to_json!
88
- undef :_fast_to_json if method_defined?(:_fast_to_json)
89
- logger.debug { "Undefining #{receiver}._fast_to_json" }
87
+ # Decoder-compatible with default MultiJSON adapters and defaults
88
+ def self.from_json_method
89
+ decode_method = String.new(%(def _fast_from_json(string)\n ))
90
+ decode_method << Result.new {
91
+ require 'oj'
92
+ %(::Oj.load(string, mode: :strict, symbol_keys: false))
93
+ }.rescue {
94
+ require 'yajl'
95
+ %(::Yajl::Parser.new(symbolize_keys: false).parse(string))
96
+ }.rescue {
97
+ require 'jrjackson' unless defined?(::JrJackson)
98
+ %(::JrJackson::Json.load(string))
99
+ }.rescue {
100
+ require 'json'
101
+ %(::JSON.parse(string, quirks_mode: false, symbolize_names: false))
102
+ }.rescue {
103
+ require 'gson'
104
+ %(::Gson::Decoder.new(::FastMultiJson::EMPTY_OPTIONS.dup).decode(string))
105
+ }.rescue {
106
+ require 'active_support/json/decoding'
107
+ %(::ActiveSupport::JSON.decode(string))
108
+ }.rescue {
109
+ fail "No JSON decoder found."
110
+ }.value!
111
+ decode_method << "\nend"
112
+ end
113
+
114
+ def self.reset_from_json!
115
+ undef :_fast_from_json if method_defined?(:_fast_from_json)
116
+ logger.debug { "Undefining #{receiver}._fast_from_json" }
117
+ end
118
+
119
+ # Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
120
+ # e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
121
+ class Result
122
+ def initialize
123
+ @value = yield
124
+ @error = nil
125
+ rescue LoadError => e
126
+ @error = e
127
+ end
128
+
129
+ def ok?
130
+ @error.nil?
131
+ end
132
+
133
+ def value!
134
+ if ok?
135
+ @value
136
+ else
137
+ raise @error
138
+ end
139
+ end
140
+
141
+ def rescue
142
+ return self if ok?
143
+ Result.new { yield(@error) }
144
+ end
90
145
  end
146
+ private_constant :Result
147
+
148
+ EMPTY_OPTIONS = {}.freeze
149
+ private_constant :EMPTY_OPTIONS
91
150
  end
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require 'bundler/inline'
5
+
6
+ gemfile do
7
+ source 'https://rubygems.org'
8
+ gem 'multi_json', require: false
9
+ gem 'oj', require: false
10
+ gem 'fast_multi_json', path: '.'
11
+ end
12
+
13
+ # Adapted from https://raw.githubusercontent.com/ohler55/oj/39c975a048d89f42062bb542fae98109520b10eb/test/perf.rb
14
+ class Perf
15
+
16
+ def initialize()
17
+ @items = []
18
+ end
19
+
20
+ def add(title, op, &blk)
21
+ @items << Item.new(title, op, &blk)
22
+ end
23
+
24
+ def before(title, &blk)
25
+ @items.each do |i|
26
+ if title == i.title
27
+ i.set_before(&blk)
28
+ break
29
+ end
30
+ end
31
+ end
32
+
33
+ def run(iter)
34
+ base = Item.new(nil, nil) { }
35
+ base.run(iter, 0.0)
36
+ @items.each do |i|
37
+ i.run(iter, base.duration)
38
+ if i.error.nil?
39
+ puts "#{i.title}.#{i.op} #{iter} times in %0.3f seconds or %0.3f #{i.op}/sec." % [i.duration, iter / i.duration]
40
+ else
41
+ puts "***** #{i.title}.#{i.op} failed! #{i.error}"
42
+ end
43
+ end
44
+ summary()
45
+ end
46
+
47
+ def summary()
48
+ fastest = nil
49
+ slowest = nil
50
+ width = 6
51
+ @items.each do |i|
52
+ next if i.duration.nil?
53
+ width = i.title.size if width < i.title.size
54
+ end
55
+ iva = @items.clone
56
+ iva.delete_if { |i| i.duration.nil? }
57
+ iva = iva.sort_by { |i| i.duration }
58
+ puts
59
+ puts "Summary:"
60
+ puts "%*s time (secs) rate (ops/sec)" % [width, 'System']
61
+ puts "#{'-' * width} ----------- --------------"
62
+ iva.each do |i|
63
+ if i.duration.nil?
64
+ else
65
+ puts "%*s %11.3f %14.3f" % [width, i.title, i.duration, i.rate ]
66
+ end
67
+ end
68
+ puts
69
+ puts "Comparison Matrix\n(performance factor, 2.0 means row is twice as fast as column)"
70
+ puts ([' ' * width] + iva.map { |i| "%*s" % [width, i.title] }).join(' ')
71
+ puts (['-' * width] + iva.map { |i| '-' * width }).join(' ')
72
+ iva.each do |i|
73
+ line = ["%*s" % [width, i.title]]
74
+ iva.each do |o|
75
+ line << "%*.2f" % [width, o.duration / i.duration]
76
+ end
77
+ puts line.join(' ')
78
+ end
79
+ puts
80
+ end
81
+
82
+ class Item
83
+ attr_accessor :title
84
+ attr_accessor :op
85
+ attr_accessor :blk
86
+ attr_accessor :duration
87
+ attr_accessor :rate
88
+ attr_accessor :error
89
+
90
+ def initialize(title, op, &blk)
91
+ @title = title
92
+ @blk = blk
93
+ @op = op
94
+ @duration = nil
95
+ @rate = nil
96
+ @error = nil
97
+ @before = nil
98
+ end
99
+
100
+ def set_before(&blk)
101
+ @before = blk
102
+ end
103
+
104
+ def run(iter, base)
105
+ begin
106
+ GC.start
107
+ @before.call unless @before.nil?
108
+ start = Time.now
109
+ iter.times { @blk.call }
110
+ @duration = Time.now - start - base
111
+ @duration = 0.0 if @duration < 0.0
112
+ @rate = iter / @duration
113
+ rescue Exception => e
114
+ @error = "#{e.class}: #{e.message}"
115
+ end
116
+ end
117
+
118
+ end # Item
119
+ end # Perf
120
+
121
+
122
+ # Adapted from https://github.com/ohler55/oj/blob/39c975a048d89f42062bb542fae98109520b10eb/test/perf_compat.rb
123
+ require 'optparse'
124
+ require 'oj'
125
+ require 'fast_multi_json'
126
+ require 'multi_json'
127
+
128
+ $verbose = false
129
+ $indent = 0
130
+ $iter = 20000
131
+ $size = 0
132
+
133
+ opts = OptionParser.new
134
+ opts.on("-v", "verbose") { $verbose = true }
135
+ opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
136
+ opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
137
+ opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i }
138
+ opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
139
+ files = opts.parse(ARGV)
140
+
141
+ def capture_error(tag, orig, load_key, dump_key, &blk)
142
+ begin
143
+ obj = blk.call(orig)
144
+ puts obj unless orig == obj
145
+ raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj
146
+ rescue Exception => e
147
+ $failed[tag] = "#{e.class}: #{e.message}"
148
+ end
149
+ end
150
+
151
+ $failed = {} # key is same as String used in tests later
152
+
153
+ # Verify that all packages dump and load correctly and return the same Object as the original.
154
+ capture_error('Oj:compat', $obj, 'load', 'dump') { |o| Oj.compat_load(Oj.dump(o, :mode => :compat)) }
155
+ capture_error('MultiJson', $obj, 'load', 'dump') { |o| MultiJson.load(MultiJson.dump(o)) }
156
+ capture_error('FastMultiJson', $obj, 'oj_load', 'dump') { |o| FastMultiJson.from_json(FastMultiJson.to_json(o)) }
157
+ capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o|
158
+ require 'json'
159
+ require 'json/ext'
160
+ JSON.generator = JSON::Ext::Generator
161
+ JSON.parser = JSON::Ext::Parser
162
+ JSON.load(JSON.generate(o))
163
+ }
164
+
165
+ module One
166
+ module Two
167
+ module Three
168
+ class Empty
169
+
170
+ def initialize()
171
+ @a = 1
172
+ @b = 2
173
+ @c = 3
174
+ end
175
+
176
+ def eql?(o)
177
+ self.class == o.class && @a == o.a && @b = o.b && @c = o.c
178
+ end
179
+ alias == eql?
180
+
181
+ def as_json(*a)
182
+ {JSON.create_id => self.class.name, 'a' => @a, 'b' => @b, 'c' => @c }
183
+ end
184
+
185
+ def to_json(*a)
186
+ JSON.generate(as_json())
187
+ end
188
+
189
+ def self.json_create(h)
190
+ self.new()
191
+ end
192
+ end # Empty
193
+ end # Three
194
+ end # Two
195
+ end # One
196
+
197
+ $obj = {
198
+ 'a' => 'Alpha', # string
199
+ 'b' => true, # boolean
200
+ 'c' => 12345, # number
201
+ 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array
202
+ 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash
203
+ 'f' => nil, # nil
204
+ 'g' => One::Two::Three::Empty.new(),
205
+ 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep
206
+ 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep
207
+ }
208
+
209
+
210
+ Oj.default_options = { :indent => $indent, :mode => :compat, :use_to_json => true, :create_additions => true, :create_id => '^o' }
211
+
212
+ if 0 < $size
213
+ s = Oj.dump($obj).size + 1
214
+ cnt = $size * 1024 / s
215
+ o = $obj
216
+ $obj = []
217
+ cnt.times do
218
+ $obj << o
219
+ end
220
+ end
221
+
222
+ puts '-' * 80
223
+ puts " ## Compat dump Performance"
224
+ perf = Perf.new()
225
+ unless $failed.has_key?('JSON::Ext')
226
+ perf.add('JSON::Ext', 'dump') { JSON.dump($obj) }
227
+ perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
228
+ end
229
+ unless $failed.has_key?('MultiJson')
230
+ perf.add('MultiJson', 'dump') { MultiJson.dump($oj) }
231
+ end
232
+ unless $failed.has_key?('FastMultiJson')
233
+ perf.add('FastMultiJson', 'to_json') { FastMultiJson.to_json($oj) }
234
+ end
235
+ unless $failed.has_key?('Oj:compat')
236
+ perf.add('Oj:compat', 'dump_compat') { Oj.dump($oj, mode: :compat) }
237
+ end
238
+ perf.run($iter)
239
+
240
+ puts
241
+ puts '-' * 80
242
+ puts
243
+
244
+ $json = JSON.dump($obj)
245
+
246
+ puts '-' * 80
247
+ puts " ## Compat load Performance"
248
+ perf = Perf.new()
249
+ unless $failed.has_key?('JSON::Ext')
250
+ perf.add('JSON::Ext', 'load') { JSON.load($json) }
251
+ perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser }
252
+ end
253
+ unless $failed.has_key?('MultiJson')
254
+ perf.add('MultiJson', 'load') { MultiJson.load($json) }
255
+ end
256
+ unless $failed.has_key?('FastMultiJson')
257
+ perf.add('FastMultiJson', 'from_json') { FastMultiJson.from_json($json) }
258
+ end
259
+ unless $failed.has_key?('Oj:compat')
260
+ perf.add('Oj:compat', 'load_compat') { Oj.load($json, mode: :compat) }
261
+ end
262
+ perf.run($iter)
263
+
264
+ puts
265
+ puts '-' * 80
266
+ puts
267
+
268
+ unless $failed.empty?
269
+ puts "The following packages were not included for the reason listed"
270
+ $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" }
271
+ end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_multi_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Fleischer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-08 00:00:00.000000000 Z
11
+ date: 2023-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.16'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.16'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -76,12 +76,13 @@ files:
76
76
  - fast_multi_json.gemspec
77
77
  - lib/fast_multi_json.rb
78
78
  - lib/fast_multi_json/version.rb
79
+ - scripts/perf_compat
79
80
  homepage: https://github.com/bf4/fast_multi_json
80
81
  licenses:
81
82
  - MIT
82
83
  metadata:
83
84
  allowed_push_host: https://rubygems.org
84
- post_install_message:
85
+ post_install_message:
85
86
  rdoc_options: []
86
87
  require_paths:
87
88
  - lib
@@ -96,9 +97,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
97
  - !ruby/object:Gem::Version
97
98
  version: '0'
98
99
  requirements: []
99
- rubyforge_project:
100
- rubygems_version: 2.6.13
101
- signing_key:
100
+ rubygems_version: 3.3.7
101
+ signing_key:
102
102
  specification_version: 4
103
103
  summary: Adapterless multi-JSON encoder/decoder
104
104
  test_files: []