multi_json 1.20.0-java

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.
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJson
4
+ # Raised when JSON parsing fails
5
+ #
6
+ # Wraps the underlying adapter's parse error with the original input
7
+ # data, plus best-effort line and column extraction from the adapter's
8
+ # error message. Line/column are populated for adapters that include
9
+ # them in their messages (Oj, the json gem) and remain nil for
10
+ # adapters that don't (Yajl, fast_jsonparser).
11
+ #
12
+ # @api public
13
+ class ParseError < StandardError
14
+ # Regex that matches the "line N[, ]column M" fragment inside an
15
+ # adapter error message. The separator between line and column is
16
+ # permissive — Oj emits ``"line 1, column 3"`` while the json gem
17
+ # emits ``"line 1 column 2"`` — so ``[,\s]+`` covers both. Column
18
+ # is optional so messages like ``"at line 5"`` still yield a line.
19
+ LOCATION_PATTERN = /line\s+(\d+)(?:[,\s]+column\s+(\d+))?/i
20
+ private_constant :LOCATION_PATTERN
21
+
22
+ # The input string that failed to parse
23
+ #
24
+ # @api public
25
+ # @return [String, nil] the original input data
26
+ # @example
27
+ # error.data #=> "{invalid json}"
28
+ attr_reader :data
29
+
30
+ # The 1-based line number reported by the adapter
31
+ #
32
+ # @api public
33
+ # @return [Integer, nil] line number, or nil if the adapter's message
34
+ # did not include one
35
+ # @example
36
+ # error.line #=> 1
37
+ attr_reader :line
38
+
39
+ # The 1-based column reported by the adapter
40
+ #
41
+ # @api public
42
+ # @return [Integer, nil] column number, or nil if the adapter's message
43
+ # did not include one
44
+ # @example
45
+ # error.column #=> 3
46
+ attr_reader :column
47
+
48
+ # Create a new ParseError
49
+ #
50
+ # @api public
51
+ # @param message [String, nil] error message
52
+ # @param data [String, nil] the input that failed to parse
53
+ # @param cause [Exception, nil] the original exception
54
+ # @return [ParseError] new error instance
55
+ # @example
56
+ # ParseError.new("unexpected token at line 1 column 2", data: "{}")
57
+ def initialize(message = nil, data: nil, cause: nil)
58
+ super(message)
59
+ @data = data
60
+ match = location_match(message)
61
+ @line = match && Integer(match[1])
62
+ @column = match && match[2] && Integer(match[2])
63
+ set_backtrace(cause.backtrace) if cause
64
+ end
65
+
66
+ # Build a ParseError from an original exception
67
+ #
68
+ # @api public
69
+ # @param original_exception [Exception] the adapter's parse error
70
+ # @param data [String] the input that failed to parse
71
+ # @return [ParseError] new error with formatted message
72
+ # @example
73
+ # ParseError.build(JSON::ParserError.new("..."), "{bad json}")
74
+ def self.build(original_exception, data)
75
+ new(original_exception.message, data: data, cause: original_exception)
76
+ end
77
+
78
+ private
79
+
80
+ # Match an adapter error message against the line/column pattern
81
+ #
82
+ # Adapter error messages sometimes embed bytes from the failing
83
+ # input (e.g., the json gem's ``"invalid byte sequence in UTF-8"``
84
+ # error). The pattern is pure ASCII so it's compatible with any
85
+ # encoding, but a UTF-8 string with invalid bytes still trips the
86
+ # regex engine — ``String#scrub`` replaces those bytes so the
87
+ # match can proceed. Strings in binary (ASCII-8BIT) or any valid
88
+ # encoding pass through scrub untouched.
89
+ #
90
+ # @api private
91
+ # @param message [String, nil] the adapter's error message
92
+ # @return [MatchData, nil] the regex match, or nil if no message or
93
+ # no location fragment was found
94
+ def location_match(message)
95
+ return unless message
96
+
97
+ LOCATION_PATTERN.match(message.scrub)
98
+ end
99
+ end
100
+
101
+ # Legacy aliases for backward compatibility
102
+ DecodeError = LoadError = ParseError
103
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJson
4
+ # Version information for MultiJson
5
+ #
6
+ # @api private
7
+ class Version
8
+ # Major version number
9
+ MAJOR = 1 unless defined? MultiJson::Version::MAJOR
10
+ # Minor version number
11
+ MINOR = 20 unless defined? MultiJson::Version::MINOR
12
+ # Patch version number
13
+ PATCH = 0 unless defined? MultiJson::Version::PATCH
14
+ # Pre-release version suffix
15
+ PRE = nil unless defined? MultiJson::Version::PRE
16
+
17
+ class << self
18
+ # Return the version string
19
+ #
20
+ # @api private
21
+ # @return [String] version in semver format
22
+ def to_s
23
+ [MAJOR, MINOR, PATCH, PRE].compact.join(".")
24
+ end
25
+ end
26
+ end
27
+
28
+ # Current version string in semver format
29
+ VERSION = Version.to_s.freeze
30
+ end
data/lib/multi_json.rb ADDED
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "multi_json/concurrency"
4
+ require_relative "multi_json/options"
5
+ require_relative "multi_json/version"
6
+ require_relative "multi_json/adapter_error"
7
+ require_relative "multi_json/parse_error"
8
+ require_relative "multi_json/options_cache"
9
+ require_relative "multi_json/adapter_selector"
10
+
11
+ # A unified interface for JSON libraries in Ruby
12
+ #
13
+ # MultiJson allows swapping between JSON backends without changing your code.
14
+ # It auto-detects available JSON libraries and uses the fastest one available.
15
+ #
16
+ # ## Method-definition patterns
17
+ #
18
+ # The current public API uses two patterns, each chosen for a specific reason:
19
+ #
20
+ # 1. ``module_function`` creates both a class method and a private instance
21
+ # method from a single ``def``. This is used for the hot-path API
22
+ # (``adapter``, ``use``, ``adapter=``, ``load``, ``dump``,
23
+ # ``current_adapter``) so that both ``MultiJson.load(...)`` and legacy
24
+ # ``Class.new { include MultiJson }.new.send(:load, ...)`` invocations
25
+ # work through the same body. The instance versions are re-publicized
26
+ # below so YARD renders them as part of the public API.
27
+ # 2. ``def self.foo`` creates only a singleton method, giving mutation
28
+ # testing a single canonical definition to target. This is used for
29
+ # {.with_adapter}, which needs precise mutation coverage of its
30
+ # fiber-local save/restore logic.
31
+ #
32
+ # Deprecated public API (``decode``, ``encode``, ``engine``, etc.) lives in
33
+ # {file:lib/multi_json/deprecated.rb} so this file stays focused on the
34
+ # current surface.
35
+ #
36
+ # @example Basic usage
37
+ # MultiJson.load('{"foo":"bar"}') #=> {"foo" => "bar"}
38
+ # MultiJson.dump({foo: "bar"}) #=> '{"foo":"bar"}'
39
+ #
40
+ # @example Specifying an adapter
41
+ # MultiJson.use(:oj)
42
+ # MultiJson.load('{"foo":"bar"}', adapter: :json_gem)
43
+ #
44
+ # @api public
45
+ module MultiJson
46
+ extend Options
47
+ extend AdapterSelector
48
+
49
+ # Tracks which deprecation warnings have already been emitted so each one
50
+ # fires at most once per process. Stored as a Set rather than a Hash so
51
+ # presence checks have unambiguous semantics for mutation tests.
52
+ DEPRECATION_WARNINGS_SHOWN = Set.new
53
+ private_constant :DEPRECATION_WARNINGS_SHOWN
54
+
55
+ class << self
56
+ private
57
+
58
+ # Emit a deprecation warning at most once per process for the given key
59
+ #
60
+ # Defined as a singleton method (rather than via module_function) so
61
+ # there is exactly one definition for mutation tests to target. The
62
+ # deprecated method bodies invoke this via ``warn_deprecation_once(...)``
63
+ # (singleton callers) and via the private instance delegates routing
64
+ # through the singleton for legacy ``include MultiJson`` consumers.
65
+ #
66
+ # @api private
67
+ # @param key [Symbol] identifier for the deprecation (typically the method name)
68
+ # @param message [String] warning message to emit on first call
69
+ # @return [void]
70
+ # @example
71
+ # MultiJson.send(:warn_deprecation_once, :foo, "MultiJson.foo is deprecated")
72
+ def warn_deprecation_once(key, message)
73
+ Concurrency.synchronize(:deprecation_warnings) do
74
+ return if DEPRECATION_WARNINGS_SHOWN.include?(key)
75
+
76
+ Kernel.warn(message)
77
+ DEPRECATION_WARNINGS_SHOWN.add(key)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Resolve the ``ParseError`` constant for an adapter class
83
+ #
84
+ # The result is memoized on the adapter class itself in a
85
+ # ``@_multi_json_parse_error`` ivar so subsequent ``MultiJson.load``
86
+ # calls skip the constant lookup entirely. The lookup is performed
87
+ # with ``inherit: false`` so a stray top-level ``::ParseError``
88
+ # constant in the host process is correctly ignored on every
89
+ # supported Ruby implementation — TruffleRuby's ``::`` operator
90
+ # walks the ancestor chain and would otherwise pick up the top-level
91
+ # constant. Custom adapters that don't define their own
92
+ # ``ParseError`` get a clear {AdapterError} instead of the bare
93
+ # ``NameError`` Ruby would raise from the rescue clause.
94
+ #
95
+ # @api private
96
+ # @param adapter_class [Class] adapter class to inspect
97
+ # @return [Class] the adapter's ParseError class
98
+ # @raise [AdapterError] when the adapter doesn't define ParseError
99
+ def self.parse_error_class_for(adapter_class)
100
+ cached = adapter_class.instance_variable_get(:@_multi_json_parse_error)
101
+ return cached if cached
102
+
103
+ resolved = adapter_class.const_get(:ParseError, false)
104
+ adapter_class.instance_variable_set(:@_multi_json_parse_error, resolved)
105
+ rescue NameError
106
+ raise AdapterError, "Adapter #{adapter_class} must define a ParseError constant"
107
+ end
108
+
109
+ # ===========================================================================
110
+ # Public API (module_function: class + private instance method)
111
+ # ===========================================================================
112
+
113
+ # @!visibility private
114
+ module_function
115
+
116
+ # Returns the current adapter class
117
+ #
118
+ # Honors a fiber-local override set by {.with_adapter} so concurrent
119
+ # blocks observe their own adapter without clobbering the process-wide
120
+ # default. Falls back to the process default when no override is set.
121
+ #
122
+ # @api public
123
+ # @return [Class] the current adapter class
124
+ # @example
125
+ # MultiJson.adapter #=> MultiJson::Adapters::Oj
126
+ def adapter
127
+ override = Fiber[:multi_json_adapter]
128
+ return override if override
129
+
130
+ @adapter ||= use(nil)
131
+ end
132
+
133
+ # Sets the adapter to use for JSON operations
134
+ #
135
+ # The merged-options cache is only reset when the new adapter loads
136
+ # successfully. A failed ``use(:nonexistent)`` leaves the cache in
137
+ # place so the previously-active adapter keeps its cached entries.
138
+ #
139
+ # @api public
140
+ # @param new_adapter [Symbol, String, Module, nil] adapter specification
141
+ # @return [Class] the loaded adapter class
142
+ # @example
143
+ # MultiJson.use(:oj)
144
+ def use(new_adapter)
145
+ loaded = load_adapter(new_adapter)
146
+ Concurrency.synchronize(:adapter) do
147
+ OptionsCache.reset
148
+ @adapter = loaded
149
+ end
150
+ end
151
+
152
+ # Sets the adapter to use for JSON operations
153
+ #
154
+ # @api public
155
+ # @return [Class] the loaded adapter class
156
+ # @example
157
+ # MultiJson.adapter = :json_gem
158
+ alias_method :adapter=, :use
159
+ module_function :adapter=
160
+
161
+ # Parses a JSON string into a Ruby object
162
+ #
163
+ # Returns ``nil`` for ``nil``, empty, and whitespace-only inputs
164
+ # instead of raising. Pass an explicit non-blank string if you want
165
+ # to surface a {ParseError} for empty payloads at the call site.
166
+ #
167
+ # @api public
168
+ # @param string [String, #read] JSON string or IO-like object
169
+ # @param options [Hash] parsing options (adapter-specific)
170
+ # @return [Object, nil] parsed Ruby object, or nil for blank input
171
+ # @raise [ParseError] if parsing fails
172
+ # @raise [AdapterError] if the adapter doesn't define a ``ParseError`` constant
173
+ # @example
174
+ # MultiJson.load('{"foo":"bar"}') #=> {"foo" => "bar"}
175
+ # MultiJson.load("") #=> nil
176
+ # MultiJson.load(" \n") #=> nil
177
+ def load(string, options = {})
178
+ adapter_class = current_adapter(options)
179
+ parse_error_class = MultiJson.parse_error_class_for(adapter_class)
180
+ begin
181
+ adapter_class.load(string, options)
182
+ rescue parse_error_class => e
183
+ raise ParseError.build(e, string)
184
+ end
185
+ end
186
+
187
+ # Returns the adapter to use for the given options
188
+ #
189
+ # ``nil`` is accepted as a no-options sentinel — explicit
190
+ # ``current_adapter(nil)`` calls fall through to the process default
191
+ # adapter without raising.
192
+ #
193
+ # @api public
194
+ # @param options [Hash, nil] options that may contain :adapter key, or
195
+ # nil to use the process default
196
+ # @return [Class] adapter class
197
+ # @example
198
+ # MultiJson.current_adapter(adapter: :oj) #=> MultiJson::Adapters::Oj
199
+ def current_adapter(options = {})
200
+ options ||= Options::EMPTY_OPTIONS
201
+ adapter_override = options[:adapter]
202
+ adapter_override ? load_adapter(adapter_override) : adapter
203
+ end
204
+
205
+ # Serializes a Ruby object to a JSON string
206
+ #
207
+ # @api public
208
+ # @param object [Object] object to serialize
209
+ # @param options [Hash] serialization options (adapter-specific)
210
+ # @return [String] JSON string
211
+ # @example
212
+ # MultiJson.dump({foo: "bar"}) #=> '{"foo":"bar"}'
213
+ def dump(object, options = {})
214
+ current_adapter(options).dump(object, options)
215
+ end
216
+
217
+ # Re-publicize the instance versions of the module_function methods so
218
+ # YARD/yardstick render them as part of the public API and legacy
219
+ # ``include MultiJson`` consumers can call them without ``.send``.
220
+ public :adapter, :use, :adapter=, :load, :current_adapter, :dump
221
+
222
+ # ===========================================================================
223
+ # Public API (def self.foo: singleton-only, for mutation-test precision)
224
+ # ===========================================================================
225
+
226
+ # Executes a block using the specified adapter
227
+ #
228
+ # Defined as a singleton method so mutation testing has exactly one
229
+ # definition to target. The override is stored in fiber-local storage
230
+ # so concurrent fibers and threads each see their own adapter without
231
+ # racing on a shared module variable; nested calls save and restore
232
+ # the previous fiber-local value.
233
+ #
234
+ # @api public
235
+ # @param new_adapter [Symbol, String, Module] adapter to use
236
+ # @yield block to execute with the temporary adapter
237
+ # @return [Object] result of the block
238
+ # @example
239
+ # MultiJson.with_adapter(:json_gem) { MultiJson.dump({}) }
240
+ def self.with_adapter(new_adapter)
241
+ previous_override = Fiber[:multi_json_adapter]
242
+ Fiber[:multi_json_adapter] = load_adapter(new_adapter)
243
+ yield
244
+ ensure
245
+ Fiber[:multi_json_adapter] = previous_override
246
+ end
247
+
248
+ # ===========================================================================
249
+ # Private instance-method delegates for the singleton-only methods above
250
+ # ===========================================================================
251
+
252
+ private
253
+
254
+ # Instance-method delegate for {MultiJson.with_adapter}
255
+ #
256
+ # @api private
257
+ # @param new_adapter [Symbol, String, Module] adapter to use
258
+ # @yield block to execute with the temporary adapter
259
+ # @return [Object] result of the block
260
+ # @example
261
+ # class Foo; include MultiJson; end
262
+ # Foo.new.send(:with_adapter, :json_gem) { ... }
263
+ def with_adapter(new_adapter, &)
264
+ MultiJson.with_adapter(new_adapter, &)
265
+ end
266
+ end
267
+
268
+ require_relative "multi_json/deprecated"
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multi_json
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.20.0
5
+ platform: java
6
+ authors:
7
+ - Michael Bleigh
8
+ - Josh Kalderimis
9
+ - Erik Berlin
10
+ - Pavel Pravosud
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 1980-01-02 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: concurrent-ruby
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.2'
29
+ description: A common interface to multiple JSON libraries, including the JSON gem,
30
+ gson, and JrJackson.
31
+ email:
32
+ - sferik@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - CHANGELOG.md
38
+ - CONTRIBUTING.md
39
+ - LICENSE.md
40
+ - README.md
41
+ - lib/multi_json.rb
42
+ - lib/multi_json/adapter.rb
43
+ - lib/multi_json/adapter_error.rb
44
+ - lib/multi_json/adapter_selector.rb
45
+ - lib/multi_json/adapters/fast_jsonparser.rb
46
+ - lib/multi_json/adapters/gson.rb
47
+ - lib/multi_json/adapters/jr_jackson.rb
48
+ - lib/multi_json/adapters/json_gem.rb
49
+ - lib/multi_json/adapters/oj.rb
50
+ - lib/multi_json/adapters/oj_common.rb
51
+ - lib/multi_json/adapters/yajl.rb
52
+ - lib/multi_json/concurrency.rb
53
+ - lib/multi_json/deprecated.rb
54
+ - lib/multi_json/options.rb
55
+ - lib/multi_json/options_cache.rb
56
+ - lib/multi_json/options_cache/concurrent_store.rb
57
+ - lib/multi_json/parse_error.rb
58
+ - lib/multi_json/version.rb
59
+ homepage: https://github.com/sferik/multi_json
60
+ licenses:
61
+ - MIT
62
+ metadata:
63
+ bug_tracker_uri: https://github.com/sferik/multi_json/issues
64
+ changelog_uri: https://github.com/sferik/multi_json/blob/v1.20.0/CHANGELOG.md
65
+ documentation_uri: https://www.rubydoc.info/gems/multi_json/1.20.0
66
+ rubygems_mfa_required: 'true'
67
+ source_code_uri: https://github.com/sferik/multi_json/tree/v1.20.0
68
+ wiki_uri: https://github.com/sferik/multi_json/wiki
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '3.2'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 4.0.10
84
+ specification_version: 4
85
+ summary: A common interface to multiple JSON libraries.
86
+ test_files: []