nosj 0.4.0-aarch64-linux → 0.5.0-aarch64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd883bdfebc07e316c4baa1706cd9ba6b8036645a8cbd0b8b9b5a888c3a0e3c5
4
- data.tar.gz: f73808429bfdf03043716ceb4b3d01a3536a4b6d2f1c092f15b54dff52ef9c82
3
+ metadata.gz: 8bd202a41866bf66ced093153a4a0ca1166ed3a2dc495eaa8189d0a6e6b58aa7
4
+ data.tar.gz: 8c7c12413a1bff0d9ff9d72d5629700e14a5c922fd1790ebe2ec46879be45d25
5
5
  SHA512:
6
- metadata.gz: 7dab8b234f37fa22a4ab1a5f0bfb906df9ecb6d85ecba50f63e5634d309387ea0ce4302eda8e2280d182ae69d0067ee852d4b23773d1c1c4164c1b6ed708cadf
7
- data.tar.gz: '0909f76cdcd4d49f015ee951d14f785312d91e6cc6ff61c18be8fe5c091f068c546f7a5f2d5a41cbce0db75de9fd4c20ddfbcca249906997f2c7a09fc8380f96'
6
+ metadata.gz: 045b0214d996b133b5c99ec958d5f929f12adf7e134671c9ff70895a4168d9a6a7ce78c261d6d8c5453b38af7363ded4a918b9d5fbb6c50dd8b06b15283de4af
7
+ data.tar.gz: d8717b79f4bddf22206fa248e57521282be0f197210683724adb8f831adf5a26581dd92e9d0192e3973070252f5acdc2968e6f8686ea5c916ebc5387a72602bc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,108 @@
1
+ ## [0.5.0] - 2026-09-25
2
+
3
+ **json 3.0 compatibility.** nosj now matches the behavior of the json
4
+ gem 3.0, and stays compatible with both the json 2.x and 3.0
5
+ interfaces: `NOSJ.parse`, `NOSJ.generate`, and the rest of nosj's own
6
+ API follow json 3.0 semantics, while the `nosj/json` drop-in follows
7
+ whichever json gem your application has installed, 2.x or 3.0, down to
8
+ its calling conventions and the documents it accepts. Upgrading json
9
+ (or not) is your choice; nosj works with either.
10
+
11
+ Behavior changes in nosj's own API, for input that was accepted
12
+ before:
13
+
14
+ - Duplicate object keys raise `NOSJ::ParserError` (positioned at the
15
+ object repeating the key, like json 3) in `parse`, `load_file`,
16
+ `valid?` (returns false), `minify`/`reformat`, and everything that
17
+ materializes values. `allow_duplicate_key: true` restores the old
18
+ behavior: the last value wins (in `minify`, repeated keys pass
19
+ through).
20
+ - Lone UTF-16 surrogates such as `"\udc00"` raise `NOSJ::ParserError`
21
+ everywhere, trailing ones included (they used to decode to raw
22
+ WTF-8 bytes, and `minify` re-escaped them).
23
+ - `generate` raises `NOSJ::GeneratorError` for keys that render alike
24
+ (`{"a" => 1, :a => 2}`, `{1 => 1, "1" => 2}`), with json 3's exact
25
+ message; `allow_duplicate_key: true` emits them as before. Hashes
26
+ whose keys are all of one kind are never checked. The Rails encoder
27
+ is unchanged.
28
+ - `stats` still describes such documents rather than refusing them.
29
+ - Unknown options raise ArgumentError with json 3's message
30
+ (`unknown keyword: foo`) in every entry point, instead of being
31
+ ignored. json options nosj does not implement (`object_class`,
32
+ `array_class`, `decimal_class`, `on_load`, `create_additions`,
33
+ `allow_comments`, `allow_control_characters`,
34
+ `allow_invalid_escape`, `sort_keys`, `as_json`) raise unless falsy;
35
+ `on_load` and the newer ones used to be silently ignored.
36
+ `escape_slash` is gone, as in json 3: use `script_safe`.
37
+ `quirks_mode` is no longer accepted. `stats` takes only the options
38
+ it documents (`max_nesting`, `allow_nan`, `allow_trailing_comma`).
39
+
40
+ The `nosj/json` drop-in follows the installed json gem:
41
+
42
+ - With json 3.0, `JSON.parse` takes keyword options only, `JSON.dump`
43
+ uses json 3's defaults (nesting capped at 100), and whatever json 3
44
+ refuses (`quirks_mode`, `escape_slash`, `create_additions`, unknown
45
+ options, positional option hashes) raises exactly as json 3 raises.
46
+ - With json 2.x, everything behaves as before, including json 2's
47
+ acceptance of duplicate keys, lone surrogates, and comments, and
48
+ its handling of keys that render alike.
49
+ - Whenever the fast path refuses a call, the installed gem runs it
50
+ again and decides: exceptions are now the gem's own (message,
51
+ `json_path`, `invalid_object`) rather than nosj's messages re-raised
52
+ as JSON classes. The second pass happens on failures only, and a
53
+ `generate` run twice this way calls `to_json` again on the objects
54
+ visited before the refusal.
55
+ - Fixed: `JSON.dump` raised NameError (`_dump_default_options`) with
56
+ json older than 2.11, which includes the json bundled with Ruby 3.3
57
+ and 3.4.
58
+ - Fixed: `JSON.generate` (and `pretty_generate`, `dump`) with both
59
+ `ascii_only` and `script_safe` raised ArgumentError; that combination,
60
+ which nosj does not implement, now goes to the gem.
61
+
62
+ ## [0.4.1] - 2026-09-25
63
+
64
+ - Fixed a crash in `NOSJ.generate`: an object whose `to_json` or
65
+ `to_s` shrank the array being generated (for example with
66
+ `Array#clear`) made the generator read freed memory, usually a
67
+ segfault. The array length is now re-read for every element, like
68
+ the json gem, so elements a callback appends are emitted too.
69
+ - Fixed memory corruption in `NOSJ.splice`: a replacement value whose
70
+ `to_json` modified the source string, deduplicated a frozen String
71
+ subclass (`-str`), or removed entries from the edits hash could make
72
+ splice read freed memory, copying unrelated heap bytes into the
73
+ result or crashing. All values are now generated before the source
74
+ is read.
75
+ - Fixed `NOSJ.lazy` and `NOSJ.each_line` reading freed memory when the
76
+ source is a frozen String subclass (such as
77
+ `ActiveSupport::SafeBuffer`) or a frozen string carrying instance
78
+ variables, and it is deduplicated with `-str` while the lazy
79
+ document is alive or between lines: Ruby swaps such a string's
80
+ buffer, and the old one kept being read (wrong values, or a crash).
81
+ - Fixed a memory leak in `NOSJ.generate` and `NOSJ.write_file`: an
82
+ exception raised by user code the generator calls bypassed its
83
+ cleanup and leaked its output buffer (megabytes per call after large
84
+ documents). Affected: a raising `respond_to?` or
85
+ `respond_to_missing?`, a raising `to_s` on an encoding-conversion
86
+ error, a raising autoload of `JSON::Fragment` (strict and Rails
87
+ modes), and a raising `Errno` constructor for a failed write. The
88
+ exception still propagates unchanged.
89
+ - Fixed: after a `NoMemoryError` in the middle of a parse (or
90
+ `minify`/`reformat`), the next call on that thread aborted the whole
91
+ process. Per-thread parser state is now recovered instead.
92
+ - `NOSJ::Lazy` nodes now take part in generational GC: holding many
93
+ nodes no longer slows down every minor GC (200,000 live nodes: 2.9 ms
94
+ per minor GC before, 0.1 ms now), and they are freed immediately
95
+ when collected.
96
+ - Fixed: lazy documents opened with `allow_trailing_comma: true` or
97
+ `allow_nan: true` could not be walked. `size`, `keys`, `each`, and
98
+ any lookup that missed or stepped over a trailing comma or a `NaN`
99
+ raised `NOSJ::ParserError`, even though `value` worked.
100
+ - `NOSJ.at_pointer`, `NOSJ.at_pointers`, and `NOSJ.at_pointer_file`
101
+ now honor `allow_nan` and `allow_trailing_comma` while resolving the
102
+ pointer, not only when materializing the matched value.
103
+ - Updated the nosj crate to 0.2.2.
104
+ - Updated dependencies, Magnus bumped to 0.9.0.
105
+
1
106
  ## [0.4.0] - 2026-09-05
2
107
 
3
108
  - Ractors: on Ruby 4.0+, `NOSJ.parse`, `NOSJ.generate`, and every
data/README.md CHANGED
@@ -65,6 +65,10 @@ through nosj:
65
65
  require "nosj/json"
66
66
  ```
67
67
 
68
+ It works with json 2.x and 3.0 alike and follows whichever your app
69
+ has installed: its calling conventions, its defaults, and the documents
70
+ it accepts.
71
+
68
72
  In a Bundler app (Rails included) that can live entirely in the
69
73
  Gemfile you can do this:
70
74
 
@@ -108,9 +112,11 @@ The `json` gem API, on the `NOSJ` module:
108
112
 
109
113
  ```ruby
110
114
  NOSJ.parse(src, symbolize_names: true) # also: freeze, max_nesting,
111
- # allow_nan, allow_trailing_comma
115
+ # allow_nan, allow_trailing_comma,
116
+ # allow_duplicate_key
112
117
  NOSJ.generate(obj) # indent, space, object_nl, ...,
113
- NOSJ.pretty_generate(obj) # ascii_only, script_safe, strict
118
+ NOSJ.pretty_generate(obj) # ascii_only, script_safe, strict,
119
+ # allow_duplicate_key
114
120
  ```
115
121
 
116
122
  ### Lazy documents
@@ -151,7 +157,8 @@ Example: an early field resolves in ~0.35µs where `JSON.parse(json).dig(...)`
151
157
  costs ~980µs on the same document—three orders of magnitude. A field
152
158
  at the far end of a 570 KB document costs ~71µs, still 13× faster
153
159
  than parse-then-dig. Misses return nil; matched subtrees materialize
154
- with the same options as `parse` (`symbolize_names:`, `freeze:`).
160
+ with the same options as `parse` (`symbolize_names:`, `freeze:`), and
161
+ `allow_nan:`/`allow_trailing_comma:` govern the walk to them too.
155
162
 
156
163
  ### Files API
157
164
 
@@ -188,10 +195,11 @@ NOSJ.reformat_file("big.json") # straight off a memory map
188
195
  ```
189
196
 
190
197
  Output is exactly `generate(parse(json))`—canonical number spellings,
191
- normalized escapes, same formatting options—except duplicate keys pass
192
- through (a reformatter must not silently drop data) and lone-surrogate
193
- strings re-escape instead of raising. Acceptance options apply too:
194
- `minify(src, allow_trailing_comma: true)` normalizes the commas away.
198
+ normalized escapes, same formatting options—and it accepts exactly what
199
+ `parse` does. Acceptance options apply too:
200
+ `minify(src, allow_trailing_comma: true)` normalizes the commas away,
201
+ and under `allow_duplicate_key: true` repeated keys pass through (a
202
+ reformatter must not silently drop data).
195
203
 
196
204
  ### Byte-splicing edits and JSON Patch
197
205
 
@@ -402,19 +410,28 @@ Reproduce with `rake bench` (the parity-gated comparison, after a PGO retrain—
402
410
 
403
411
  ## Switching from the json gem
404
412
 
405
- You mostly don't have to do anything. Some differences:
406
-
407
- - The legacy object-deserialization options (`create_additions`,
408
- `object_class`, `array_class`, `decimal_class`) raise ArgumentError;
409
- the `nosj/json` drop-in falls back to the original gem for them.
410
- - Behaviors the `json` gem itself deprecates (JS comments, raw invalid
411
- UTF-8) follow the strict semantics instead.
413
+ You mostly don't have to do anything. `NOSJ.*` follows json 3.0,
414
+ whichever json your app runs; the `nosj/json` drop-in follows the
415
+ installed one, 2.x or 3.0. Some differences:
416
+
417
+ - json 3.0 semantics: duplicate keys, lone surrogates, JS comments,
418
+ and invalid UTF-8 are errors (`allow_duplicate_key: true` restores
419
+ last-key-wins), keys that render alike raise in `generate`, and
420
+ unknown options raise ArgumentError. With json 2.x installed, the
421
+ drop-in keeps json 2's leniency: when nosj refuses a call, the
422
+ installed gem runs it and has the last word.
423
+ - The json options nosj doesn't implement (`create_additions`,
424
+ `object_class`, `array_class`, `decimal_class`, `on_load`,
425
+ `allow_comments`, `allow_control_characters`,
426
+ `allow_invalid_escape`, `sort_keys`, `as_json`) raise ArgumentError
427
+ unless falsy; the drop-in passes them to the original gem.
412
428
  - Unlike `Array#dig`, negative indices in `NOSJ.dig` return nil (JSON
413
429
  Pointer has no equivalent).
