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
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Raised when an adapter cannot be loaded or is not recognized
|
|
3
|
+
#
|
|
4
|
+
# @api public
|
|
2
5
|
class AdapterError < ArgumentError
|
|
6
|
+
# Create a new AdapterError
|
|
7
|
+
#
|
|
8
|
+
# @api public
|
|
9
|
+
# @param message [String, nil] error message
|
|
10
|
+
# @param cause [Exception, nil] the original exception
|
|
11
|
+
# @return [AdapterError] new error instance
|
|
12
|
+
# @example
|
|
13
|
+
# AdapterError.new("Unknown adapter", cause: original_error)
|
|
3
14
|
def initialize(message = nil, cause: nil)
|
|
4
15
|
super(message)
|
|
5
16
|
set_backtrace(cause.backtrace) if cause
|
|
6
17
|
end
|
|
7
18
|
|
|
19
|
+
# Build an AdapterError from an original exception
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
# @param original_exception [Exception] the original load error
|
|
23
|
+
# @return [AdapterError] new error with formatted message
|
|
24
|
+
# @example
|
|
25
|
+
# AdapterError.build(LoadError.new("cannot load such file"))
|
|
8
26
|
def self.build(original_exception)
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
new(
|
|
28
|
+
"Did not recognize your adapter specification (#{original_exception.message}).",
|
|
29
|
+
cause: original_exception
|
|
30
|
+
)
|
|
11
31
|
end
|
|
12
32
|
end
|
|
13
33
|
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module MultiJson
|
|
2
|
+
# Handles adapter discovery, loading, and selection
|
|
3
|
+
#
|
|
4
|
+
# Adapters can be specified as:
|
|
5
|
+
# - Symbol/String: adapter name (e.g., :oj, "json_gem")
|
|
6
|
+
# - Module: adapter class directly
|
|
7
|
+
# - nil/false: use default adapter
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
module AdapterSelector
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
# Alternate spellings for adapter names
|
|
14
|
+
ALIASES = {"jrjackson" => "jr_jackson"}.freeze
|
|
15
|
+
|
|
16
|
+
# Strategy lambdas for loading adapters based on specification type
|
|
17
|
+
LOADERS = {
|
|
18
|
+
Module => ->(adapter, _selector) { adapter },
|
|
19
|
+
String => ->(name, selector) { selector.send(:load_adapter_by_name, name) },
|
|
20
|
+
Symbol => ->(name, selector) { selector.send(:load_adapter_by_name, name.to_s) },
|
|
21
|
+
NilClass => ->(_adapter, selector) { selector.send(:load_adapter, selector.default_adapter) },
|
|
22
|
+
FalseClass => ->(_adapter, selector) { selector.send(:load_adapter, selector.default_adapter) }
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# Returns the default adapter to use
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
# @return [Symbol] adapter name
|
|
29
|
+
# @example
|
|
30
|
+
# AdapterSelector.default_adapter #=> :oj
|
|
31
|
+
def default_adapter
|
|
32
|
+
@default_adapter ||= detect_best_adapter
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Detects the best available JSON adapter
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
# @return [Symbol] adapter name
|
|
41
|
+
def detect_best_adapter
|
|
42
|
+
loaded_adapter || installable_adapter || fallback_adapter
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Finds an already-loaded JSON library
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @return [Symbol, nil] adapter name if found
|
|
49
|
+
def loaded_adapter
|
|
50
|
+
return :fast_jsonparser if defined?(::FastJsonparser)
|
|
51
|
+
return :oj if defined?(::Oj)
|
|
52
|
+
return :yajl if defined?(::Yajl)
|
|
53
|
+
return :jr_jackson if defined?(::JrJackson)
|
|
54
|
+
return :json_gem if defined?(::JSON::Ext::Parser)
|
|
55
|
+
|
|
56
|
+
:gson if defined?(::Gson)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Tries to require and use an installable adapter
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
# @return [Symbol, nil] adapter name if successfully required
|
|
63
|
+
def installable_adapter
|
|
64
|
+
::MultiJson::REQUIREMENT_MAP.each_key do |adapter_name|
|
|
65
|
+
return adapter_name if try_require(adapter_name)
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Attempts to require a JSON library
|
|
71
|
+
#
|
|
72
|
+
# @api private
|
|
73
|
+
# @param adapter_name [Symbol] adapter to require
|
|
74
|
+
# @return [Boolean] true if require succeeded
|
|
75
|
+
def try_require(adapter_name)
|
|
76
|
+
require ::MultiJson::REQUIREMENT_MAP.fetch(adapter_name)
|
|
77
|
+
true
|
|
78
|
+
rescue ::LoadError
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the fallback adapter when no others available
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
85
|
+
# @return [Symbol] the ok_json adapter name
|
|
86
|
+
def fallback_adapter
|
|
87
|
+
warn_about_fallback unless @default_adapter_warning_shown
|
|
88
|
+
@default_adapter_warning_shown = true
|
|
89
|
+
:ok_json
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Warns the user about using the slow fallback adapter
|
|
93
|
+
#
|
|
94
|
+
# @api private
|
|
95
|
+
# @return [void]
|
|
96
|
+
def warn_about_fallback
|
|
97
|
+
Kernel.warn(
|
|
98
|
+
"[WARNING] MultiJson is using the default adapter (ok_json). " \
|
|
99
|
+
"We recommend loading a different JSON library to improve performance."
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Loads an adapter from a specification
|
|
104
|
+
#
|
|
105
|
+
# @api private
|
|
106
|
+
# @param adapter_spec [Symbol, String, Module, nil] adapter specification
|
|
107
|
+
# @return [Class] the adapter class
|
|
108
|
+
def load_adapter(adapter_spec)
|
|
109
|
+
loader = find_loader_for(adapter_spec)
|
|
110
|
+
return loader.call(adapter_spec, self) if loader
|
|
111
|
+
|
|
112
|
+
raise ::LoadError, adapter_spec
|
|
113
|
+
rescue ::LoadError => e
|
|
114
|
+
raise AdapterError.build(e)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Finds the appropriate loader for an adapter specification
|
|
118
|
+
#
|
|
119
|
+
# @api private
|
|
120
|
+
# @param adapter_spec [Object] adapter specification
|
|
121
|
+
# @return [Proc, nil] loader proc if found
|
|
122
|
+
def find_loader_for(adapter_spec)
|
|
123
|
+
klass = adapter_spec.class
|
|
124
|
+
return LOADERS.fetch(klass) if LOADERS.key?(klass)
|
|
125
|
+
|
|
126
|
+
LOADERS.fetch(Module) if adapter_spec.is_a?(Module)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Loads an adapter by its string name
|
|
130
|
+
#
|
|
131
|
+
# @api private
|
|
132
|
+
# @param name [String] adapter name
|
|
133
|
+
# @return [Class] the adapter class
|
|
134
|
+
def load_adapter_by_name(name)
|
|
135
|
+
normalized = ALIASES.fetch(name, name).downcase
|
|
136
|
+
require_relative "adapters/#{normalized}"
|
|
137
|
+
|
|
138
|
+
class_name = normalized.split("_").map(&:capitalize).join
|
|
139
|
+
::MultiJson::Adapters.const_get(class_name)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "fast_jsonparser"
|
|
2
|
+
require "oj"
|
|
3
|
+
require_relative "../adapter"
|
|
4
|
+
require_relative "oj_common"
|
|
5
|
+
|
|
6
|
+
module MultiJson
|
|
7
|
+
module Adapters
|
|
8
|
+
# Use the FastJsonparser library to load and Oj to dump.
|
|
9
|
+
class FastJsonparser < Adapter
|
|
10
|
+
include OjCommon
|
|
11
|
+
|
|
12
|
+
defaults :load, symbolize_keys: false
|
|
13
|
+
defaults :dump, mode: :compat, time_format: :ruby, use_to_json: true
|
|
14
|
+
|
|
15
|
+
ParseError = ::FastJsonparser::ParseError
|
|
16
|
+
|
|
17
|
+
# Parse a JSON string into a Ruby object
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
# @param string [String] JSON string to parse
|
|
21
|
+
# @param options [Hash] parsing options
|
|
22
|
+
# @return [Object] parsed Ruby object
|
|
23
|
+
#
|
|
24
|
+
# @example Parse JSON string
|
|
25
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
26
|
+
def load(string, options = {})
|
|
27
|
+
::FastJsonparser.parse(string, symbolize_keys: options[:symbolize_keys])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Serialize a Ruby object to JSON
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
# @param object [Object] object to serialize
|
|
34
|
+
# @param options [Hash] serialization options
|
|
35
|
+
# @return [String] JSON string
|
|
36
|
+
#
|
|
37
|
+
# @example Serialize object to JSON
|
|
38
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
39
|
+
def dump(object, options = {})
|
|
40
|
+
::Oj.dump(object, prepare_dump_options(options))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -7,10 +7,28 @@ module MultiJson
|
|
|
7
7
|
class Gson < Adapter
|
|
8
8
|
ParseError = ::Gson::DecodeError
|
|
9
9
|
|
|
10
|
+
# Parse a JSON string into a Ruby object
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
# @param string [String] JSON string to parse
|
|
14
|
+
# @param options [Hash] parsing options
|
|
15
|
+
# @return [Object] parsed Ruby object
|
|
16
|
+
#
|
|
17
|
+
# @example Parse JSON string
|
|
18
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
10
19
|
def load(string, options = {})
|
|
11
20
|
::Gson::Decoder.new(options).decode(string)
|
|
12
21
|
end
|
|
13
22
|
|
|
23
|
+
# Serialize a Ruby object to JSON
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
# @param object [Object] object to serialize
|
|
27
|
+
# @param options [Hash] serialization options
|
|
28
|
+
# @return [String] JSON string
|
|
29
|
+
#
|
|
30
|
+
# @example Serialize object to JSON
|
|
31
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
14
32
|
def dump(object, options = {})
|
|
15
33
|
::Gson::Encoder.new(options).encode(object)
|
|
16
34
|
end
|
|
@@ -7,15 +7,42 @@ module MultiJson
|
|
|
7
7
|
class JrJackson < Adapter
|
|
8
8
|
ParseError = ::JrJackson::ParseError
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Parse a JSON string into a Ruby object
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
# @param string [String] JSON string to parse
|
|
14
|
+
# @param options [Hash] parsing options
|
|
15
|
+
# @return [Object] parsed Ruby object
|
|
16
|
+
#
|
|
17
|
+
# @example Parse JSON string
|
|
18
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
19
|
+
def load(string, options = {})
|
|
11
20
|
::JrJackson::Json.load(string, options)
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
if ::JrJackson::Json.method(:dump).arity == 1
|
|
24
|
+
# Serialize a Ruby object to JSON
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
# @param object [Object] object to serialize
|
|
28
|
+
# @param _ [Hash] serialization options (unused in this version)
|
|
29
|
+
# @return [String] JSON string
|
|
30
|
+
#
|
|
31
|
+
# @example Serialize object to JSON
|
|
32
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
15
33
|
def dump(object, _)
|
|
16
34
|
::JrJackson::Json.dump(object)
|
|
17
35
|
end
|
|
18
36
|
else
|
|
37
|
+
# Serialize a Ruby object to JSON
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
# @param object [Object] object to serialize
|
|
41
|
+
# @param options [Hash] serialization options
|
|
42
|
+
# @return [String] JSON string
|
|
43
|
+
#
|
|
44
|
+
# @example Serialize object to JSON
|
|
45
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
19
46
|
def dump(object, options = {})
|
|
20
47
|
::JrJackson::Json.dump(object, options)
|
|
21
48
|
end
|
|
@@ -17,6 +17,15 @@ module MultiJson
|
|
|
17
17
|
}.freeze
|
|
18
18
|
private_constant :PRETTY_STATE_PROTOTYPE
|
|
19
19
|
|
|
20
|
+
# Parse a JSON string into a Ruby object
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
# @param string [String] JSON string to parse
|
|
24
|
+
# @param options [Hash] parsing options
|
|
25
|
+
# @return [Object] parsed Ruby object
|
|
26
|
+
#
|
|
27
|
+
# @example Parse JSON string
|
|
28
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
20
29
|
def load(string, options = {})
|
|
21
30
|
string = string.dup.force_encoding(Encoding::UTF_8) if string.encoding != Encoding::UTF_8
|
|
22
31
|
|
|
@@ -24,15 +33,26 @@ module MultiJson
|
|
|
24
33
|
::JSON.parse(string, options)
|
|
25
34
|
end
|
|
26
35
|
|
|
36
|
+
# Serialize a Ruby object to JSON
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
# @param object [Object] object to serialize
|
|
40
|
+
# @param options [Hash] serialization options
|
|
41
|
+
# @return [String] JSON string
|
|
42
|
+
#
|
|
43
|
+
# @example Serialize object to JSON
|
|
44
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
27
45
|
def dump(object, options = {})
|
|
28
|
-
opts = options.
|
|
46
|
+
opts = options.except(:adapter)
|
|
47
|
+
json_object = object.respond_to?(:as_json) ? object.as_json : object
|
|
48
|
+
return ::JSON.dump(json_object) if opts.empty?
|
|
29
49
|
|
|
30
50
|
if opts.delete(:pretty)
|
|
31
51
|
opts = PRETTY_STATE_PROTOTYPE.merge(opts)
|
|
32
|
-
return ::JSON.pretty_generate(
|
|
52
|
+
return ::JSON.pretty_generate(json_object, opts)
|
|
33
53
|
end
|
|
34
54
|
|
|
35
|
-
::JSON.generate(
|
|
55
|
+
::JSON.generate(json_object, opts)
|
|
36
56
|
end
|
|
37
57
|
end
|
|
38
58
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
require "oj"
|
|
2
2
|
require_relative "../adapter"
|
|
3
|
+
require_relative "oj_common"
|
|
3
4
|
|
|
4
5
|
module MultiJson
|
|
5
6
|
module Adapters
|
|
6
7
|
# Use the Oj library to dump/load.
|
|
7
8
|
class Oj < Adapter
|
|
9
|
+
include OjCommon
|
|
10
|
+
|
|
8
11
|
defaults :load, mode: :strict, symbolize_keys: false
|
|
9
12
|
defaults :dump, mode: :compat, time_format: :ruby, use_to_json: true
|
|
10
13
|
|
|
@@ -19,44 +22,44 @@ module MultiJson
|
|
|
19
22
|
WRAPPED_CLASSES = %w[Oj::ParseError JSON::ParserError].freeze
|
|
20
23
|
private_constant :WRAPPED_CLASSES
|
|
21
24
|
|
|
25
|
+
# Case equality for exception matching in rescue clauses
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
# @param exception [Exception] exception to check
|
|
29
|
+
# @return [Boolean] true if exception is a parse error
|
|
30
|
+
#
|
|
31
|
+
# @example Match parse errors in rescue
|
|
32
|
+
# rescue ParseError => e
|
|
22
33
|
def self.===(exception)
|
|
23
34
|
exception.is_a?(::SyntaxError) || WRAPPED_CLASSES.include?(exception.class.to_s)
|
|
24
35
|
end
|
|
25
36
|
end
|
|
26
37
|
|
|
38
|
+
# Parse a JSON string into a Ruby object
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
# @param string [String] JSON string to parse
|
|
42
|
+
# @param options [Hash] parsing options
|
|
43
|
+
# @return [Object] parsed Ruby object
|
|
44
|
+
#
|
|
45
|
+
# @example Parse JSON string
|
|
46
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
27
47
|
def load(string, options = {})
|
|
28
48
|
options[:symbol_keys] = options[:symbolize_keys]
|
|
29
49
|
::Oj.load(string, options)
|
|
30
50
|
end
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
52
|
+
# Serialize a Ruby object to JSON
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
# @param object [Object] object to serialize
|
|
56
|
+
# @param options [Hash] serialization options
|
|
57
|
+
# @return [String] JSON string
|
|
58
|
+
#
|
|
59
|
+
# @example Serialize object to JSON
|
|
60
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
49
61
|
def dump(object, options = {})
|
|
50
|
-
|
|
51
|
-
options[:indent] = 2 if options[:pretty]
|
|
52
|
-
options[:indent] = options[:indent].to_i if options[:indent]
|
|
53
|
-
elsif OJ_V3
|
|
54
|
-
options.merge!(PRETTY_STATE_PROTOTYPE.dup) if options.delete(:pretty)
|
|
55
|
-
else
|
|
56
|
-
raise "Unsupported Oj version: #{::Oj::VERSION}"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
::Oj.dump(object, options)
|
|
62
|
+
::Oj.dump(object, prepare_dump_options(options))
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
65
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "rubygems/version"
|
|
2
|
+
|
|
3
|
+
module MultiJson
|
|
4
|
+
module Adapters
|
|
5
|
+
module OjCommon
|
|
6
|
+
OJ_VERSION = Gem::Version.new(::Oj::VERSION)
|
|
7
|
+
OJ_V2 = OJ_VERSION.segments.first == 2
|
|
8
|
+
OJ_V3 = OJ_VERSION.segments.first == 3
|
|
9
|
+
private_constant :OJ_VERSION, :OJ_V2, :OJ_V3
|
|
10
|
+
|
|
11
|
+
if OJ_V3
|
|
12
|
+
PRETTY_STATE_PROTOTYPE = {
|
|
13
|
+
indent: " ",
|
|
14
|
+
space: " ",
|
|
15
|
+
space_before: "",
|
|
16
|
+
object_nl: "\n",
|
|
17
|
+
array_nl: "\n",
|
|
18
|
+
ascii_only: false
|
|
19
|
+
}.freeze
|
|
20
|
+
private_constant :PRETTY_STATE_PROTOTYPE
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Prepare options for Oj.dump based on Oj version
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
# @param options [Hash] serialization options
|
|
29
|
+
# @return [Hash] processed options for Oj.dump
|
|
30
|
+
#
|
|
31
|
+
# @example Prepare dump options
|
|
32
|
+
# prepare_dump_options(pretty: true)
|
|
33
|
+
def prepare_dump_options(options)
|
|
34
|
+
if OJ_V2
|
|
35
|
+
options[:indent] = 2 if options[:pretty]
|
|
36
|
+
options[:indent] = options[:indent].to_i if options[:indent]
|
|
37
|
+
elsif OJ_V3
|
|
38
|
+
options.merge!(PRETTY_STATE_PROTOTYPE.dup) if options.delete(:pretty)
|
|
39
|
+
else
|
|
40
|
+
raise "Unsupported Oj version: #{::Oj::VERSION}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
options
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -4,11 +4,21 @@ require_relative "../vendor/okjson"
|
|
|
4
4
|
|
|
5
5
|
module MultiJson
|
|
6
6
|
module Adapters
|
|
7
|
+
# Use the vendored OkJson library to dump/load.
|
|
7
8
|
class OkJson < Adapter
|
|
8
9
|
include ConvertibleHashKeys
|
|
9
10
|
|
|
10
11
|
ParseError = ::MultiJson::OkJson::Error
|
|
11
12
|
|
|
13
|
+
# Parse a JSON string into a Ruby object
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
# @param string [String] JSON string to parse
|
|
17
|
+
# @param options [Hash] parsing options
|
|
18
|
+
# @return [Object] parsed Ruby object
|
|
19
|
+
#
|
|
20
|
+
# @example Parse JSON string
|
|
21
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
12
22
|
def load(string, options = {})
|
|
13
23
|
result = ::MultiJson::OkJson.decode("[#{string}]").first
|
|
14
24
|
options[:symbolize_keys] ? symbolize_keys(result) : result
|
|
@@ -16,6 +26,15 @@ module MultiJson
|
|
|
16
26
|
raise ParseError
|
|
17
27
|
end
|
|
18
28
|
|
|
29
|
+
# Serialize a Ruby object to JSON
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @param object [Object] object to serialize
|
|
33
|
+
# @param _ [Hash] serialization options (unused)
|
|
34
|
+
# @return [String] JSON string
|
|
35
|
+
#
|
|
36
|
+
# @example Serialize object to JSON
|
|
37
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
19
38
|
def dump(object, _ = {})
|
|
20
39
|
::MultiJson::OkJson.valenc(stringify_keys(object))
|
|
21
40
|
end
|
|
@@ -7,10 +7,28 @@ module MultiJson
|
|
|
7
7
|
class Yajl < Adapter
|
|
8
8
|
ParseError = ::Yajl::ParseError
|
|
9
9
|
|
|
10
|
+
# Parse a JSON string into a Ruby object
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
# @param string [String] JSON string to parse
|
|
14
|
+
# @param options [Hash] parsing options
|
|
15
|
+
# @return [Object] parsed Ruby object
|
|
16
|
+
#
|
|
17
|
+
# @example Parse JSON string
|
|
18
|
+
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
10
19
|
def load(string, options = {})
|
|
11
20
|
::Yajl::Parser.new(symbolize_keys: options[:symbolize_keys]).parse(string)
|
|
12
21
|
end
|
|
13
22
|
|
|
23
|
+
# Serialize a Ruby object to JSON
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
# @param object [Object] object to serialize
|
|
27
|
+
# @param options [Hash] serialization options
|
|
28
|
+
# @return [String] JSON string
|
|
29
|
+
#
|
|
30
|
+
# @example Serialize object to JSON
|
|
31
|
+
# adapter.dump({key: "value"}) #=> '{"key":"value"}'
|
|
14
32
|
def dump(object, options = {})
|
|
15
33
|
::Yajl::Encoder.encode(object, options)
|
|
16
34
|
end
|
|
@@ -1,49 +1,64 @@
|
|
|
1
1
|
module MultiJson
|
|
2
|
+
# Mixin for converting hash keys between symbols and strings
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
2
5
|
module ConvertibleHashKeys
|
|
3
6
|
SIMPLE_OBJECT_CLASSES = [String, Numeric, TrueClass, FalseClass, NilClass].freeze
|
|
4
7
|
private_constant :SIMPLE_OBJECT_CLASSES
|
|
5
8
|
|
|
6
9
|
private
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
# Converts hash keys to symbols recursively
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
# @param value [Object] value to convert
|
|
15
|
+
# @return [Object] value with symbolized keys
|
|
16
|
+
def symbolize_keys(value)
|
|
17
|
+
convert_hash_keys(value) { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
12
18
|
end
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
# Converts hash keys to strings recursively
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
# @param value [Object] value to convert
|
|
24
|
+
# @return [Object] value with stringified keys
|
|
25
|
+
def stringify_keys(value)
|
|
26
|
+
convert_hash_keys(value) { |key| key.respond_to?(:to_s) ? key.to_s : key }
|
|
18
27
|
end
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
# Recursively converts hash keys using the given block
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @param value [Object] value to convert
|
|
33
|
+
# @yield [key] block to transform each key
|
|
34
|
+
# @return [Object] converted value
|
|
35
|
+
def convert_hash_keys(value, &key_modifier)
|
|
21
36
|
case value
|
|
22
|
-
when Array
|
|
23
|
-
handle_array(value, &block)
|
|
24
37
|
when Hash
|
|
25
|
-
|
|
38
|
+
value.to_h { |k, v| [key_modifier.call(k), convert_hash_keys(v, &key_modifier)] }
|
|
39
|
+
when Array
|
|
40
|
+
value.map { |v| convert_hash_keys(v, &key_modifier) }
|
|
26
41
|
else
|
|
27
|
-
|
|
42
|
+
convert_simple_object(value)
|
|
28
43
|
end
|
|
29
44
|
end
|
|
30
45
|
|
|
31
|
-
|
|
46
|
+
# Converts non-hash objects to a JSON-safe format
|
|
47
|
+
#
|
|
48
|
+
# @api private
|
|
49
|
+
# @param obj [Object] object to convert
|
|
50
|
+
# @return [Object] converted object
|
|
51
|
+
def convert_simple_object(obj)
|
|
32
52
|
return obj if simple_object?(obj) || obj.respond_to?(:to_json)
|
|
33
53
|
|
|
34
54
|
obj.respond_to?(:to_s) ? obj.to_s : obj
|
|
35
55
|
end
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
57
|
+
# Checks if an object is a simple JSON-safe type
|
|
58
|
+
#
|
|
59
|
+
# @api private
|
|
60
|
+
# @param obj [Object] object to check
|
|
61
|
+
# @return [Boolean] true if object is a simple type
|
|
47
62
|
def simple_object?(obj)
|
|
48
63
|
SIMPLE_OBJECT_CLASSES.any? { |klass| obj.is_a?(klass) }
|
|
49
64
|
end
|