multi_json 1.20.1 → 1.21.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/README.md +115 -38
- data/lib/multi_json/adapter.rb +62 -10
- data/lib/multi_json/adapter_error.rb +1 -1
- data/lib/multi_json/adapter_selector.rb +34 -14
- data/lib/multi_json/adapters/fast_jsonparser.rb +12 -10
- data/lib/multi_json/adapters/json_gem.rb +2 -19
- data/lib/multi_json/adapters/oj.rb +6 -6
- data/lib/multi_json/adapters/oj_common.rb +1 -1
- data/lib/multi_json/adapters/yajl.rb +1 -1
- data/lib/multi_json/concurrency.rb +7 -7
- data/lib/multi_json/deprecated.rb +26 -23
- data/lib/multi_json/options.rb +97 -23
- data/lib/multi_json/options_cache/mutex_store.rb +1 -1
- data/lib/multi_json/options_cache.rb +7 -7
- data/lib/multi_json/parse_error.rb +1 -1
- data/lib/multi_json/version.rb +6 -6
- data/lib/multi_json.rb +123 -53
- metadata +5 -7
- data/CHANGELOG.md +0 -312
- data/CONTRIBUTING.md +0 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6eb65ee97a46816e1bfe43fbeff173a83846bb1ff85e3c5e4fe97c80cec4dacf
|
|
4
|
+
data.tar.gz: 9eedd9bd17375a5540dc4878613a9165e49925d1997c24f4d9776f41efb1d7f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c727bce3f895367c1092319c4725dedde9252706cc6da7a5d8b1ef4bef5abb025869b7a04dc44218dfe53198b8da0c0c36e1d61037824a34a59dcfd78a030f68
|
|
7
|
+
data.tar.gz: 2dc0b84560382d05f58549875227be1dbc32c274172f2c67e4c395f4baede84217061a10fcf22ebd078ce7e89fb25a88e067f7424b4bdd830c75081e96d2938f
|
data/README.md
CHANGED
|
@@ -16,22 +16,33 @@ fastest available JSON coder. Here's how to use it:
|
|
|
16
16
|
```ruby
|
|
17
17
|
require "multi_json"
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
MultiJSON.parse('{"abc":"def"}') #=> {"abc" => "def"}
|
|
20
|
+
MultiJSON.parse('{"abc":"def"}', symbolize_names: true) #=> {abc: "def"}
|
|
21
|
+
MultiJSON.generate({abc: "def"}) # convert Ruby back to JSON
|
|
22
|
+
MultiJSON.generate({abc: "def"}, pretty: true) # encoded in a pretty form (if supported by the coder)
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
> [!IMPORTANT]
|
|
26
|
+
> **1.21.0 renames the public API to match Ruby stdlib `JSON`.** The canonical
|
|
27
|
+
> verbs are now `MultiJSON.parse` / `MultiJSON.generate`, and the canonical
|
|
28
|
+
> module is `MultiJSON` (all-caps). The legacy `MultiJson` constant,
|
|
29
|
+
> `MultiJSON.load` / `MultiJSON.dump`, `:symbolize_keys`, and friends still
|
|
30
|
+
> work but emit one-time deprecation warnings and **will be removed in 2.0.0**.
|
|
31
|
+
> Run your app with `ruby -W:deprecated` to surface them; the warnings are
|
|
32
|
+
> tagged with the `:deprecated` category so you can silence the whole set with
|
|
33
|
+
> `Warning[:deprecated] = false`. See [Deprecated in 1.21.0](#deprecated-in-1210)
|
|
34
|
+
> for the full list.
|
|
35
|
+
|
|
36
|
+
`MultiJSON.parse` returns `nil` for `nil`, empty, and whitespace-only inputs
|
|
26
37
|
instead of raising, so a missing or blank payload is observable as a `nil`
|
|
27
|
-
return value rather than an exception. When
|
|
28
|
-
will throw a `
|
|
29
|
-
`
|
|
38
|
+
return value rather than an exception. When parsing invalid JSON, MultiJSON
|
|
39
|
+
will throw a `MultiJSON::ParseError`. `MultiJSON::DecodeError` and
|
|
40
|
+
`MultiJSON::LoadError` are aliases for backwards compatibility.
|
|
30
41
|
|
|
31
42
|
```ruby
|
|
32
43
|
begin
|
|
33
|
-
|
|
34
|
-
rescue
|
|
44
|
+
MultiJSON.parse("{invalid json}")
|
|
45
|
+
rescue MultiJSON::ParseError => exception
|
|
35
46
|
exception.data #=> "{invalid json}"
|
|
36
47
|
exception.cause #=> JSON::ParserError: ...
|
|
37
48
|
exception.line #=> 1 (for adapters that report a location, e.g. Oj or the json gem)
|
|
@@ -39,6 +50,61 @@ rescue MultiJson::ParseError => exception
|
|
|
39
50
|
end
|
|
40
51
|
```
|
|
41
52
|
|
|
53
|
+
### Drop-in replacement for stdlib `JSON`
|
|
54
|
+
|
|
55
|
+
MultiJSON mirrors the surface of Ruby's stdlib [`JSON`][json-gem] so
|
|
56
|
+
most call sites swap in with a one-line change:
|
|
57
|
+
|
|
58
|
+
```diff
|
|
59
|
+
- require "json"
|
|
60
|
+
+ require "multi_json"
|
|
61
|
+
|
|
62
|
+
- JSON.parse(text, symbolize_names: true)
|
|
63
|
+
+ MultiJSON.parse(text, symbolize_names: true)
|
|
64
|
+
|
|
65
|
+
- JSON.generate(object, pretty: true)
|
|
66
|
+
+ MultiJSON.generate(object, pretty: true)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Method names and the common options line up with stdlib so existing
|
|
70
|
+
pretty-print calls and option keys keep working without changes:
|
|
71
|
+
|
|
72
|
+
| stdlib `JSON` | `MultiJSON` | Status |
|
|
73
|
+
| ---------------------- | -------------------------- | :---: |
|
|
74
|
+
| `JSON.parse(str)` | `MultiJSON.parse(str)` | ✓ |
|
|
75
|
+
| `JSON.generate(obj)` | `MultiJSON.generate(obj)` | ✓ |
|
|
76
|
+
| `pretty: true` | `pretty: true` | ✓ |
|
|
77
|
+
| `symbolize_names: true` | `symbolize_names: true` | ✓ |
|
|
78
|
+
|
|
79
|
+
### Deprecated in 1.21.0
|
|
80
|
+
|
|
81
|
+
The module constant and primary verbs were renamed to match Ruby
|
|
82
|
+
stdlib `JSON.parse` / `JSON.generate` and the JSON spec (RFC 8259).
|
|
83
|
+
The old names still work in 1.x but now emit a one-time deprecation
|
|
84
|
+
warning; **they will be removed in 2.0.0**.
|
|
85
|
+
|
|
86
|
+
| Deprecated | Use instead |
|
|
87
|
+
| ----------------------------- | ------------------------------- |
|
|
88
|
+
| `MultiJson` (constant) | `MultiJSON` (all-caps) |
|
|
89
|
+
| `MultiJSON.load(str)` | `MultiJSON.parse(str)` |
|
|
90
|
+
| `MultiJSON.dump(obj)` | `MultiJSON.generate(obj)` |
|
|
91
|
+
| `MultiJSON.load_options=` | `MultiJSON.parse_options=` |
|
|
92
|
+
| `MultiJSON.load_options` | `MultiJSON.parse_options` |
|
|
93
|
+
| `MultiJSON.dump_options=` | `MultiJSON.generate_options=` |
|
|
94
|
+
| `MultiJSON.dump_options` | `MultiJSON.generate_options` |
|
|
95
|
+
| `symbolize_keys:` option | `symbolize_names:` option |
|
|
96
|
+
|
|
97
|
+
The `MultiJson` constant (CamelCase) continues to work as a thin
|
|
98
|
+
delegator; every method call, constant lookup, and rescue clause
|
|
99
|
+
routes through `MultiJSON` transparently.
|
|
100
|
+
|
|
101
|
+
> [!TIP]
|
|
102
|
+
> The recommended upgrade path to 2.0 is: pin `~> 1.21` first, run
|
|
103
|
+
> `ruby -W:deprecated` against your app or test suite to surface every
|
|
104
|
+
> deprecation, migrate each call site to the canonical name, then bump to
|
|
105
|
+
> `~> 2.0`. The 2.0 release deletes the deprecated aliases entirely, so the
|
|
106
|
+
> warnings during 1.21.x are your map.
|
|
107
|
+
|
|
42
108
|
`ParseError` instance has `cause` reader which contains the original exception.
|
|
43
109
|
It also has `data` reader with the input that caused the problem, and `line`/`column`
|
|
44
110
|
readers populated for adapters whose error messages include a location (Oj and the
|
|
@@ -46,13 +112,13 @@ json gem). Adapters that don't include one (Yajl, fast_jsonparser) leave both ni
|
|
|
46
112
|
|
|
47
113
|
### Tuning the options cache
|
|
48
114
|
|
|
49
|
-
MultiJSON memoizes the merged option hash for each `
|
|
50
|
-
option hashes don't trigger repeated work. The cache is bounded —
|
|
51
|
-
entries per direction — and applications that generate many
|
|
52
|
-
can raise the ceiling at runtime:
|
|
115
|
+
MultiJSON memoizes the merged option hash for each `parse`/`generate` call so
|
|
116
|
+
identical option hashes don't trigger repeated work. The cache is bounded —
|
|
117
|
+
defaulting to 1000 entries per direction — and applications that generate many
|
|
118
|
+
distinct option hashes can raise the ceiling at runtime:
|
|
53
119
|
|
|
54
120
|
```ruby
|
|
55
|
-
|
|
121
|
+
MultiJSON::OptionsCache.max_cache_size = 5000
|
|
56
122
|
```
|
|
57
123
|
|
|
58
124
|
`max_cache_size` must be a positive integer; `0`, negative values, and
|
|
@@ -60,14 +126,14 @@ non-integers raise `ArgumentError`.
|
|
|
60
126
|
|
|
61
127
|
Lowering the limit only takes effect for *new* inserts; existing cache
|
|
62
128
|
entries are left in place until normal eviction trims them below the
|
|
63
|
-
new ceiling. Call `
|
|
129
|
+
new ceiling. Call `MultiJSON::OptionsCache.reset` if you want to evict
|
|
64
130
|
immediately.
|
|
65
131
|
|
|
66
132
|
The `use` method, which sets the MultiJSON adapter, takes either a symbol or a
|
|
67
133
|
class (to allow for custom JSON parsers) that responds to both `.load` and `.dump`
|
|
68
134
|
at the class level.
|
|
69
135
|
|
|
70
|
-
When MultiJSON fails to load the specified adapter, it'll throw `
|
|
136
|
+
When MultiJSON fails to load the specified adapter, it'll throw `MultiJSON::AdapterError`
|
|
71
137
|
which inherits from `ArgumentError`.
|
|
72
138
|
|
|
73
139
|
### Writing a custom adapter
|
|
@@ -88,38 +154,49 @@ class MyAdapter
|
|
|
88
154
|
end
|
|
89
155
|
end
|
|
90
156
|
|
|
91
|
-
|
|
157
|
+
MultiJSON.use(MyAdapter)
|
|
92
158
|
```
|
|
93
159
|
|
|
94
|
-
`ParseError` is required: `
|
|
95
|
-
to wrap parse failures in `
|
|
96
|
-
omits the constant raises `
|
|
160
|
+
`ParseError` is required: `MultiJSON.parse` rescues `MyAdapter::ParseError`
|
|
161
|
+
to wrap parse failures in `MultiJSON::ParseError`, and an adapter that
|
|
162
|
+
omits the constant raises `MultiJSON::AdapterError` on the first parse
|
|
97
163
|
attempt instead of producing a confusing `NameError`.
|
|
98
164
|
|
|
99
|
-
For more, inherit from `
|
|
165
|
+
For more, inherit from `MultiJSON::Adapter` to pick up shared option
|
|
100
166
|
merging, the `defaults :load, ...` / `defaults :dump, ...` DSL, and the
|
|
101
167
|
blank-input short-circuit. The built-in adapters in
|
|
102
168
|
`lib/multi_json/adapters/` are working examples.
|
|
103
169
|
|
|
170
|
+
> [!NOTE]
|
|
171
|
+
> The adapter contract methods on the adapter class itself stay named
|
|
172
|
+
> `.load` / `.dump` in 1.21.x (and the `defaults :load, ...` / `defaults
|
|
173
|
+
> :dump, ...` DSL keys match). The 2.0 release renames them to `.parse` /
|
|
174
|
+
> `.generate` to align with the public API; if you ship a custom adapter,
|
|
175
|
+
> you'll need to rename those methods (and the `defaults` keys) when you
|
|
176
|
+
> upgrade.
|
|
177
|
+
|
|
104
178
|
MultiJSON tries to have intelligent defaulting. If any supported library is
|
|
105
179
|
already loaded, MultiJSON uses it before attempting to load others. When no
|
|
106
180
|
backend is preloaded, MultiJSON walks its preference list and uses the first
|
|
107
|
-
one that loads successfully
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
181
|
+
one that loads successfully. The list is split per platform — JRuby's
|
|
182
|
+
available adapter set differs from MRI's, and the bundled benchmark suite
|
|
183
|
+
ranks `json_gem` ahead of `fast_jsonparser`/`oj`/`yajl` on Ruby 3.4+. CI
|
|
184
|
+
re-runs the benchmark and fails if the observed ranking diverges from the
|
|
185
|
+
table below.
|
|
186
|
+
|
|
187
|
+
| rank | MRI / TruffleRuby | JRuby |
|
|
188
|
+
| ---- | ----------------- | --------------- |
|
|
189
|
+
| 1 | The JSON gem | `jrjackson` |
|
|
190
|
+
| 2 | `fast_jsonparser` | The JSON gem |
|
|
191
|
+
| 3 | `oj` | `gson` |
|
|
192
|
+
| 4 | `yajl-ruby` | — |
|
|
193
|
+
|
|
194
|
+
A dash means the adapter isn't usable on that runtime: `fast_jsonparser`,
|
|
195
|
+
`oj`, and `yajl-ruby` are MRI/TruffleRuby C extensions with no JRuby builds;
|
|
196
|
+
`jrjackson` and `gson` are JRuby-only. The JSON gem is a Ruby default gem,
|
|
197
|
+
so it's always available as a last-resort fallback on any supported Ruby.
|
|
198
|
+
If you have a workload where a different backend is faster, set it
|
|
199
|
+
explicitly with `MultiJSON.use(:your_adapter)`.
|
|
123
200
|
|
|
124
201
|
## Gem Variants
|
|
125
202
|
|
data/lib/multi_json/adapter.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "singleton"
|
|
4
4
|
require_relative "options"
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module MultiJSON
|
|
7
7
|
# Base class for JSON adapter implementations
|
|
8
8
|
#
|
|
9
9
|
# Each adapter wraps a specific JSON library (Oj, JSON gem, etc.) and
|
|
@@ -24,7 +24,7 @@ module MultiJson
|
|
|
24
24
|
VALID_DEFAULTS_ACTIONS = %i[load dump].freeze
|
|
25
25
|
private_constant :BLANK_PATTERN, :VALID_DEFAULTS_ACTIONS
|
|
26
26
|
|
|
27
|
-
# Get default
|
|
27
|
+
# Get default parse options, walking the superclass chain
|
|
28
28
|
#
|
|
29
29
|
# Returns the closest ancestor's `@default_load_options` ivar so a
|
|
30
30
|
# parent class calling {.defaults} after a subclass has been
|
|
@@ -33,18 +33,36 @@ module MultiJson
|
|
|
33
33
|
#
|
|
34
34
|
# @api private
|
|
35
35
|
# @return [Hash] frozen options hash
|
|
36
|
-
def
|
|
36
|
+
def default_parse_options
|
|
37
37
|
walk_default_options(:@default_load_options)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Get default
|
|
40
|
+
# Get default generate options, walking the superclass chain
|
|
41
41
|
#
|
|
42
42
|
# @api private
|
|
43
43
|
# @return [Hash] frozen options hash
|
|
44
|
-
def
|
|
44
|
+
def default_generate_options
|
|
45
45
|
walk_default_options(:@default_dump_options)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# Get default parse options, walking the superclass chain
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
# @deprecated Use {.default_parse_options} instead. Will be removed in v2.0.
|
|
52
|
+
# @return [Hash] frozen options hash
|
|
53
|
+
def default_load_options
|
|
54
|
+
default_parse_options
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get default generate options, walking the superclass chain
|
|
58
|
+
#
|
|
59
|
+
# @api private
|
|
60
|
+
# @deprecated Use {.default_generate_options} instead. Will be removed in v2.0.
|
|
61
|
+
# @return [Hash] frozen options hash
|
|
62
|
+
def default_dump_options
|
|
63
|
+
default_generate_options
|
|
64
|
+
end
|
|
65
|
+
|
|
48
66
|
# DSL for setting adapter-specific default options
|
|
49
67
|
#
|
|
50
68
|
# ``action`` must be ``:load`` or ``:dump``; ``value`` must be a
|
|
@@ -59,7 +77,7 @@ module MultiJson
|
|
|
59
77
|
# @raise [ArgumentError] when action is anything other than :load
|
|
60
78
|
# or :dump, or when value isn't a Hash
|
|
61
79
|
# @example Set load defaults for an adapter
|
|
62
|
-
# class MyAdapter <
|
|
80
|
+
# class MyAdapter < MultiJSON::Adapter
|
|
63
81
|
# defaults :load, symbolize_keys: false
|
|
64
82
|
# end
|
|
65
83
|
def defaults(action, value)
|
|
@@ -139,7 +157,7 @@ module MultiJson
|
|
|
139
157
|
BLANK_PATTERN.match?(input.valid_encoding? ? input : input.scrub)
|
|
140
158
|
end
|
|
141
159
|
|
|
142
|
-
# Merges
|
|
160
|
+
# Merges generate options from adapter, global, and call-site
|
|
143
161
|
#
|
|
144
162
|
# @api private
|
|
145
163
|
# @param options [Hash] call-site options
|
|
@@ -147,11 +165,18 @@ module MultiJson
|
|
|
147
165
|
def merged_dump_options(options)
|
|
148
166
|
cache_key = strip_adapter_key(options)
|
|
149
167
|
OptionsCache.dump.fetch(cache_key) do
|
|
150
|
-
|
|
168
|
+
generate_options(cache_key).merge(MultiJSON.generate_options(cache_key)).merge!(cache_key)
|
|
151
169
|
end
|
|
152
170
|
end
|
|
153
171
|
|
|
154
|
-
# Merges
|
|
172
|
+
# Merges parse options from adapter, global, and call-site
|
|
173
|
+
#
|
|
174
|
+
# Each layer is normalized first so a deprecated ``:symbolize_keys``
|
|
175
|
+
# key in any source becomes the canonical ``:symbolize_names`` —
|
|
176
|
+
# done per-layer rather than post-merge so the expected override
|
|
177
|
+
# semantics (call-site > global > adapter default) still apply
|
|
178
|
+
# when a caller mixes the deprecated and canonical names across
|
|
179
|
+
# layers.
|
|
155
180
|
#
|
|
156
181
|
# @api private
|
|
157
182
|
# @param options [Hash] call-site options
|
|
@@ -159,10 +184,37 @@ module MultiJson
|
|
|
159
184
|
def merged_load_options(options)
|
|
160
185
|
cache_key = strip_adapter_key(options)
|
|
161
186
|
OptionsCache.load.fetch(cache_key) do
|
|
162
|
-
|
|
187
|
+
adapter = normalize_symbolize_option(parse_options(cache_key))
|
|
188
|
+
global = normalize_symbolize_option(MultiJSON.parse_options(cache_key))
|
|
189
|
+
call_site = normalize_symbolize_option(cache_key)
|
|
190
|
+
adapter.merge(global).merge!(call_site)
|
|
163
191
|
end
|
|
164
192
|
end
|
|
165
193
|
|
|
194
|
+
# Translate the deprecated ``:symbolize_keys`` option to ``:symbolize_names``
|
|
195
|
+
#
|
|
196
|
+
# Matches Ruby stdlib's ``JSON.parse`` naming. Emits a one-time
|
|
197
|
+
# deprecation warning on first encounter of ``:symbolize_keys``.
|
|
198
|
+
# When both names appear in the same layer (unusual — only
|
|
199
|
+
# possible if the caller explicitly set both), the canonical
|
|
200
|
+
# ``:symbolize_names`` value wins and ``:symbolize_keys`` is
|
|
201
|
+
# silently dropped.
|
|
202
|
+
#
|
|
203
|
+
# @api private
|
|
204
|
+
# @param options [Hash] options layer to normalize
|
|
205
|
+
# @return [Hash] hash with ``:symbolize_keys`` translated, or the
|
|
206
|
+
# original hash when no translation is needed
|
|
207
|
+
def normalize_symbolize_option(options)
|
|
208
|
+
return options unless options.key?(:symbolize_keys)
|
|
209
|
+
|
|
210
|
+
MultiJSON.warn_deprecation_once(:symbolize_keys_option,
|
|
211
|
+
"The :symbolize_keys option is deprecated and will be removed in v2.0. Use :symbolize_names instead.")
|
|
212
|
+
|
|
213
|
+
new_opts = options.except(:symbolize_keys)
|
|
214
|
+
new_opts[:symbolize_names] = options[:symbolize_keys] unless new_opts.key?(:symbolize_names)
|
|
215
|
+
new_opts
|
|
216
|
+
end
|
|
217
|
+
|
|
166
218
|
# Removes the :adapter key from options for cache key
|
|
167
219
|
#
|
|
168
220
|
# Returns a shared frozen empty hash for the common no-options call
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module MultiJSON
|
|
4
4
|
# Handles adapter discovery, loading, and selection
|
|
5
5
|
#
|
|
6
6
|
# Adapters can be specified as:
|
|
@@ -17,14 +17,34 @@ module MultiJson
|
|
|
17
17
|
# constant whose presence indicates the backing library is already
|
|
18
18
|
# loaded. ``loaded`` is a ``::``-separated path so we can walk it
|
|
19
19
|
# without an explicit ``defined?`` check.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
#
|
|
21
|
+
# The hash order is split per platform: on MRI/TruffleRuby the
|
|
22
|
+
# bundled benchmark suite ranks json_gem ahead of fast_jsonparser/
|
|
23
|
+
# oj/yajl on Ruby 3.4+; on JRuby the FFI-vs-pure-Ruby tradeoff
|
|
24
|
+
# hasn't been re-benchmarked yet, so jr_jackson stays first there.
|
|
25
|
+
# CI re-runs the benchmark with ``--verify-preference`` to fail
|
|
26
|
+
# if the observed ranking diverges.
|
|
27
|
+
# :nocov:
|
|
28
|
+
ADAPTERS = if RUBY_ENGINE == "jruby"
|
|
29
|
+
{
|
|
30
|
+
jr_jackson: {require: "jrjackson", loaded: "JrJackson"},
|
|
31
|
+
json_gem: {require: "json", loaded: "JSON::Ext::Parser"},
|
|
32
|
+
gson: {require: "gson", loaded: "Gson"},
|
|
33
|
+
fast_jsonparser: {require: "fast_jsonparser", loaded: "FastJsonparser"},
|
|
34
|
+
oj: {require: "oj", loaded: "Oj"},
|
|
35
|
+
yajl: {require: "yajl", loaded: "Yajl"}
|
|
36
|
+
}.freeze
|
|
37
|
+
else
|
|
38
|
+
{
|
|
39
|
+
json_gem: {require: "json", loaded: "JSON::Ext::Parser"},
|
|
40
|
+
fast_jsonparser: {require: "fast_jsonparser", loaded: "FastJsonparser"},
|
|
41
|
+
oj: {require: "oj", loaded: "Oj"},
|
|
42
|
+
yajl: {require: "yajl", loaded: "Yajl"},
|
|
43
|
+
jr_jackson: {require: "jrjackson", loaded: "JrJackson"},
|
|
44
|
+
gson: {require: "gson", loaded: "Gson"}
|
|
45
|
+
}.freeze
|
|
46
|
+
end
|
|
47
|
+
# :nocov:
|
|
28
48
|
private_constant :ADAPTERS
|
|
29
49
|
|
|
30
50
|
# Backwards-compatible view of {ADAPTERS} that exposes only the
|
|
@@ -46,13 +66,13 @@ module MultiJson
|
|
|
46
66
|
#
|
|
47
67
|
# Used by adapters that only implement one direction (e.g.
|
|
48
68
|
# FastJsonparser only parses) so the other direction can be delegated
|
|
49
|
-
# to whichever library
|
|
69
|
+
# to whichever library MultiJSON would otherwise pick.
|
|
50
70
|
#
|
|
51
71
|
# @api private
|
|
52
72
|
# @param excluded [Symbol] adapter name to skip during detection
|
|
53
73
|
# @return [Class] the adapter class
|
|
54
74
|
# @example
|
|
55
|
-
# AdapterSelector.default_adapter_excluding(:fast_jsonparser) #=>
|
|
75
|
+
# AdapterSelector.default_adapter_excluding(:fast_jsonparser) #=> MultiJSON::Adapters::Oj
|
|
56
76
|
def default_adapter_excluding(excluded)
|
|
57
77
|
Concurrency.synchronize(:default_adapter) do
|
|
58
78
|
name = loaded_adapter(excluding: excluded)
|
|
@@ -131,7 +151,7 @@ module MultiJson
|
|
|
131
151
|
# @return [void]
|
|
132
152
|
def warn_about_fallback
|
|
133
153
|
Kernel.warn(
|
|
134
|
-
"[WARNING]
|
|
154
|
+
"[WARNING] MultiJSON is falling back to the json_gem adapter " \
|
|
135
155
|
"because no other JSON library could be loaded."
|
|
136
156
|
)
|
|
137
157
|
end
|
|
@@ -169,7 +189,7 @@ module MultiJson
|
|
|
169
189
|
require_relative "adapters/#{normalized}"
|
|
170
190
|
|
|
171
191
|
class_name = normalized.split("_").map(&:capitalize).join
|
|
172
|
-
::
|
|
192
|
+
::MultiJSON::Adapters.const_get(class_name)
|
|
173
193
|
end
|
|
174
194
|
|
|
175
195
|
# Validate that an adapter satisfies the documented contract
|
|
@@ -187,7 +207,7 @@ module MultiJson
|
|
|
187
207
|
raise AdapterError, "Adapter #{adapter} must respond to .load" unless adapter.respond_to?(:load)
|
|
188
208
|
raise AdapterError, "Adapter #{adapter} must respond to .dump" unless adapter.respond_to?(:dump)
|
|
189
209
|
|
|
190
|
-
|
|
210
|
+
MultiJSON.parse_error_class_for(adapter)
|
|
191
211
|
adapter
|
|
192
212
|
end
|
|
193
213
|
end
|
|
@@ -4,13 +4,13 @@ require "fast_jsonparser"
|
|
|
4
4
|
require_relative "../adapter"
|
|
5
5
|
require_relative "../adapter_selector"
|
|
6
6
|
|
|
7
|
-
module
|
|
7
|
+
module MultiJSON
|
|
8
8
|
module Adapters
|
|
9
9
|
# Use the FastJsonparser library to load, and the fastest other
|
|
10
10
|
# available adapter to dump.
|
|
11
11
|
#
|
|
12
12
|
# FastJsonparser only implements parsing, so the ``dump`` side of
|
|
13
|
-
# the adapter is delegated to whichever adapter
|
|
13
|
+
# the adapter is delegated to whichever adapter MultiJSON would
|
|
14
14
|
# pick if FastJsonparser weren't installed (oj → yajl → jr_jackson
|
|
15
15
|
# → json_gem → gson). The delegate is resolved lazily at the first
|
|
16
16
|
# ``dump`` call, not at file load time, so load order doesn't lock
|
|
@@ -18,7 +18,7 @@ module MultiJson
|
|
|
18
18
|
# the first ``dump`` call (typical applications already have ``oj``
|
|
19
19
|
# loaded by then).
|
|
20
20
|
class FastJsonparser < Adapter
|
|
21
|
-
defaults :load,
|
|
21
|
+
defaults :load, symbolize_names: false
|
|
22
22
|
|
|
23
23
|
# Exception raised when JSON parsing fails
|
|
24
24
|
ParseError = ::FastJsonparser::ParseError
|
|
@@ -43,8 +43,8 @@ module MultiJson
|
|
|
43
43
|
# @api private
|
|
44
44
|
# @return [Class] delegate adapter class
|
|
45
45
|
def dump_delegate
|
|
46
|
-
|
|
47
|
-
@dump_delegate ||=
|
|
46
|
+
MultiJSON::Concurrency.synchronize(:dump_delegate) do
|
|
47
|
+
@dump_delegate ||= MultiJSON::AdapterSelector.default_adapter_excluding(:fast_jsonparser)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -53,19 +53,21 @@ module MultiJson
|
|
|
53
53
|
#
|
|
54
54
|
# FastJsonparser.parse only accepts ``symbolize_keys`` and raises
|
|
55
55
|
# on unknown keyword arguments, so the adapter explicitly forwards
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
56
|
+
# MultiJSON's canonical ``:symbolize_names`` option as
|
|
57
|
+
# FastJsonparser's native ``symbolize_keys:`` kwarg and silently
|
|
58
|
+
# drops the rest. Pass other options through
|
|
59
|
+
# ``MultiJSON.parse_options=`` and they'll apply to whichever
|
|
60
|
+
# adapter MultiJSON selects when fast_jsonparser isn't installed.
|
|
59
61
|
#
|
|
60
62
|
# @api private
|
|
61
63
|
# @param string [String] JSON string to parse
|
|
62
|
-
# @param options [Hash] parsing options (only :
|
|
64
|
+
# @param options [Hash] parsing options (only :symbolize_names is honored)
|
|
63
65
|
# @return [Object] parsed Ruby object
|
|
64
66
|
#
|
|
65
67
|
# @example Parse JSON string
|
|
66
68
|
# adapter.load('{"key":"value"}') #=> {"key" => "value"}
|
|
67
69
|
def load(string, options = {})
|
|
68
|
-
::FastJsonparser.parse(string, symbolize_keys: options[:
|
|
70
|
+
::FastJsonparser.parse(string, symbolize_keys: options[:symbolize_names])
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative "../adapter"
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module MultiJSON
|
|
7
7
|
module Adapters
|
|
8
8
|
# Use the JSON gem to dump/load.
|
|
9
9
|
class JsonGem < Adapter
|
|
@@ -44,7 +44,7 @@ module MultiJson
|
|
|
44
44
|
raise ::JSON::ParserError, "Invalid UTF-8 byte sequence in JSON input" unless string.valid_encoding?
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
::JSON.parse(string,
|
|
47
|
+
::JSON.parse(string, options)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Serialize a Ruby object to JSON
|
|
@@ -67,23 +67,6 @@ module MultiJson
|
|
|
67
67
|
|
|
68
68
|
::JSON.pretty_generate(json_object, PRETTY_STATE_PROTOTYPE.merge(options.except(:pretty)))
|
|
69
69
|
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
# Translate ``:symbolize_keys`` into JSON gem's ``:symbolize_names``
|
|
74
|
-
#
|
|
75
|
-
# Returns a new hash without mutating the input. ``options`` is the
|
|
76
|
-
# cached hash returned from {Adapter.merged_load_options}, so in-place
|
|
77
|
-
# edits would pollute the cache and corrupt subsequent calls.
|
|
78
|
-
#
|
|
79
|
-
# @api private
|
|
80
|
-
# @param options [Hash] merged load options
|
|
81
|
-
# @return [Hash] options with ``:symbolize_keys`` translated
|
|
82
|
-
def translate_load_options(options)
|
|
83
|
-
return options unless options[:symbolize_keys]
|
|
84
|
-
|
|
85
|
-
options.except(:symbolize_keys).merge(symbolize_names: true)
|
|
86
|
-
end
|
|
87
70
|
end
|
|
88
71
|
end
|
|
89
72
|
end
|
|
@@ -4,7 +4,7 @@ require "oj"
|
|
|
4
4
|
require_relative "../adapter"
|
|
5
5
|
require_relative "oj_common"
|
|
6
6
|
|
|
7
|
-
module
|
|
7
|
+
module MultiJSON
|
|
8
8
|
# Namespace for JSON adapter implementations
|
|
9
9
|
#
|
|
10
10
|
# Each adapter wraps a specific JSON library and provides a consistent
|
|
@@ -14,7 +14,7 @@ module MultiJson
|
|
|
14
14
|
class Oj < Adapter
|
|
15
15
|
include OjCommon
|
|
16
16
|
|
|
17
|
-
defaults :load, mode: :strict,
|
|
17
|
+
defaults :load, mode: :strict, symbolize_names: false
|
|
18
18
|
defaults :dump, mode: :compat, time_format: :ruby, use_to_json: true
|
|
19
19
|
|
|
20
20
|
# In certain cases the Oj gem may throw a ``JSON::ParserError``
|
|
@@ -69,10 +69,10 @@ module MultiJson
|
|
|
69
69
|
|
|
70
70
|
private
|
|
71
71
|
|
|
72
|
-
# Translate ``:
|
|
72
|
+
# Translate ``:symbolize_names`` into Oj's ``:symbol_keys``
|
|
73
73
|
#
|
|
74
74
|
# Returns a new hash without mutating the input.
|
|
75
|
-
# ``:symbol_keys`` is always set (true or false) so
|
|
75
|
+
# ``:symbol_keys`` is always set (true or false) so MultiJSON's
|
|
76
76
|
# behavior is independent of any global ``Oj.default_options``
|
|
77
77
|
# the host application may have set. The input is the cached hash
|
|
78
78
|
# returned from {Adapter.merged_load_options}, so in-place edits
|
|
@@ -80,9 +80,9 @@ module MultiJson
|
|
|
80
80
|
#
|
|
81
81
|
# @api private
|
|
82
82
|
# @param options [Hash] merged load options
|
|
83
|
-
# @return [Hash] options with ``:
|
|
83
|
+
# @return [Hash] options with ``:symbolize_names`` translated
|
|
84
84
|
def translate_load_options(options)
|
|
85
|
-
options.except(:
|
|
85
|
+
options.except(:symbolize_names).merge(symbol_keys: options[:symbolize_names] == true)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
end
|