414
430
  - Parse errors raise `NOSJ::ParserError` (`NOSJ::NestingError` past
415
431
  `max_nesting`, like the gem); messages use byte offsets rather than
416
432
  the gem's phrasing, and the exception carries `#line`, `#column`,
417
- and a caret `#snippet`.
433
+ and a caret `#snippet`. Through the drop-in, exceptions are the
434
+ installed gem's own (message, `json_path`, `invalid_object`).
418
435
 
419
436
  Everything else—including the gem's exact float formatting, which is
420
437
  not the shortest-round-trip form most libraries emit—matches
data/lib/nosj/3.3/nosj.so CHANGED
Binary file
data/lib/nosj/3.4/nosj.so CHANGED
Binary file
data/lib/nosj/4.0/nosj.so CHANGED
Binary file
data/lib/nosj/json.rb CHANGED
@@ -12,12 +12,18 @@
12
12
  # Entry points built on JSON.parse (JSON.load, JSON.parse!,
13
13
  # JSON.load_file, JSON.unsafe_load) pick up the fast path automatically
14
14
  # and keep their exact legacy behavior when they need unsupported options
15
- # (JSON.load's create_additions default always takes the fallback).
15
+ # (json 2's JSON.load passes create_additions, so it always takes the
16
+ # fallback).
16
17
  #
17
- # Exceptions from the fast path are re-raised as the JSON classes
18
- # (JSON::ParserError, JSON::GeneratorError, JSON::NestingError), so
19
- # existing rescue clauses keep working. Parse error MESSAGES are
20
- # NOSJ's (byte offsets rather than the gem's phrasing).
18
+ # The drop-in follows whichever json is installed, 2.x or 3.x: its
19
+ # calling conventions (json 3's keyword-only parse, its dump defaults)
20
+ # and its semantics. NOSJ implements json 3's (duplicate keys and lone
21
+ # surrogates are errors), so whenever the fast path refuses a call, the
22
+ # original gem runs it again and has the last word: json 2 accepts what
23
+ # it always accepted, and every exception is the gem's own, message,
24
+ # json_path and invalid_object included. That second pass happens on
25
+ # failures only; a generate run twice this way calls to_json on the
26
+ # objects visited before the refusal twice.
21
27
  #
22
28
  # Not rerouted: obj.to_json (core extensions drive the gem's generator
23
29
  # directly), and objects with a custom to_json inside a rerouted
@@ -30,15 +36,26 @@ module NOSJ
30
36
  # Implementation detail of `require "nosj/json"`.
31
37
  # @private
32
38
  module JSONDropIn
33
- # quirks_mode rides the fast path because NOSJ.parse is always
34
- # quirks-mode (top-level scalars parse) and ignores the key; Rails
35
- # 7.x passes it from ActiveSupport::JSON.decode.
39
+ # json 3 made parse's options keyword-only, fixed dump's defaults and
40
+ # raises for options json 2 ignored or aliased.
41
+ JSON3 = ::JSON::VERSION.to_i >= 3
42
+
36
43
  PARSE_OPTS = %i[symbolize_names freeze max_nesting allow_nan
37
- allow_trailing_comma quirks_mode].freeze
44
+ allow_trailing_comma allow_duplicate_key].freeze
45
+ # json 2 ignores quirks_mode, which Rails 7.x passes from
46
+ # ActiveSupport::JSON.decode, so its fast path takes the key and
47
+ # drops it (NOSJ.parse always parses top-level scalars, and refuses
48
+ # unknown keys). json 3 raises for it, so there it reaches the gem
49
+ # like any other unknown option.
50
+ QUIRKS_MODE = :quirks_mode
51
+ JSON2_PARSE_OPTS = (PARSE_OPTS + [QUIRKS_MODE]).freeze
38
52
  GENERATE_OPTS = %i[indent space space_before object_nl array_nl
39
- max_nesting allow_nan ascii_only script_safe
40
- escape_slash strict depth
41
- buffer_initial_length].freeze
53
+ max_nesting allow_nan ascii_only script_safe strict depth
54
+ buffer_initial_length allow_duplicate_key].freeze
55
+ JSON3_DUMP_DEFAULTS = {allow_nan: true}.freeze
56
+ # json 2.10 and older accept only strict: in dump's options hash,
57
+ # through this private helper; later versions merge any option.
58
+ DUMP_MERGES_OPTIONS = !::JSON.respond_to?(:merge_dump_options, true)
42
59
 
43
60
  module_function
44
61
 
@@ -52,6 +69,13 @@ module NOSJ
52
69
  true
53
70
  end
54
71
 
72
+ # Generate options the fast path handles: supported keys, minus the
73
+ # one combination NOSJ refuses (ascii_only with script_safe, which
74
+ # json supports).
75
+ def generate_supported?(opts)
76
+ supported?(opts, GENERATE_OPTS) && !(opts && opts[:ascii_only] && opts[:script_safe])
77
+ end
78
+
55
79
  def parse(source, opts)
56
80
  # NOSJ.parse is deliberately strict about encodings (json-3.0
57
81
  # semantics), but the drop-in must match the installed gem, which
@@ -61,30 +85,60 @@ module NOSJ
61
85
  # (copy-on-write bytes), and the validity scan is memoized
62
86
  # coderange the parse would compute anyway. Anything else
63
87
  # non-UTF-8 (UTF-16, ...) belongs to gem json, which transcodes.
88
+ input = source
64
89
  if source.is_a?(String)
65
90
  case source.encoding
66
91
  when Encoding::UTF_8, Encoding::US_ASCII
67
92
  # the fast path as-is
68
93
  when Encoding::BINARY
69
94
  utf8 = source.dup.force_encoding(Encoding::UTF_8)
70
- source = utf8 if utf8.valid_encoding?
95
+ input = utf8 if utf8.valid_encoding?
71
96
  else
72
- return ::JSON.nosj_original_parse(source, **(opts || {}))
97
+ return original_parse(source, opts)
73
98
  end
74
99
  end
75
- NOSJ.parse(source, opts)
76
- rescue NOSJ::NestingError => e
77
- raise ::JSON::NestingError, e.message
78
- rescue NOSJ::ParserError => e
79
- raise ::JSON::ParserError, e.message
100
+ NOSJ.parse(input, opts)
101
+ rescue NOSJ::ParserError, NOSJ::NestingError
102
+ original_parse(source, opts)
103
+ end
104
+
105
+ # json 2's options without quirks_mode (see QUIRKS_MODE), allocating
106
+ # nothing for Rails' lone `quirks_mode: true`.
107
+ def without_quirks_mode(opts)
108
+ return opts unless opts&.key?(QUIRKS_MODE)
109
+ if opts.size == 1
110
+ nil
111
+ else
112
+ opts.except(QUIRKS_MODE)
113
+ end
114
+ end
115
+
116
+ # The installed gem's parse. json 3 takes keywords only; json 2's
117
+ # positional options hash receives them just the same.
118
+ def original_parse(source, opts)
119
+ ::JSON.nosj_original_parse(source, **(opts || {}))
80
120
  end
81
121
 
82
122
  def generate(obj, opts, pretty)
83
123
  pretty ? NOSJ.pretty_generate(obj, opts) : NOSJ.generate(obj, opts)
84
- rescue NOSJ::NestingError => e
85
- raise ::JSON::NestingError, e.message
86
- rescue NOSJ::GeneratorError => e
87
- raise ::JSON::GeneratorError, e.message
124
+ rescue NOSJ::GeneratorError, NOSJ::NestingError
125
+ if pretty
126
+ ::JSON.nosj_original_pretty_generate(obj, opts)
127
+ else
128
+ ::JSON.nosj_original_generate(obj, opts)
129
+ end
130
+ end
131
+
132
+ # The options JSON.dump generates with before the caller's own: fixed
133
+ # in json 3; json 2 reads its user-settable dump_default_options,
134
+ # through the internal reader 2.11 added when it deprecated the
135
+ # public one. Which one is decided here, once.
136
+ if JSON3
137
+ def dump_defaults = JSON3_DUMP_DEFAULTS
138
+ elsif ::JSON.respond_to?(:_dump_default_options)
139
+ def dump_defaults = ::JSON._dump_default_options
140
+ else
141
+ def dump_defaults = ::JSON.dump_default_options
88
142
  end
89
143
  end
90
144
  end
@@ -101,16 +155,26 @@ module JSON
101
155
  alias_method :nosj_original_pretty_generate, :pretty_generate
102
156
  alias_method :nosj_original_dump, :dump
103
157
 
104
- def parse(source, opts = nil)
105
- if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::PARSE_OPTS)
106
- NOSJ::JSONDropIn.parse(source, opts)
107
- else
108
- nosj_original_parse(source, opts)
158
+ if NOSJ::JSONDropIn::JSON3
159
+ def parse(source, **opts)
160
+ if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::PARSE_OPTS)
161
+ NOSJ::JSONDropIn.parse(source, opts)
162
+ else
163
+ nosj_original_parse(source, **opts)
164
+ end
165
+ end
166
+ else
167
+ def parse(source, opts = nil)
168
+ if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::JSON2_PARSE_OPTS)
169
+ NOSJ::JSONDropIn.parse(source, NOSJ::JSONDropIn.without_quirks_mode(opts))
170
+ else
171
+ nosj_original_parse(source, opts)
172
+ end
109
173
  end
110
174
  end
111
175
 
112
176
  def generate(obj, opts = nil)
113
- if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::GENERATE_OPTS)
177
+ if NOSJ::JSONDropIn.generate_supported?(opts)
114
178
  NOSJ::JSONDropIn.generate(obj, opts, false)
115
179
  else
116
180
  nosj_original_generate(obj, opts)
@@ -118,7 +182,7 @@ module JSON
118
182
  end
119
183
 
120
184
  def pretty_generate(obj, opts = nil)
121
- if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::GENERATE_OPTS)
185
+ if NOSJ::JSONDropIn.generate_supported?(opts)
122
186
  NOSJ::JSONDropIn.generate(obj, opts, true)
123
187
  else
124
188
  nosj_original_pretty_generate(obj, opts)
@@ -127,17 +191,19 @@ module JSON
127
191
 
128
192
  def dump(obj, an_io = nil, limit = nil, kwargs = nil)
129
193
  # Fast path for the common shapes, dump(obj) and dump(obj, opts
130
- # hash), mirroring gem json: dump defaults merged under the
131
- # user's options, NestingError surfaced as ArgumentError. IO and
132
- # limit arguments take gem json's own dump.
133
- if limit.nil? && kwargs.nil? && (an_io.nil? || an_io.instance_of?(Hash))
134
- opts = _dump_default_options
194
+ # hash): the installed gem's dump defaults merged under the
195
+ # caller's options. IO and limit arguments, and anything the
196
+ # fast path refuses, take gem json's own dump, which also turns
197
+ # json 2's NestingError into its ArgumentError.
198
+ if limit.nil? && kwargs.nil? &&
199
+ (an_io.nil? || NOSJ::JSONDropIn::DUMP_MERGES_OPTIONS && an_io.instance_of?(Hash))
200
+ opts = NOSJ::JSONDropIn.dump_defaults
135
201
  opts = opts.merge(an_io) if an_io
136
- if NOSJ::JSONDropIn.supported?(opts, NOSJ::JSONDropIn::GENERATE_OPTS)
202
+ if NOSJ::JSONDropIn.generate_supported?(opts)
137
203
  begin
138
- return NOSJ::JSONDropIn.generate(obj, opts, false)
139
- rescue ::JSON::NestingError
140
- raise ArgumentError, "exceed depth limit"
204
+ return NOSJ.generate(obj, opts)
205
+ rescue NOSJ::GeneratorError, NOSJ::NestingError
206
+ # the gem's own dump below runs it again and decides
141
207
  end
142
208
  end
143
209
  end
data/lib/nosj/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module NOSJ
4
4
  # The gem version.
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
data/lib/nosj.rb CHANGED
@@ -86,10 +86,13 @@ module NOSJ
86
86
  # Parses a JSON document, JSON.parse-compatible: same values, same
87
87
  # option names, same behavior, byte-for-byte.
88
88
  #
89
- # The +json+ gem's legacy object-deserialization options
90
- # (+object_class+, +array_class+, +decimal_class+,
91
- # +create_additions+) are deliberately unsupported and raise
92
- # ArgumentError.
89
+ # Options follow json 3: an unknown key raises ArgumentError
90
+ # (<code>unknown keyword: foo</code>). The json options nosj does not
91
+ # implement (+object_class+, +array_class+, +decimal_class+,
92
+ # +on_load+, +create_additions+, +allow_comments+,
93
+ # +allow_control_characters+, +allow_invalid_escape+) raise
94
+ # ArgumentError unless falsy, since their falsy default is nosj's
95
+ # behavior.
93
96
  #
94
97
  # @example
95
98
  # NOSJ.parse('{"a":[1,true]}') #=> {"a" => [1, true]}
@@ -98,12 +101,14 @@ module NOSJ
98
101
  # @param source [String] the JSON document (UTF-8 or US-ASCII)
99
102
  # @param opts [Hash, nil] +symbolize_names+, +freeze+, +max_nesting+
100
103
  # (Integer or +false+ for unlimited), +allow_nan+,
101
- # +allow_trailing_comma+
104
+ # +allow_trailing_comma+, +allow_duplicate_key+ (json 3 semantics:
105
+ # a repeated key raises unless this is true, then the last one wins)
102
106
  # @return [Object] the parsed value tree
103
- # @raise [ParserError] when the document is malformed or not UTF-8;
104
- # carries the failure position ({ParserError#line} and friends)
107
+ # @raise [ParserError] when the document is malformed, repeats a key,
108
+ # holds a lone surrogate (+"\udc00"+), or is not UTF-8; carries the
109
+ # failure position ({ParserError#line} and friends)
105
110
  # @raise [NestingError] when nesting exceeds +max_nesting+
106
- # @raise [ArgumentError] for unsupported options
111
+ # @raise [ArgumentError] for unknown or unsupported options
107
112
  def self.parse(source, opts = nil)
108
113
  parse_native(source, opts)
109
114
  end
@@ -119,12 +124,18 @@ module NOSJ
119
124
  # @param obj [Object] the value tree to serialize
120
125
  # @param opts [Hash, nil] +indent+, +space+, +space_before+,
121
126
  # +object_nl+, +array_nl+, +max_nesting+ (Integer or +false+),
122
- # +allow_nan+, +ascii_only+, +script_safe+ (alias +escape_slash+),
123
- # +strict+, +depth+, +buffer_initial_length+
127
+ # +allow_nan+, +ascii_only+, +script_safe+, +strict+, +depth+,
128
+ # +buffer_initial_length+, +allow_duplicate_key+ (json 3
129
+ # semantics: keys that render alike, like <code>"a"</code> and
130
+ # <code>:a</code>, raise unless true). As in json 3, an unknown
131
+ # key raises (+escape_slash+ is gone: use +script_safe+), and the
132
+ # unimplemented +sort_keys+ and +as_json+ raise unless falsy.
124
133
  # @return [String] the JSON document
125
134
  # @raise [GeneratorError] for non-finite floats without +allow_nan+,
126
- # unsupported objects under +strict+, or broken string encodings
135
+ # unsupported objects under +strict+, keys that render alike, or
136
+ # broken string encodings
127
137
  # @raise [NestingError] when nesting exceeds +max_nesting+
138
+ # @raise [ArgumentError] for unknown or unsupported options
128
139
 
129
140
  # Generates human-readable JSON, JSON.pretty_generate-compatible
130
141
  # (two-space indent, newlines between elements). Options override the
@@ -156,7 +167,7 @@ module NOSJ
156
167
  # @param source [String] the JSON document
157
168
  # @param opts [Hash, nil] same options as {.parse}
158
169
  # @return [Boolean]
159
- # @raise [ArgumentError] for unsupported options
170
+ # @raise [ArgumentError] for unknown or unsupported options
160
171
  def self.valid?(source, opts = nil)
161
172
  valid_native(source, opts)
162
173
  end
@@ -205,7 +216,8 @@ module NOSJ
205
216
 
206
217
  # Partial parsing by JSON Pointer (with the standard +~0+/+~1+
207
218
  # escapes). The matched subtree materializes under the same options
208
- # as {.parse}.
219
+ # as {.parse}; +allow_nan+ and +allow_trailing_comma+ also govern the
220
+ # walk to it.
209
221
  #
210
222
  # @example
211
223
  # NOSJ.at_pointer(json, "/users/3/name") #=> "grace" or nil
