fast_multi_json 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []