multi_json 1.15.0 → 1.16.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.
@@ -1,11 +1,34 @@
1
- require 'json/ext'
2
- require 'multi_json/adapters/json_common'
1
+ require_relative "../adapter"
2
+ require "json"
3
3
 
4
4
  module MultiJson
5
5
  module Adapters
6
6
  # Use the JSON gem to dump/load.
7
- class JsonGem < JsonCommon
7
+ class JsonGem < Adapter
8
8
  ParseError = ::JSON::ParserError
9
+
10
+ defaults :load, create_additions: false, quirks_mode: true
11
+
12
+ PRETTY_STATE_PROTOTYPE = {
13
+ indent: " ",
14
+ space: " ",
15
+ object_nl: "\n",
16
+ array_nl: "\n"
17
+ }.freeze
18
+ private_constant :PRETTY_STATE_PROTOTYPE
19
+
20
+ def load(string, options = {})
21
+ string = string.dup.force_encoding(Encoding::UTF_8) if string.encoding != Encoding::UTF_8
22
+
23
+ options[:symbolize_names] = true if options.delete(:symbolize_keys)
24
+ ::JSON.parse(string, options)
25
+ end
26
+
27
+ def dump(object, options = {})
28
+ options.merge!(PRETTY_STATE_PROTOTYPE) if options.delete(:pretty)
29
+
30
+ object.to_json(options)
31
+ end
9
32
  end
10
33
  end
11
34
  end
@@ -1,11 +1,7 @@
1
- require 'json/pure'
2
- require 'multi_json/adapters/json_common'
1
+ require_relative "json_gem"
3
2
 
4
3
  module MultiJson
5
4
  module Adapters
6
- # Use JSON pure to dump/load.
7
- class JsonPure < JsonCommon
8
- ParseError = ::JSON::ParserError
9
- end
5
+ JsonPure = JsonGem
10
6
  end
11
7
  end
@@ -1,13 +1,12 @@
1
- require 'set'
2
- require 'oj'
3
- require 'multi_json/adapter'
1
+ require "oj"
2
+ require_relative "../adapter"
4
3
 
5
4
  module MultiJson
6
5
  module Adapters
7
6
  # Use the Oj library to dump/load.
8
7
  class Oj < Adapter
9
- defaults :load, :mode => :strict, :symbolize_keys => false
10
- defaults :dump, :mode => :compat, :time_format => :ruby, :use_to_json => true
8
+ defaults :load, mode: :strict, symbolize_keys: false
9
+ defaults :dump, mode: :compat, time_format: :ruby, use_to_json: true
11
10
 
12
11
  # In certain cases OJ gem may throw JSON::ParserError exception instead
13
12
  # of its own class. Also, we can't expect ::JSON::ParserError and
@@ -18,14 +17,10 @@ module MultiJson
18
17
  # (at least for now).
19
18
  class ParseError < ::SyntaxError
20
19
  WRAPPED_CLASSES = %w[Oj::ParseError JSON::ParserError].to_set.freeze
20
+ private_constant :WRAPPED_CLASSES
21
21
 
22
22
  def self.===(exception)
23
- case exception
24
- when ::SyntaxError
25
- true
26
- else
27
- WRAPPED_CLASSES.include?(exception.class.to_s)
28
- end
23
+ exception.is_a?(::SyntaxError) || WRAPPED_CLASSES.include?(exception.class.to_s)
29
24
  end
30
25
  end
31
26
 
@@ -34,29 +29,34 @@ module MultiJson
34
29
  ::Oj.load(string, options)
35
30
  end
36
31
 
37
- case ::Oj::VERSION
38
- when /\A2\./
39
- def dump(object, options = {})
40
- options.merge!(:indent => 2) if options[:pretty]
41
- options[:indent] = options[:indent].to_i if options[:indent]
42
- ::Oj.dump(object, options)
43
- end
44
- when /\A3\./
32
+ OJ_VERSION = ::Oj::VERSION
33
+ OJ_V2 = OJ_VERSION.start_with?("2.")
34
+ OJ_V3 = OJ_VERSION.start_with?("3.")
35
+ private_constant :OJ_VERSION, :OJ_V2, :OJ_V3
36
+
37
+ if OJ_V3
45
38
  PRETTY_STATE_PROTOTYPE = {
46
- :indent => " ",
47
- :space => " ",
48
- :space_before => "",
49
- :object_nl => "\n",
50
- :array_nl => "\n",
51
- :ascii_only => false,
52
- }
53
-
54
- def dump(object, options = {})
39
+ indent: " ",
40
+ space: " ",
41
+ space_before: "",
42
+ object_nl: "\n",
43
+ array_nl: "\n",
44
+ ascii_only: false
45
+ }.freeze
46
+ private_constant :PRETTY_STATE_PROTOTYPE
47
+ end
48
+
49
+ def dump(object, options = {})
50
+ if OJ_V2
51
+ options[:indent] = 2 if options[:pretty]
52
+ options[:indent] = options[:indent].to_i if options[:indent]
53
+ elsif OJ_V3
55
54
  options.merge!(PRETTY_STATE_PROTOTYPE.dup) if options.delete(:pretty)
56
- ::Oj.dump(object, options)
55
+ else
56
+ raise "Unsupported Oj version: #{::Oj::VERSION}"
57
57
  end
58
- else
59
- fail "Unsupported Oj version: #{::Oj::VERSION}"
58
+
59
+ ::Oj.dump(object, options)
60
60
  end
61
61
  end
62
62
  end
@@ -1,6 +1,6 @@
1
- require 'multi_json/adapter'
2
- require 'multi_json/convertible_hash_keys'
3
- require 'multi_json/vendor/okjson'
1
+ require_relative "../adapter"
2
+ require_relative "../convertible_hash_keys"
3
+ require_relative "../vendor/okjson"
4
4
 
5
5
  module MultiJson
6
6
  module Adapters
@@ -1,5 +1,5 @@
1
- require 'yajl'
2
- require 'multi_json/adapter'
1
+ require "yajl"
2
+ require_relative "../adapter"
3
3
 
4
4
  module MultiJson
5
5
  module Adapters
@@ -8,7 +8,7 @@ module MultiJson
8
8
  ParseError = ::Yajl::ParseError
9
9
 
10
10
  def load(string, options = {})
11
- ::Yajl::Parser.new(:symbolize_keys => options[:symbolize_keys]).parse(string)
11
+ ::Yajl::Parser.new(symbolize_keys: options[:symbolize_keys]).parse(string)
12
12
  end
13
13
 
14
14
  def dump(object, options = {})
@@ -1,6 +1,9 @@
1
1
  module MultiJson
2
2
  module ConvertibleHashKeys
3
- private
3
+ SIMPLE_OBJECT_CLASSES = [String, Numeric, TrueClass, FalseClass, NilClass].freeze
4
+ private_constant :SIMPLE_OBJECT_CLASSES
5
+
6
+ private
4
7
 
5
8
  def symbolize_keys(hash)
6
9
  prepare_hash(hash) do |key|
@@ -14,30 +17,35 @@ module MultiJson
14
17
  end
15
18
  end
16
19
 
17
- def prepare_hash(hash, &key_modifier)
18
- return hash unless block_given?
19
- case hash
20
+ def prepare_hash(value, &)
21
+ case value
20
22
  when Array
21
- hash.map do |value|
22
- prepare_hash(value, &key_modifier)
23
- end
23
+ handle_array(value, &)
24
24
  when Hash
25
- hash.inject({}) do |result, (key, value)|
26
- new_key = key_modifier.call(key)
27
- new_value = prepare_hash(value, &key_modifier)
28
- result.merge! new_key => new_value
29
- end
30
- when String, Numeric, true, false, nil
31
- hash
25
+ handle_hash(value, &)
32
26
  else
33
- if hash.respond_to?(:to_json)
34
- hash
35
- elsif hash.respond_to?(:to_s)
36
- hash.to_s
37
- else
38
- hash
39
- end
27
+ handle_simple_objects(value)
40
28
  end
41
29
  end
30
+
31
+ def handle_simple_objects(obj)
32
+ return obj if simple_object?(obj) || obj.respond_to?(:to_json)
33
+
34
+ obj.respond_to?(:to_s) ? obj.to_s : obj
35
+ end
36
+
37
+ def handle_array(array, &key_modifier)
38
+ array.map { |value| prepare_hash(value, &key_modifier) }
39
+ end
40
+
41
+ def handle_hash(original_hash, &key_modifier)
42
+ original_hash.each_with_object({}) do |(key, value), result|
43
+ result[key_modifier.call(key)] = prepare_hash(value, &key_modifier)
44
+ end
45
+ end
46
+
47
+ def simple_object?(obj)
48
+ SIMPLE_OBJECT_CLASSES.any? { |klass| obj.is_a?(klass) }
49
+ end
42
50
  end
43
51
  end
@@ -10,30 +10,40 @@ module MultiJson
10
10
  @dump_options = options
11
11
  end
12
12
 
13
- def load_options(*args)
14
- defined?(@load_options) && get_options(@load_options, *args) || default_load_options
13
+ def load_options(*)
14
+ (defined?(@load_options) && get_options(@load_options, *)) || default_load_options
15
15
  end
16
16
 
17
- def dump_options(*args)
18
- defined?(@dump_options) && get_options(@dump_options, *args) || default_dump_options
17
+ def dump_options(*)
18
+ (defined?(@dump_options) && get_options(@dump_options, *)) || default_dump_options
19
19
  end
20
20
 
21
21
  def default_load_options
22
- @default_load_options ||= {}
22
+ @default_load_options ||= {}.freeze
23
23
  end
24
24
 
25
25
  def default_dump_options
26
- @default_dump_options ||= {}
26
+ @default_dump_options ||= {}.freeze
27
27
  end
28
28
 
29
- private
29
+ private
30
30
 
31
- def get_options(options, *args)
32
- if options.respond_to?(:call) && options.arity
33
- options.arity == 0 ? options[] : options[*args]
34
- elsif options.respond_to?(:to_hash)
35
- options.to_hash
36
- end
31
+ def get_options(options, *)
32
+ return handle_callable_options(options, *) if options_callable?(options)
33
+
34
+ handle_hashable_options(options)
35
+ end
36
+
37
+ def options_callable?(options)
38
+ options.respond_to?(:call)
39
+ end
40
+
41
+ def handle_callable_options(options, *)
42
+ options.arity.zero? ? options.call : options.call(*)
43
+ end
44
+
45
+ def handle_hashable_options(options)
46
+ options.respond_to?(:to_hash) ? options.to_hash : nil
37
47
  end
38
48
  end
39
49
  end
@@ -1,29 +1,53 @@
1
1
  module MultiJson
2
2
  module OptionsCache
3
- extend self
3
+ class Store
4
+ # Normally MultiJson is used with a few option sets for both dump/load
5
+ # methods. When options are generated dynamically though, every call would
6
+ # cause a cache miss and the cache would grow indefinitely. To prevent
7
+ # this, we just reset the cache every time the number of keys outgrows
8
+ # 1000.
9
+ MAX_CACHE_SIZE = 1000
10
+ private_constant :MAX_CACHE_SIZE
4
11
 
5
- def reset
6
- @dump_cache = {}
7
- @load_cache = {}
8
- end
12
+ def initialize
13
+ @cache = {}
14
+ @mutex = Mutex.new
15
+ end
9
16
 
10
- def fetch(type, key, &block)
11
- cache = instance_variable_get("@#{type}_cache")
12
- cache.key?(key) ? cache[key] : write(cache, key, &block)
13
- end
17
+ def reset
18
+ @mutex.synchronize do
19
+ @cache = {}
20
+ end
21
+ end
22
+
23
+ def fetch(key, &)
24
+ @mutex.synchronize do
25
+ return @cache[key] if @cache.key?(key)
26
+ end
14
27
 
15
- private
28
+ value = yield
16
29
 
17
- # Normally MultiJson is used with a few option sets for both dump/load
18
- # methods. When options are generated dynamically though, every call would
19
- # cause a cache miss and the cache would grow indefinitely. To prevent
20
- # this, we just reset the cache every time the number of keys outgrows
21
- # 1000.
22
- MAX_CACHE_SIZE = 1000
30
+ @mutex.synchronize do
31
+ if @cache.key?(key)
32
+ # We ran into a race condition, keep the existing value
33
+ @cache[key]
34
+ else
35
+ @cache.clear if @cache.size >= MAX_CACHE_SIZE
36
+ @cache[key] = value
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ class << self
43
+ attr_reader :dump, :load
23
44
 
24
- def write(cache, key)
25
- cache.clear if cache.length >= MAX_CACHE_SIZE
26
- cache[key] = yield
45
+ def reset
46
+ @dump = Store.new
47
+ @load = Store.new
48
+ end
27
49
  end
50
+
51
+ reset
28
52
  end
29
53
  end
@@ -1,15 +1,15 @@
1
1
  module MultiJson
2
2
  class ParseError < StandardError
3
- attr_reader :data, :cause
3
+ attr_reader :data
4
+
5
+ def initialize(message = nil, data: nil, cause: nil)
6
+ super(message)
7
+ @data = data
8
+ set_backtrace(cause.backtrace) if cause
9
+ end
4
10
 
5
11
  def self.build(original_exception, data)
6
- new(original_exception.message).tap do |exception|
7
- exception.instance_eval do
8
- @cause = original_exception
9
- set_backtrace original_exception.backtrace
10
- @data = data
11
- end
12
- end
12
+ new(original_exception.message, data: data, cause: original_exception)
13
13
  end
14
14
  end
15
15