@@ -322,18 +334,19 @@ module NOSJ
322
334
  # Minifies a document without building any Ruby values: the parser's
323
335
  # events pipe straight into the emission kernels, SIMD in and SIMD
324
336
  # out. Output is exactly what <code>generate(parse(json))</code>
325
- # would produce, except duplicate object keys pass through instead of
326
- # being collapsed (a reformatter must not silently drop data).
327
- # Numbers come out in the canonical spelling (+1.50+ becomes +1.5+)
328
- # and string escapes are normalized.
337
+ # would produce, and it accepts exactly what {.parse} accepts; under
338
+ # +allow_duplicate_key+, repeated keys pass through instead of being
339
+ # collapsed (a reformatter must not silently drop data). Numbers come
340
+ # out in the canonical spelling (+1.50+ becomes +1.5+) and string
341
+ # escapes are normalized.
329
342
  #
330
343
  # @example
331
344
  # NOSJ.minify(%({ "a": [1, 2],\n "b": "x" })) #=> '{"a":[1,2],"b":"x"}'
332
345
  #
333
346
  # @param json [String] the document (UTF-8 or US-ASCII)
334
347
  # @param opts [Hash, nil] acceptance options (+allow_nan+,
335
- # +allow_trailing_comma+, +max_nesting+); trailing commas are
336
- # normalized away when accepted
348
+ # +allow_trailing_comma+, +allow_duplicate_key+, +max_nesting+);
349
+ # trailing commas are normalized away when accepted
337
350
  # @return [String] the minified document
338
351
  # @raise [ParserError] when the document is malformed
339
352
  # @raise [NestingError] past +max_nesting+
@@ -357,8 +370,7 @@ module NOSJ
357
370
  # @return [String] the reformatted document
358
371
  # @raise [ParserError] when the document is malformed
359
372
  # @raise [NestingError] past +max_nesting+
360
- # @raise [GeneratorError] when +ascii_only+ meets a lone-surrogate
361
- # string it cannot represent
373
+ # @raise [GeneratorError] for a non-finite float without +allow_nan+
362
374
  def self.reformat(json, opts = nil)
363
375
  if opts&.key?(:pretty)
364
376
  pretty = opts[:pretty]
@@ -597,7 +609,9 @@ module NOSJ
597
609
  #
598
610
  # @param source [String] the JSON document (UTF-8 or US-ASCII)
599
611
  # @param opts [Hash, nil] +max_nesting+, +allow_nan+,
600
- # +allow_trailing_comma+ (acceptance options only)
612
+ # +allow_trailing_comma+ (acceptance options only). Being a
613
+ # diagnostic, stats also describes documents {.parse} would refuse
614
+ # for a repeated key or a lone surrogate.
601
615
  # @return [Hash] the statistics described above
602
616
  # @raise [ParserError] when the document is malformed or not UTF-8
603
617
  def self.stats(source, opts = nil)
data/sig/nosj.rbs CHANGED
@@ -14,9 +14,10 @@ module NOSJ
14
14
  # Options arrive as a positional Hash (the JSON gem's own calling
15
15
  # convention; an explicit **kwargs would allocate per call), nil when
16
16
  # omitted. Parse: symbolize_names, freeze, max_nesting, allow_nan,
17
- # allow_trailing_comma. Generate: indent, space, space_before,
18
- # object_nl, array_nl, max_nesting, allow_nan, ascii_only,
19
- # script_safe/escape_slash, strict, depth, buffer_initial_length.
17
+ # allow_trailing_comma, allow_duplicate_key. Generate: indent, space,
18
+ # space_before, object_nl, array_nl, max_nesting, allow_nan,
19
+ # ascii_only, script_safe, strict, depth, buffer_initial_length,
20
+ # allow_duplicate_key. Unknown keys raise ArgumentError (json 3).
20
21
  type opts = Hash[Symbol, untyped]?
21
22
 
22
23
  # One NOSJ.dig path element (negative Integer indices resolve to nil).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nosj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Yaroslav Markin
@@ -66,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  - !ruby/object:Gem::Version
67
67
  version: 3.3.11
68
68
  requirements: []
69
- rubygems_version: 4.0.16
69
+ rubygems_version: 4.0.20
70
70
  specification_version: 4
71
71
  summary: An extremely fast JSON parser and generator for Ruby, written in Rust and
72
72
  SIMD-accelerated on every platform.