multi_json 1.18.0 → 1.19.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 +4 -4
- data/CHANGELOG.md +102 -157
- data/CONTRIBUTING.md +21 -15
- data/README.md +7 -5
- data/lib/multi_json/adapter.rb +80 -19
- data/lib/multi_json/adapter_error.rb +22 -2
- data/lib/multi_json/adapter_selector.rb +142 -0
- data/lib/multi_json/adapters/fast_jsonparser.rb +44 -0
- data/lib/multi_json/adapters/gson.rb +18 -0
- data/lib/multi_json/adapters/jr_jackson.rb +28 -1
- data/lib/multi_json/adapters/json_gem.rb +23 -3
- data/lib/multi_json/adapters/oj.rb +30 -27
- data/lib/multi_json/adapters/oj_common.rb +47 -0
- data/lib/multi_json/adapters/ok_json.rb +19 -0
- data/lib/multi_json/adapters/yajl.rb +18 -0
- data/lib/multi_json/convertible_hash_keys.rb +39 -24
- data/lib/multi_json/options.rb +56 -19
- data/lib/multi_json/options_cache.rb +67 -22
- data/lib/multi_json/parse_error.rb +30 -1
- data/lib/multi_json/vendor/okjson.rb +1 -1
- data/lib/multi_json/version.rb +13 -2
- data/lib/multi_json.rb +165 -106
- metadata +10 -8
- data/lib/multi_json/adapters/json_pure.rb +0 -7
data/lib/multi_json/options.rb
CHANGED
|
@@ -1,49 +1,86 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Mixin providing configurable load/dump options
|
|
3
|
+
#
|
|
4
|
+
# Supports static hashes or dynamic callables (procs/lambdas).
|
|
5
|
+
# Extended by both MultiJson (global options) and Adapter classes.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
2
8
|
module Options
|
|
9
|
+
EMPTY_OPTIONS = {}.freeze
|
|
10
|
+
private_constant :EMPTY_OPTIONS
|
|
11
|
+
|
|
12
|
+
# Set options for load operations
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
# @param options [Hash, Proc] options hash or callable
|
|
16
|
+
# @return [Hash, Proc] the options
|
|
3
17
|
def load_options=(options)
|
|
4
18
|
OptionsCache.reset
|
|
5
19
|
@load_options = options
|
|
6
20
|
end
|
|
7
21
|
|
|
22
|
+
# Set options for dump operations
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
25
|
+
# @param options [Hash, Proc] options hash or callable
|
|
26
|
+
# @return [Hash, Proc] the options
|
|
8
27
|
def dump_options=(options)
|
|
9
28
|
OptionsCache.reset
|
|
10
29
|
@dump_options = options
|
|
11
30
|
end
|
|
12
31
|
|
|
13
|
-
|
|
14
|
-
|
|
32
|
+
# Get options for load operations
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
# @return [Hash] resolved options hash
|
|
36
|
+
def load_options(...)
|
|
37
|
+
resolve_options(@load_options, ...) || default_load_options
|
|
15
38
|
end
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
40
|
+
# Get options for dump operations
|
|
41
|
+
#
|
|
42
|
+
# @api private
|
|
43
|
+
# @return [Hash] resolved options hash
|
|
44
|
+
def dump_options(...)
|
|
45
|
+
resolve_options(@dump_options, ...) || default_dump_options
|
|
19
46
|
end
|
|
20
47
|
|
|
48
|
+
# Get default load options
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
# @return [Hash] frozen empty hash
|
|
21
52
|
def default_load_options
|
|
22
|
-
@default_load_options ||=
|
|
53
|
+
@default_load_options ||= EMPTY_OPTIONS
|
|
23
54
|
end
|
|
24
55
|
|
|
56
|
+
# Get default dump options
|
|
57
|
+
#
|
|
58
|
+
# @api private
|
|
59
|
+
# @return [Hash] frozen empty hash
|
|
25
60
|
def default_dump_options
|
|
26
|
-
@default_dump_options ||=
|
|
61
|
+
@default_dump_options ||= EMPTY_OPTIONS
|
|
27
62
|
end
|
|
28
63
|
|
|
29
64
|
private
|
|
30
65
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
options.respond_to?(:call)
|
|
39
|
-
end
|
|
66
|
+
# Resolves options from a hash or callable
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
69
|
+
# @param options [Hash, Proc, nil] options configuration
|
|
70
|
+
# @return [Hash, nil] resolved options hash
|
|
71
|
+
def resolve_options(options, ...)
|
|
72
|
+
return invoke_callable(options, ...) if options.respond_to?(:call)
|
|
40
73
|
|
|
41
|
-
|
|
42
|
-
options.arity.zero? ? options.call : options.call(*args)
|
|
74
|
+
options.to_hash if options.respond_to?(:to_hash)
|
|
43
75
|
end
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
# Invokes a callable options provider
|
|
78
|
+
#
|
|
79
|
+
# @api private
|
|
80
|
+
# @param callable [Proc] options provider
|
|
81
|
+
# @return [Hash] options returned by the callable
|
|
82
|
+
def invoke_callable(callable, ...)
|
|
83
|
+
callable.arity.zero? ? callable.call : callable.call(...)
|
|
47
84
|
end
|
|
48
85
|
end
|
|
49
86
|
end
|
|
@@ -1,47 +1,92 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Thread-safe LRU-like cache for merged options hashes
|
|
3
|
+
#
|
|
4
|
+
# Caches are separated for load and dump operations. Each cache is
|
|
5
|
+
# bounded to prevent unbounded memory growth when options are
|
|
6
|
+
# generated dynamically.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
2
9
|
module OptionsCache
|
|
10
|
+
# Maximum entries before oldest entry is evicted
|
|
11
|
+
MAX_CACHE_SIZE = 1000
|
|
12
|
+
|
|
13
|
+
# Thread-safe cache store using double-checked locking pattern
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
3
16
|
class Store
|
|
4
|
-
#
|
|
5
|
-
|
|
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
|
|
17
|
+
# Sentinel value to detect cache misses (unique object identity)
|
|
18
|
+
NOT_FOUND = Object.new
|
|
11
19
|
|
|
20
|
+
# Create a new cache store
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
# @return [Store] new store instance
|
|
12
24
|
def initialize
|
|
13
25
|
@cache = {}
|
|
14
26
|
@mutex = Mutex.new
|
|
15
27
|
end
|
|
16
28
|
|
|
29
|
+
# Clear all cached entries
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @return [void]
|
|
17
33
|
def reset
|
|
18
|
-
@mutex.synchronize
|
|
19
|
-
@cache = {}
|
|
20
|
-
end
|
|
34
|
+
@mutex.synchronize { @cache.clear }
|
|
21
35
|
end
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
# Fetch a value from cache or compute it
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
# @param key [Object] cache key
|
|
41
|
+
# @param default [Object] default value if key not found
|
|
42
|
+
# @yield block to compute value if not cached
|
|
43
|
+
# @return [Object] cached or computed value
|
|
44
|
+
def fetch(key, default = nil)
|
|
45
|
+
# Fast path: check cache without lock (safe for reads)
|
|
46
|
+
value = @cache.fetch(key, NOT_FOUND)
|
|
47
|
+
return value unless value.equal?(NOT_FOUND)
|
|
48
|
+
|
|
49
|
+
# Slow path: acquire lock and compute value
|
|
24
50
|
@mutex.synchronize do
|
|
25
|
-
|
|
51
|
+
@cache.fetch(key) { block_given? ? store(key, yield) : default }
|
|
26
52
|
end
|
|
53
|
+
end
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
private
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
# Stores a value in the cache with LRU eviction
|
|
58
|
+
#
|
|
59
|
+
# @api private
|
|
60
|
+
# @param key [Object] cache key
|
|
61
|
+
# @param value [Object] value to store
|
|
62
|
+
# @return [Object] the stored value
|
|
63
|
+
def store(key, value)
|
|
64
|
+
# Double-check in case another thread computed while we waited
|
|
65
|
+
@cache.fetch(key) do
|
|
66
|
+
# Evict oldest entry if at capacity (Hash maintains insertion order)
|
|
67
|
+
@cache.shift if @cache.size >= MAX_CACHE_SIZE
|
|
68
|
+
@cache[key] = value
|
|
38
69
|
end
|
|
39
70
|
end
|
|
40
71
|
end
|
|
41
72
|
|
|
42
73
|
class << self
|
|
43
|
-
|
|
74
|
+
# Get the dump options cache
|
|
75
|
+
#
|
|
76
|
+
# @api private
|
|
77
|
+
# @return [Store] dump cache store
|
|
78
|
+
attr_reader :dump
|
|
79
|
+
|
|
80
|
+
# Get the load options cache
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
# @return [Store] load cache store
|
|
84
|
+
attr_reader :load
|
|
44
85
|
|
|
86
|
+
# Reset both caches
|
|
87
|
+
#
|
|
88
|
+
# @api private
|
|
89
|
+
# @return [void]
|
|
45
90
|
def reset
|
|
46
91
|
@dump = Store.new
|
|
47
92
|
@load = Store.new
|
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Raised when JSON parsing fails
|
|
3
|
+
#
|
|
4
|
+
# Wraps the underlying adapter's parse error with the original input data.
|
|
5
|
+
#
|
|
6
|
+
# @api public
|
|
2
7
|
class ParseError < StandardError
|
|
8
|
+
# The input string that failed to parse
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @return [String, nil] the original input data
|
|
12
|
+
# @example
|
|
13
|
+
# error.data #=> "{invalid json}"
|
|
3
14
|
attr_reader :data
|
|
4
15
|
|
|
16
|
+
# Create a new ParseError
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @param message [String, nil] error message
|
|
20
|
+
# @param data [String, nil] the input that failed to parse
|
|
21
|
+
# @param cause [Exception, nil] the original exception
|
|
22
|
+
# @return [ParseError] new error instance
|
|
23
|
+
# @example
|
|
24
|
+
# ParseError.new("unexpected token", data: "{invalid}", cause: err)
|
|
5
25
|
def initialize(message = nil, data: nil, cause: nil)
|
|
6
26
|
super(message)
|
|
7
27
|
@data = data
|
|
8
28
|
set_backtrace(cause.backtrace) if cause
|
|
9
29
|
end
|
|
10
30
|
|
|
31
|
+
# Build a ParseError from an original exception
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
# @param original_exception [Exception] the adapter's parse error
|
|
35
|
+
# @param data [String] the input that failed to parse
|
|
36
|
+
# @return [ParseError] new error with formatted message
|
|
37
|
+
# @example
|
|
38
|
+
# ParseError.build(JSON::ParserError.new("..."), "{bad json}")
|
|
11
39
|
def self.build(original_exception, data)
|
|
12
40
|
new(original_exception.message, data: data, cause: original_exception)
|
|
13
41
|
end
|
|
14
42
|
end
|
|
15
43
|
|
|
16
|
-
|
|
44
|
+
# Legacy aliases for backward compatibility
|
|
45
|
+
DecodeError = LoadError = ParseError
|
|
17
46
|
end
|
data/lib/multi_json/version.rb
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Version information for MultiJson
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
2
5
|
class Version
|
|
6
|
+
# Major version number
|
|
3
7
|
MAJOR = 1 unless defined? MultiJson::Version::MAJOR
|
|
4
|
-
|
|
8
|
+
# Minor version number
|
|
9
|
+
MINOR = 19 unless defined? MultiJson::Version::MINOR
|
|
10
|
+
# Patch version number
|
|
5
11
|
PATCH = 0 unless defined? MultiJson::Version::PATCH
|
|
12
|
+
# Pre-release version suffix
|
|
6
13
|
PRE = nil unless defined? MultiJson::Version::PRE
|
|
7
14
|
|
|
8
15
|
class << self
|
|
9
|
-
#
|
|
16
|
+
# Return the version string
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
# @return [String] version in semver format
|
|
10
20
|
def to_s
|
|
11
21
|
[MAJOR, MINOR, PATCH, PRE].compact.join(".")
|
|
12
22
|
end
|
|
13
23
|
end
|
|
14
24
|
end
|
|
15
25
|
|
|
26
|
+
# Current version string in semver format
|
|
16
27
|
VERSION = Version.to_s.freeze
|
|
17
28
|
end
|
data/lib/multi_json.rb
CHANGED
|
@@ -3,170 +3,229 @@ require_relative "multi_json/version"
|
|
|
3
3
|
require_relative "multi_json/adapter_error"
|
|
4
4
|
require_relative "multi_json/parse_error"
|
|
5
5
|
require_relative "multi_json/options_cache"
|
|
6
|
-
|
|
6
|
+
require_relative "multi_json/adapter_selector"
|
|
7
|
+
|
|
8
|
+
# A unified interface for JSON libraries in Ruby
|
|
9
|
+
#
|
|
10
|
+
# MultiJson allows swapping between JSON backends without changing your code.
|
|
11
|
+
# It auto-detects available JSON libraries and uses the fastest one available.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# MultiJson.load('{"foo":"bar"}') #=> {"foo" => "bar"}
|
|
15
|
+
# MultiJson.dump({foo: "bar"}) #=> '{"foo":"bar"}'
|
|
16
|
+
#
|
|
17
|
+
# @example Specifying an adapter
|
|
18
|
+
# MultiJson.use(:oj)
|
|
19
|
+
# MultiJson.load('{"foo":"bar"}', adapter: :json_gem)
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
7
22
|
module MultiJson
|
|
8
|
-
|
|
9
|
-
extend
|
|
23
|
+
extend Options
|
|
24
|
+
extend AdapterSelector
|
|
10
25
|
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
# @!visibility private
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# @!group Configuration
|
|
13
30
|
|
|
31
|
+
# Set default options for both load and dump operations
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
# @deprecated Use {.load_options=} and {.dump_options=} instead
|
|
35
|
+
# @param value [Hash] options hash
|
|
36
|
+
# @return [Hash] the options hash
|
|
37
|
+
# @example
|
|
38
|
+
# MultiJson.default_options = {symbolize_keys: true}
|
|
39
|
+
def default_options=(value)
|
|
40
|
+
Kernel.warn "MultiJson.default_options setter is deprecated\n" \
|
|
41
|
+
"Use MultiJson.load_options and MultiJson.dump_options instead"
|
|
14
42
|
self.load_options = self.dump_options = value
|
|
15
43
|
end
|
|
16
44
|
|
|
45
|
+
# Get the default options
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @deprecated Use {.load_options} or {.dump_options} instead
|
|
49
|
+
# @return [Hash] the current load options
|
|
50
|
+
# @example
|
|
51
|
+
# MultiJson.default_options #=> {}
|
|
17
52
|
def default_options
|
|
18
|
-
Kernel.warn "MultiJson.default_options is deprecated\
|
|
19
|
-
|
|
53
|
+
Kernel.warn "MultiJson.default_options is deprecated\n" \
|
|
54
|
+
"Use MultiJson.load_options or MultiJson.dump_options instead"
|
|
20
55
|
load_options
|
|
21
56
|
end
|
|
22
57
|
|
|
58
|
+
# @deprecated These methods are no longer used
|
|
23
59
|
%w[cached_options reset_cached_options!].each do |method_name|
|
|
24
|
-
define_method
|
|
60
|
+
define_method(method_name) do |*|
|
|
25
61
|
Kernel.warn "MultiJson.#{method_name} method is deprecated and no longer used."
|
|
26
62
|
end
|
|
27
63
|
end
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
# Legacy alias for adapter name mappings
|
|
66
|
+
ALIASES = AdapterSelector::ALIASES
|
|
30
67
|
|
|
68
|
+
# Maps adapter symbols to their require paths for auto-loading
|
|
31
69
|
REQUIREMENT_MAP = {
|
|
70
|
+
fast_jsonparser: "fast_jsonparser",
|
|
32
71
|
oj: "oj",
|
|
33
72
|
yajl: "yajl",
|
|
34
73
|
jr_jackson: "jrjackson",
|
|
35
74
|
json_gem: "json",
|
|
36
|
-
gson: "gson"
|
|
37
|
-
json_pure: "json"
|
|
75
|
+
gson: "gson"
|
|
38
76
|
}.freeze
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"We recommend loading a different JSON library to improve performance."
|
|
50
|
-
)
|
|
51
|
-
true
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
:ok_json
|
|
78
|
+
class << self
|
|
79
|
+
# Returns the default adapter name (alias for default_adapter)
|
|
80
|
+
#
|
|
81
|
+
# @api public
|
|
82
|
+
# @deprecated Use {.default_adapter} instead
|
|
83
|
+
# @return [Symbol] the default adapter name
|
|
84
|
+
# @example
|
|
85
|
+
# MultiJson.default_engine #=> :oj
|
|
86
|
+
alias_method :default_engine, :default_adapter
|
|
55
87
|
end
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Get the current adapter class.
|
|
60
|
-
def adapter
|
|
61
|
-
return @adapter if defined?(@adapter) && @adapter
|
|
89
|
+
# @!endgroup
|
|
62
90
|
|
|
63
|
-
|
|
91
|
+
# @!group Adapter Management
|
|
64
92
|
|
|
65
|
-
|
|
93
|
+
# Returns the current adapter class
|
|
94
|
+
#
|
|
95
|
+
# @api private
|
|
96
|
+
# @return [Class] the current adapter class
|
|
97
|
+
# @example
|
|
98
|
+
# MultiJson.adapter #=> MultiJson::Adapters::Oj
|
|
99
|
+
def adapter
|
|
100
|
+
@adapter ||= use(nil)
|
|
66
101
|
end
|
|
102
|
+
|
|
103
|
+
# Returns the current adapter class (alias for adapter)
|
|
104
|
+
#
|
|
105
|
+
# @api private
|
|
106
|
+
# @deprecated Use {.adapter} instead
|
|
107
|
+
# @return [Class] the current adapter class
|
|
108
|
+
# @example
|
|
109
|
+
# MultiJson.engine #=> MultiJson::Adapters::Oj
|
|
67
110
|
alias_method :engine, :adapter
|
|
68
111
|
|
|
69
|
-
#
|
|
70
|
-
# Supported by default are:
|
|
112
|
+
# Sets the adapter to use for JSON operations
|
|
71
113
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# * <tt>:gson</tt> (JRuby only)
|
|
78
|
-
# * <tt>:jr_jackson</tt> (JRuby only)
|
|
114
|
+
# @api private
|
|
115
|
+
# @param new_adapter [Symbol, String, Module, nil] adapter specification
|
|
116
|
+
# @return [Class] the loaded adapter class
|
|
117
|
+
# @example
|
|
118
|
+
# MultiJson.use(:oj)
|
|
79
119
|
def use(new_adapter)
|
|
80
120
|
@adapter = load_adapter(new_adapter)
|
|
81
121
|
ensure
|
|
82
122
|
OptionsCache.reset
|
|
83
123
|
end
|
|
124
|
+
|
|
125
|
+
# Sets the adapter to use for JSON operations
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
# @return [Class] the loaded adapter class
|
|
129
|
+
# @example
|
|
130
|
+
# MultiJson.adapter = :json_gem
|
|
84
131
|
alias_method :adapter=, :use
|
|
132
|
+
|
|
133
|
+
# Sets the adapter to use for JSON operations
|
|
134
|
+
#
|
|
135
|
+
# @api private
|
|
136
|
+
# @deprecated Use {.adapter=} instead
|
|
137
|
+
# @return [Class] the loaded adapter class
|
|
138
|
+
# @example
|
|
139
|
+
# MultiJson.engine = :json_gem
|
|
85
140
|
alias_method :engine=, :use
|
|
141
|
+
module_function :adapter=, :engine=
|
|
86
142
|
|
|
87
|
-
|
|
88
|
-
case new_adapter
|
|
89
|
-
when String, Symbol
|
|
90
|
-
load_adapter_from_string_name new_adapter.to_s
|
|
91
|
-
when NilClass, FalseClass
|
|
92
|
-
load_adapter default_adapter
|
|
93
|
-
when Class, Module
|
|
94
|
-
new_adapter
|
|
95
|
-
else
|
|
96
|
-
raise ::LoadError, new_adapter
|
|
97
|
-
end
|
|
98
|
-
rescue ::LoadError => e
|
|
99
|
-
raise(AdapterError.build(e), cause: e)
|
|
100
|
-
end
|
|
143
|
+
# @!endgroup
|
|
101
144
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
#
|
|
145
|
+
# @!group JSON Operations
|
|
146
|
+
|
|
147
|
+
# Parses a JSON string into a Ruby object
|
|
105
148
|
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
149
|
+
# @api private
|
|
150
|
+
# @param string [String, #read] JSON string or IO-like object
|
|
151
|
+
# @param options [Hash] parsing options (adapter-specific)
|
|
152
|
+
# @return [Object] parsed Ruby object
|
|
153
|
+
# @raise [ParseError] if parsing fails
|
|
154
|
+
# @example
|
|
155
|
+
# MultiJson.load('{"foo":"bar"}') #=> {"foo" => "bar"}
|
|
108
156
|
def load(string, options = {})
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
raise(ParseError.build(e, string), cause: e)
|
|
114
|
-
end
|
|
157
|
+
adapter_class = current_adapter(options)
|
|
158
|
+
adapter_class.load(string, options)
|
|
159
|
+
rescue adapter_class::ParseError => e
|
|
160
|
+
raise ParseError.build(e, string)
|
|
115
161
|
end
|
|
162
|
+
|
|
163
|
+
# Parses a JSON string into a Ruby object
|
|
164
|
+
#
|
|
165
|
+
# @api private
|
|
166
|
+
# @return [Object] parsed Ruby object
|
|
167
|
+
# @example
|
|
168
|
+
# MultiJson.decode('{"foo":"bar"}') #=> {"foo" => "bar"}
|
|
116
169
|
alias_method :decode, :load
|
|
117
170
|
|
|
171
|
+
# Returns the adapter to use for the given options
|
|
172
|
+
#
|
|
173
|
+
# @api private
|
|
174
|
+
# @param options [Hash] options that may contain :adapter key
|
|
175
|
+
# @return [Class] adapter class
|
|
176
|
+
# @example
|
|
177
|
+
# MultiJson.current_adapter(adapter: :oj) #=> MultiJson::Adapters::Oj
|
|
118
178
|
def current_adapter(options = {})
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
adapter
|
|
123
|
-
end
|
|
179
|
+
options ||= {}
|
|
180
|
+
adapter_override = options[:adapter]
|
|
181
|
+
adapter_override ? load_adapter(adapter_override) : adapter
|
|
124
182
|
end
|
|
125
183
|
|
|
126
|
-
#
|
|
184
|
+
# Serializes a Ruby object to a JSON string
|
|
185
|
+
#
|
|
186
|
+
# @api private
|
|
187
|
+
# @param object [Object] object to serialize
|
|
188
|
+
# @param options [Hash] serialization options (adapter-specific)
|
|
189
|
+
# @return [String] JSON string
|
|
190
|
+
# @example
|
|
191
|
+
# MultiJson.dump({foo: "bar"}) #=> '{"foo":"bar"}'
|
|
127
192
|
def dump(object, options = {})
|
|
128
193
|
current_adapter(options).dump(object, options)
|
|
129
194
|
end
|
|
195
|
+
|
|
196
|
+
# Serializes a Ruby object to a JSON string
|
|
197
|
+
#
|
|
198
|
+
# @api private
|
|
199
|
+
# @return [String] JSON string
|
|
200
|
+
# @example
|
|
201
|
+
# MultiJson.encode({foo: "bar"}) #=> '{"foo":"bar"}'
|
|
130
202
|
alias_method :encode, :dump
|
|
131
203
|
|
|
132
|
-
#
|
|
204
|
+
# Executes a block using the specified adapter
|
|
205
|
+
#
|
|
206
|
+
# @api private
|
|
207
|
+
# @param new_adapter [Symbol, String, Module] adapter to use
|
|
208
|
+
# @yield block to execute with the temporary adapter
|
|
209
|
+
# @return [Object] result of the block
|
|
210
|
+
# @example
|
|
211
|
+
# MultiJson.with_adapter(:json_gem) { MultiJson.dump({}) }
|
|
133
212
|
def with_adapter(new_adapter)
|
|
134
|
-
|
|
213
|
+
previous_adapter = adapter
|
|
135
214
|
self.adapter = new_adapter
|
|
136
215
|
yield
|
|
137
216
|
ensure
|
|
138
|
-
self.adapter =
|
|
139
|
-
end
|
|
140
|
-
alias_method :with_engine, :with_adapter
|
|
141
|
-
|
|
142
|
-
private
|
|
143
|
-
|
|
144
|
-
# Checks for already loaded adapters and returns the first match
|
|
145
|
-
def loaded_adapter
|
|
146
|
-
return :oj if defined?(::Oj)
|
|
147
|
-
return :yajl if defined?(::Yajl)
|
|
148
|
-
return :jr_jackson if defined?(::JrJackson)
|
|
149
|
-
return :json_gem if defined?(::JSON::Ext::Parser)
|
|
150
|
-
return :gson if defined?(::Gson)
|
|
151
|
-
|
|
152
|
-
nil
|
|
217
|
+
self.adapter = previous_adapter
|
|
153
218
|
end
|
|
154
219
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
end
|
|
220
|
+
# Executes a block using the specified adapter
|
|
221
|
+
#
|
|
222
|
+
# @api private
|
|
223
|
+
# @deprecated Use {.with_adapter} instead
|
|
224
|
+
# @return [Object] result of the block
|
|
225
|
+
# @example
|
|
226
|
+
# MultiJson.with_engine(:json_gem) { MultiJson.dump({}) }
|
|
227
|
+
alias_method :with_engine, :with_adapter
|
|
228
|
+
module_function :with_engine
|
|
165
229
|
|
|
166
|
-
|
|
167
|
-
normalized_name = ALIASES.fetch(name, name).to_s
|
|
168
|
-
require "multi_json/adapters/#{normalized_name.downcase}"
|
|
169
|
-
klass_name = normalized_name.split("_").map(&:capitalize).join
|
|
170
|
-
MultiJson::Adapters.const_get(klass_name)
|
|
171
|
-
end
|
|
230
|
+
# @!endgroup
|
|
172
231
|
end
|