nosj 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 87c27bdaeada8a26a98f378d168befec0d1b50b497663bf09ea58b133358b98f
4
+ data.tar.gz: 0756f4a05107fabcffe58c889c7a40487c7f060d944d9b5bce161430d4e0c70f
5
+ SHA512:
6
+ metadata.gz: ff14cc073152752000f9ad0102222c69e3a63ec561b8411886164c5e1c85fb30044d31e6286074169e1cc531a533b8d59d5b103473b4ae413c32c4e0d565b7f9
7
+ data.tar.gz: 673019e943af6446a6bb63dacaaaee002f11c07e450154abd5b0842deffc1f798e924ec3531366d2717a71391bf630d98b83b1e2320e90aa3790c83cb2cd93dd
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ ## [0.1.0] - 2026-07-15
2
+
3
+ Initial release.
4
+
5
+ - `NOSJ.parse`, `NOSJ.generate`, and `NOSJ.pretty_generate`: `json`-gem-compatible parsing and generation—same output bytes, same option names, same error classes and messages—built on the first-party SIMD [nosj](https://crates.io/crates/nosj) crate (NEON on Apple Silicon; SSE2/AVX2 on x86-64, selected at runtime). Faster than the `json` gem and the third-party parsers (Oj, RapidJSON, FastJsonparser, Yajl) across the benchmark corpus, in both directions.
6
+ - Partial parsing: `NOSJ.dig` and `NOSJ.at_pointer` resolve a JSON Pointer and materialize only the matched subtree; `NOSJ.dig_many` and `NOSJ.at_pointers` resolve whole batches of paths in a single pass over the document.
7
+ - `NOSJ.valid?`: full-strictness validation that allocates no Ruby objects.
8
+ - Drop-in acceleration: `require "nosj/json"` reroutes `JSON.parse`, `JSON.generate`, `JSON.pretty_generate`, and `JSON.dump` through nosj, falling back to the original implementation for unsupported options; `require "nosj/multi_json"` adds a MultiJson adapter.
9
+ - Precompiled platform gems, each built natively with profile-guided optimization: Linux x86-64 and arm64 (glibc and musl), macOS (Apple Silicon), Windows (x64), for Ruby 3.3 through 4.0. Other platforms compile the source gem.
10
+ - RBS signatures and full YARD documentation.
data/Cargo.lock ADDED
@@ -0,0 +1,375 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "ahash"
7
+ version = "0.8.12"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
10
+ dependencies = [
11
+ "cfg-if",
12
+ "getrandom",
13
+ "once_cell",
14
+ "version_check",
15
+ "zerocopy",
16
+ ]
17
+
18
+ [[package]]
19
+ name = "aho-corasick"
20
+ version = "1.1.4"
21
+ source = "registry+https://github.com/rust-lang/crates.io-index"
22
+ checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
23
+ dependencies = [
24
+ "memchr",
25
+ ]
26
+
27
+ [[package]]
28
+ name = "bindgen"
29
+ version = "0.72.1"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
32
+ dependencies = [
33
+ "bitflags",
34
+ "cexpr",
35
+ "clang-sys",
36
+ "itertools",
37
+ "proc-macro2",
38
+ "quote",
39
+ "regex",
40
+ "rustc-hash",
41
+ "shlex",
42
+ "syn",
43
+ ]
44
+
45
+ [[package]]
46
+ name = "bitflags"
47
+ version = "2.13.0"
48
+ source = "registry+https://github.com/rust-lang/crates.io-index"
49
+ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
50
+
51
+ [[package]]
52
+ name = "cexpr"
53
+ version = "0.6.0"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
56
+ dependencies = [
57
+ "nom",
58
+ ]
59
+
60
+ [[package]]
61
+ name = "cfg-if"
62
+ version = "1.0.4"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
65
+
66
+ [[package]]
67
+ name = "clang-sys"
68
+ version = "1.8.1"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
71
+ dependencies = [
72
+ "glob",
73
+ "libc",
74
+ "libloading",
75
+ ]
76
+
77
+ [[package]]
78
+ name = "either"
79
+ version = "1.16.0"
80
+ source = "registry+https://github.com/rust-lang/crates.io-index"
81
+ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
82
+
83
+ [[package]]
84
+ name = "fast-float2"
85
+ version = "0.2.3"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
88
+
89
+ [[package]]
90
+ name = "getrandom"
91
+ version = "0.3.4"
92
+ source = "registry+https://github.com/rust-lang/crates.io-index"
93
+ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
94
+ dependencies = [
95
+ "cfg-if",
96
+ "libc",
97
+ "r-efi",
98
+ "wasip2",
99
+ ]
100
+
101
+ [[package]]
102
+ name = "glob"
103
+ version = "0.3.3"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
106
+
107
+ [[package]]
108
+ name = "itertools"
109
+ version = "0.13.0"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
112
+ dependencies = [
113
+ "either",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "lazy_static"
118
+ version = "1.5.0"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
121
+
122
+ [[package]]
123
+ name = "libc"
124
+ version = "0.2.186"
125
+ source = "registry+https://github.com/rust-lang/crates.io-index"
126
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
127
+
128
+ [[package]]
129
+ name = "libloading"
130
+ version = "0.8.9"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
133
+ dependencies = [
134
+ "cfg-if",
135
+ "windows-link",
136
+ ]
137
+
138
+ [[package]]
139
+ name = "magnus"
140
+ version = "0.9.0"
141
+ source = "git+https://github.com/matsadler/magnus#4e46772050e47cd6cd988fa935263cc5c583e388"
142
+ dependencies = [
143
+ "magnus-macros",
144
+ "rb-sys",
145
+ "rb-sys-env",
146
+ "seq-macro",
147
+ ]
148
+
149
+ [[package]]
150
+ name = "magnus-macros"
151
+ version = "0.9.0"
152
+ source = "git+https://github.com/matsadler/magnus#4e46772050e47cd6cd988fa935263cc5c583e388"
153
+ dependencies = [
154
+ "proc-macro2",
155
+ "quote",
156
+ "syn",
157
+ ]
158
+
159
+ [[package]]
160
+ name = "memchr"
161
+ version = "2.8.3"
162
+ source = "registry+https://github.com/rust-lang/crates.io-index"
163
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
164
+
165
+ [[package]]
166
+ name = "minimal-lexical"
167
+ version = "0.2.1"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
170
+
171
+ [[package]]
172
+ name = "nom"
173
+ version = "7.1.3"
174
+ source = "registry+https://github.com/rust-lang/crates.io-index"
175
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
176
+ dependencies = [
177
+ "memchr",
178
+ "minimal-lexical",
179
+ ]
180
+
181
+ [[package]]
182
+ name = "nosj"
183
+ version = "0.1.1"
184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
185
+ checksum = "c6ecf0db23b7e9944ecee15ab3dd01b0bde0ab2a195442f363847739f667ac8d"
186
+ dependencies = [
187
+ "fast-float2",
188
+ ]
189
+
190
+ [[package]]
191
+ name = "nosj_native"
192
+ version = "0.1.0"
193
+ dependencies = [
194
+ "ahash",
195
+ "magnus",
196
+ "nosj",
197
+ "rb-sys",
198
+ ]
199
+
200
+ [[package]]
201
+ name = "once_cell"
202
+ version = "1.21.4"
203
+ source = "registry+https://github.com/rust-lang/crates.io-index"
204
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
205
+
206
+ [[package]]
207
+ name = "proc-macro2"
208
+ version = "1.0.106"
209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
210
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
211
+ dependencies = [
212
+ "unicode-ident",
213
+ ]
214
+
215
+ [[package]]
216
+ name = "quote"
217
+ version = "1.0.46"
218
+ source = "registry+https://github.com/rust-lang/crates.io-index"
219
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
220
+ dependencies = [
221
+ "proc-macro2",
222
+ ]
223
+
224
+ [[package]]
225
+ name = "r-efi"
226
+ version = "5.3.0"
227
+ source = "registry+https://github.com/rust-lang/crates.io-index"
228
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
229
+
230
+ [[package]]
231
+ name = "rb-sys"
232
+ version = "0.9.128"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
235
+ dependencies = [
236
+ "rb-sys-build",
237
+ ]
238
+
239
+ [[package]]
240
+ name = "rb-sys-build"
241
+ version = "0.9.128"
242
+ source = "registry+https://github.com/rust-lang/crates.io-index"
243
+ checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
244
+ dependencies = [
245
+ "bindgen",
246
+ "lazy_static",
247
+ "proc-macro2",
248
+ "quote",
249
+ "regex",
250
+ "shell-words",
251
+ "syn",
252
+ ]
253
+
254
+ [[package]]
255
+ name = "rb-sys-env"
256
+ version = "0.2.3"
257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
258
+ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
259
+
260
+ [[package]]
261
+ name = "regex"
262
+ version = "1.13.0"
263
+ source = "registry+https://github.com/rust-lang/crates.io-index"
264
+ checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
265
+ dependencies = [
266
+ "aho-corasick",
267
+ "memchr",
268
+ "regex-automata",
269
+ "regex-syntax",
270
+ ]
271
+
272
+ [[package]]
273
+ name = "regex-automata"
274
+ version = "0.4.15"
275
+ source = "registry+https://github.com/rust-lang/crates.io-index"
276
+ checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
277
+ dependencies = [
278
+ "aho-corasick",
279
+ "memchr",
280
+ "regex-syntax",
281
+ ]
282
+
283
+ [[package]]
284
+ name = "regex-syntax"
285
+ version = "0.8.11"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
288
+
289
+ [[package]]
290
+ name = "rustc-hash"
291
+ version = "2.1.3"
292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
293
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
294
+
295
+ [[package]]
296
+ name = "seq-macro"
297
+ version = "0.3.6"
298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
299
+ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
300
+
301
+ [[package]]
302
+ name = "shell-words"
303
+ version = "1.1.1"
304
+ source = "registry+https://github.com/rust-lang/crates.io-index"
305
+ checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
306
+
307
+ [[package]]
308
+ name = "shlex"
309
+ version = "1.3.0"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
312
+
313
+ [[package]]
314
+ name = "syn"
315
+ version = "2.0.119"
316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
317
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
318
+ dependencies = [
319
+ "proc-macro2",
320
+ "quote",
321
+ "unicode-ident",
322
+ ]
323
+
324
+ [[package]]
325
+ name = "unicode-ident"
326
+ version = "1.0.24"
327
+ source = "registry+https://github.com/rust-lang/crates.io-index"
328
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
329
+
330
+ [[package]]
331
+ name = "version_check"
332
+ version = "0.9.5"
333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
334
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
335
+
336
+ [[package]]
337
+ name = "wasip2"
338
+ version = "1.0.4+wasi-0.2.12"
339
+ source = "registry+https://github.com/rust-lang/crates.io-index"
340
+ checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
341
+ dependencies = [
342
+ "wit-bindgen",
343
+ ]
344
+
345
+ [[package]]
346
+ name = "windows-link"
347
+ version = "0.2.1"
348
+ source = "registry+https://github.com/rust-lang/crates.io-index"
349
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
350
+
351
+ [[package]]
352
+ name = "wit-bindgen"
353
+ version = "0.57.1"
354
+ source = "registry+https://github.com/rust-lang/crates.io-index"
355
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
356
+
357
+ [[package]]
358
+ name = "zerocopy"
359
+ version = "0.8.54"
360
+ source = "registry+https://github.com/rust-lang/crates.io-index"
361
+ checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
362
+ dependencies = [
363
+ "zerocopy-derive",
364
+ ]
365
+
366
+ [[package]]
367
+ name = "zerocopy-derive"
368
+ version = "0.8.54"
369
+ source = "registry+https://github.com/rust-lang/crates.io-index"
370
+ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
371
+ dependencies = [
372
+ "proc-macro2",
373
+ "quote",
374
+ "syn",
375
+ ]
data/Cargo.toml ADDED
@@ -0,0 +1,27 @@
1
+ # This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is
2
+ # a Rust project. Your extensions dependencies should be added to the Cargo.toml
3
+ # in the ext/ directory.
4
+
5
+ [workspace]
6
+ members = ["./ext/nosj"]
7
+ resolver = "2"
8
+
9
+ [profile.release]
10
+ opt-level = 3
11
+ lto = "fat"
12
+ codegen-units = 1
13
+ # Ship lean artifacts: leftover debug sections (e.g. from the
14
+ # precompiled std) are stripped. The symbol table stays, so crash
15
+ # backtraces keep function names.
16
+ strip = "debuginfo"
17
+ # No landing pads: a Ruby extension must never unwind across the C
18
+ # boundary anyway, and magnus's per-pair hash-iteration panic guard
19
+ # (std::panic::catch_unwind) compiles to a plain call under abort.
20
+ # Measured ~4ns per object pair on Zen 4 with unwinding enabled.
21
+ panic = "abort"
22
+
23
+ [profile.profiling]
24
+ inherits = "release"
25
+ debug = true
26
+ opt-level = 3
27
+ lto = "fat"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yaroslav Markin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ nosj (Ruby gem)
2
+
3
+ Copyright (c) 2026 Yaroslav Markin. Released under the MIT License
4
+ (see LICENSE.txt).
5
+
6
+ Every distribution of this gem embeds the nosj Rust crate
7
+ (https://github.com/yaroslav/nosj), compiled into the native
8
+ extension: precompiled platform gems ship the compiled binary, and the
9
+ source gem compiles the crate at install time. The crate is licensed
10
+ MIT AND BSL-1.0 AND Apache-2.0; its NOTICE file itemizes the derived
11
+ components (the fpconv/Grisu2 float formatter under BSL-1.0; the
12
+ fast_float decimal conversion and simdjson structural-indexing
13
+ techniques under Apache-2.0) and reproduces each component license.
data/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # gem nosj
2
+
3
+ **gem nosj** is an **very fast JSON parser and generator for Ruby**, written in Rust on the first-party [nosj](https://github.com/yaroslav/nosj) crate and **SIMD-accelerated** on every platform (NEON on Apple Silicon, SSE2/AVX2 on x86-64).
4
+
5
+ > gem nosj is the powerful evil twin of the json gem.
6
+
7
+ [![GitHub Release](https://img.shields.io/github/v/release/yaroslav/nosj-ruby)](https://github.com/yaroslav/nosj-ruby/releases)
8
+ [![Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/gems/nosj)
9
+
10
+ - It is **faster** than gem json and every
11
+ third-party parser, including Oj, RapidJSON, FastJsonparser, Yajl. 1.0–1.8× faster than the bundled json gem, 1.3–11× faster than Oj, and up to 17×
12
+ faster than Yajl—[see Benchmarks](#benchmarks).
13
+ - It comes **precompiled** (platform gems built with per-platform optimizations,
14
+ nothing to compile on install).
15
+ - It has a **partial parsing mode**: JSON Pointer lookups that pull single values out of big documents in microseconds, skipping everything else.
16
+ - Same API and option names as gem json.
17
+
18
+ **And there's more**: validate documents without building a single Ruby object, resolve whole batches of paths in one pass, and accelerate an entire application with a one-line drop-in.
19
+
20
+ - [Requirements](#requirements)
21
+ - [Getting started](#getting-started)
22
+ - [What's in the box](#whats-in-the-box)
23
+ - [Benchmarks](#benchmarks)
24
+ - [Switching from the json gem](#switching-from-the-json-gem)
25
+ - [How it works](#how-it-works)
26
+ - [Development](#development)
27
+ - [License](#license)
28
+
29
+ ## Requirements
30
+
31
+ - **Ruby 3.3 or newer** (CRuby; tested on 3.3, 3.4, and 4.0).
32
+ - **Linux** (x86-64 and arm64, glibc and musl), **macOS** (Apple
33
+ Silicon), or **Windows** (x64): these platforms install precompiled,
34
+ per-platform-optimized gems with nothing to build.
35
+
36
+ ## Getting started
37
+
38
+ ```bash
39
+ bundle add nosj
40
+ ```
41
+
42
+ ```ruby
43
+ require "nosj"
44
+
45
+ NOSJ.parse('{"a":[1,true]}') #=> {"a" => [1, true]}
46
+ NOSJ.generate({"a" => [1, true]}) #=> '{"a":[1,true]}'
47
+ ```
48
+
49
+ That's it—if you know the `json` gem, you already know `nosj`.
50
+
51
+ Want the speedup without touching your code? One line reroutes
52
+ `JSON.parse`, `JSON.generate`, `JSON.pretty_generate`, and `JSON.dump`
53
+ through nosj:
54
+
55
+ ```ruby
56
+ require "nosj/json"
57
+ ```
58
+
59
+ In a Bundler app (Rails included) that can live entirely in the
60
+ Gemfile you can do this:
61
+
62
+ ```ruby
63
+ gem "nosj", require: "nosj/json"
64
+ ```
65
+
66
+ Options nosj supports take the fast path; anything exotic
67
+ (`create_additions`, `object_class`, `JSON::State`, procs, IO
68
+ arguments) falls back to the original implementation, so `JSON.load`,
69
+ `JSON.parse!`, and `JSON.load_file` keep their exact behavior.
70
+ Exceptions re-raise as the JSON classes, so your rescue clauses keep
71
+ working. Measured through the patch: parse 1.11×, generate 1.05× over
72
+ the original gem. (A MultiJson adapter ships too:
73
+ `require "nosj/multi_json"`, then `MultiJson.use NOSJ::MultiJsonAdapter`.)
74
+
75
+ ## What's in the box
76
+
77
+ **The `json` gem API**, on the `NOSJ` module:
78
+
79
+ ```ruby
80
+ NOSJ.parse(src, symbolize_names: true) # also: freeze, max_nesting,
81
+ # allow_nan, allow_trailing_comma
82
+ NOSJ.generate(obj) # indent, space, object_nl, ...,
83
+ NOSJ.pretty_generate(obj) # ascii_only, script_safe, strict
84
+ ```
85
+
86
+ **Validation without parsing.** `NOSJ.valid?` runs the full
87
+ parser—tokenizers, string decode, number validation—into a null sink
88
+ and allocates no Ruby objects at all. It is 2-4× faster than
89
+ `NOSJ.parse`, which already leads every parser above:
90
+
91
+ ```ruby
92
+ NOSJ.valid?('{"a":1}') #=> true
93
+ NOSJ.valid?('{"a":}') #=> false
94
+ NOSJ.valid?(src, max_nesting: false) # same options as parse
95
+ ```
96
+
97
+ **Partial parsing.** Pull values out of a document without
98
+ materializing the rest—skipped content is stepped over at SIMD block
99
+ speed, so a lookup costs what it skips, not what the document weighs:
100
+
101
+ ```ruby
102
+ NOSJ.dig(json, "users", 3, "name") # Hash#dig-shaped
103
+ NOSJ.at_pointer(json, "/users/3/name") # JSON Pointer
104
+
105
+ # Many lookups in one pass. A batch costs about as much as its
106
+ # single deepest member:
107
+ NOSJ.at_pointers(json, ["/users/3/name", "/meta/count"])
108
+ NOSJ.dig_many(json, [["users", 3, "name"], ["meta", "count"]])
109
+ ```
110
+
111
+ Example: an early field resolves in ~0.35µs where `JSON.parse(json).dig(...)`
112
+ costs ~980µs on the same document—three orders of magnitude. A field
113
+ at the far end of a 570 KB document costs ~71µs, still 13× faster
114
+ than parse-then-dig. Misses return nil; matched subtrees materialize
115
+ with the same options as `parse` (`symbolize_names:`, `freeze:`).
116
+
117
+ ## Benchmarks
118
+
119
+ Every installed JSON gem, benchmark-ips: AWS EC2 c7a.2xlarge (AMD EPYC 9R14, Zen 4), Ruby 4.0.6 + YJIT, json 2.21.1, Oj 3.17.4, RapidJSON 0.4.0, FastJsonparser 0.6.0, Yajl 1.4.3, PGO build, 2026-07-16. `×N` = times slower than nosj.
120
+
121
+ Parse:
122
+
123
+ | file | nosj (i/s) | json | Oj | FastJsonparser | RapidJSON | Yajl |
124
+ |---|---:|---:|---:|---:|---:|---:|
125
+ | activitypub | **12.4k** | ×1.18 | ×1.53 | ×1.89 | ×1.99 | ×4.57 |
126
+ | canada | **248** | ×1.10 | ×8.28 | ×1.45 | ×1.51 | ×4.96 |
127
+ | citm_catalog | **504** | ×1.03 | ×1.97 | ×2.07 | ×1.83 | ×4.93 |
128
+ | gsoc-2018 | **397** | ×1.30 | ×1.47 | ×1.81 | ×1.80 | ×4.78 |
129
+ | homebrew-formula | **15.2** | ×1.13 | ×1.54 | ×2.42 | ×2.02 | ×4.30 |
130
+ | homebrew-llvm | **49.3k** | ×1.60 | ×1.48 | ×1.50 | ×1.85 | ×5.11 |
131
+ | mesh | **1.1k** | ×1.30 | ×3.93 | ×1.82 | ×1.95 | ×6.96 |
132
+ | numbers | **5.9k** | ×1.19 | ×4.64 | ×1.43 | ×1.75 | ×7.04 |
133
+ | ohai | **14.4k** | ×1.47 | ×1.61 | ×2.11 | ×1.97 | ×4.71 |
134
+ | simple | **977k** | ×1.31 | ×1.77 | ×2.08 | ×1.63 | ×5.15 |
135
+ | small_mixed | **3.2M** | ×1.48 | ×2.85 | ×2.44 | ×1.82 | ×6.75 |
136
+ | tolstoy | **8.9k** | ×1.79 | ×1.96 | ×2.29 | ×2.10 | ×17.31 |
137
+ | twitter | **1.1k** | ×1.09 | ×1.83 | ×2.25 | ×2.61 | ×5.40 |
138
+
139
+ Generate:
140
+
141
+ | file | nosj (i/s) | json | Oj | RapidJSON | Yajl |
142
+ |---|---:|---:|---:|---:|---:|
143
+ | activitypub | **34.8k** | ×1.20 | ×1.78 | ×2.41 | ×5.84 |
144
+ | canada | **150** | ×0.98\* | ×10.98 | ×11.20 | ×10.87 |
145
+ | citm_catalog | **1.3k** | ×1.04 | ×1.44 | ×1.55 | ×2.85 |
146
+ | gsoc-2018 | **1.1k** | ×1.28 | ×2.64 | ×3.64 | ×11.00 |
147
+ | homebrew-formula | **20.9** | ×1.07 | ×1.25 | ×1.62 | ×3.03 |
148
+ | homebrew-llvm | **71.5k** | ×1.23 | ×2.22 | ×3.04 | ×5.80 |
149
+ | mesh | **613** | ×1.08 | ×9.24 | ×9.40 | ×9.26 |
150
+ | numbers | **2.1k** | ×1.03 | ×10.63 | ×11.00 | ×10.69 |
151
+ | ohai | **39.4k** | ×1.07 | ×1.29 | ×1.50 | ×3.37 |
152
+ | simple | **2.2M** | ×1.03 | ×1.58 | ×1.62 | ×4.36 |
153
+ | small_mixed | **5.6M** | ×1.09 | ×2.26 | ×1.83 | ×7.16 |
154
+ | tolstoy | **8.3k** | ×1.30 | ×4.15 | ×6.50 | ×14.54 |
155
+ | twitter | **2.9k** | ×1.09 | ×1.46 | ×2.01 | ×4.05 |
156
+
157
+ \* canada-generate is a statistical tie with the json gem (within
158
+ measurement error).
159
+
160
+ Reproduce with `rake bench` (the parity-gated comparison, after a PGO retrain—the shipping configuration) or `rake bench:ips` (the multi-gem shoot-out).
161
+
162
+ ## Switching from the json gem
163
+
164
+ You mostly don't have to do anything. Some differences:
165
+
166
+ - The legacy object-deserialization options (`create_additions`,
167
+ `object_class`, `array_class`, `decimal_class`) raise ArgumentError;
168
+ the `nosj/json` drop-in falls back to the original gem for them.
169
+ - Behaviors the `json` gem itself deprecates (JS comments, raw invalid
170
+ UTF-8) follow the strict semantics instead.
171
+ - Unlike `Array#dig`, negative indices in `NOSJ.dig` return nil (JSON
172
+ Pointer has no equivalent).
173
+ - Parse error *messages* use byte offsets rather than the gem's
174
+ phrasing (classes match).
175
+
176
+ Everything else—including the gem's exact float formatting, which is
177
+ not the shortest-round-trip form most libraries emit—matches
178
+ byte-for-byte and is verified continuously against the full corpus.
179
+
180
+ ## How it works
181
+
182
+ Most fast parsers build their own tree first and convert it into Ruby
183
+ objects second, paying for every string and container twice. nosj is
184
+ built on the [nosj](https://github.com/yaroslav/nosj) Rust crate, an
185
+ event parser with no tree of its own:
186
+
187
+ - **No intermediate tree.** The crate parses with NEON/SSE2/AVX2 SIMD
188
+ kernels and emits *events*; the extension builds interned hash keys,
189
+ strings, and containers directly on Ruby's heap during the parse,
190
+ with GC-safe value stacks and epoch-evicted key caches. Generation
191
+ walks Ruby objects once, streaming through fused scan-and-store
192
+ escape kernels.
193
+ - **Byte-exact floats.** Output reproduces the json gem's fpconv
194
+ (Grisu2) float format digit for digit—round-tripping is verified,
195
+ not assumed.
196
+ - **PGO everywhere.** Local builds, CI, and every precompiled platform
197
+ gem train on the benchmark corpus before the shipping compile; the
198
+ precompiled binaries use portable codegen with SIMD tiers detected at
199
+ runtime.
200
+
201
+ ## Development
202
+
203
+ ```bash
204
+ bundle exec rake compile # build the extension (applies a PGO profile if present)
205
+ bundle exec rake spec # the gem-parity suite
206
+ bundle exec rake bench # PGO retrain + the parity-gated sweep vs the json gem
207
+ bundle exec rake bench:fast # the sweep without retraining
208
+ bundle exec rake "bench:ips[twitter]" # multi-gem shoot-out (benchmark-ips); no args = full corpus
209
+ ```
210
+
211
+ ## License
212
+
213
+ MIT. The underlying Rust crate is `MIT AND BSL-1.0 AND Apache-2.0`; its
214
+ NOTICE file itemizes the derived components.