nosj 0.1.0-x86_64-linux → 0.2.0-x86_64-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 +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +63 -11
- data/lib/nosj/3.3/nosj.so +0 -0
- data/lib/nosj/3.4/nosj.so +0 -0
- data/lib/nosj/4.0/nosj.so +0 -0
- data/lib/nosj/lazy.rb +165 -0
- data/lib/nosj/version.rb +1 -1
- data/lib/nosj.rb +85 -2
- data/sig/nosj.rbs +54 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46cefa7803d25d779048e2c450ea1c5b5daa0c42508277432ef99df7918c783c
|
|
4
|
+
data.tar.gz: b11bcb71b9334785acbc7c3f1c48570f212e77646cb3c613db7cc76261895250
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1789804de8bff93994f515aba7d8fe5ac0f527e865bb8d917c8ed7d9a2cd08e7a3cddb2b0194653697eb62aafa466820c97b01c15a9afde9530a4f8b98bdedd5
|
|
7
|
+
data.tar.gz: 5aab59746b4856fb2afb505d18da3c4d9b2c3f0f02e115c402a17e94c5669b911443899cbf0958771606ae95789764ced92b091a1d5917cb186c620570811978
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.2.0] - 2026-07-16
|
|
2
|
+
|
|
3
|
+
- File APIs. `NOSJ.load_file(path, opts)` parses a file directly
|
|
4
|
+
(~1.3× faster than `parse(File.read(path))`—no file-sized Ruby
|
|
5
|
+
String is created), and `NOSJ.write_file(path, obj, opts)` generates
|
|
6
|
+
straight to disk, returning the byte count like `File.write`.
|
|
7
|
+
`NOSJ.load_lazy_file(path, opts)` wraps a file as a lazy document
|
|
8
|
+
over a read-only memory map, and `NOSJ.at_pointer_file` /
|
|
9
|
+
`NOSJ.dig_file` pull single values out of a file without reading the
|
|
10
|
+
rest into Ruby. Missing files raise the usual `Errno` exceptions.
|
|
11
|
+
- `NOSJ.lazy`: lazy documents. Wrap a document once, then read only
|
|
12
|
+
what you need: `doc["users"][3]["name"]` parses just that path, `#dig`
|
|
13
|
+
and `#at_pointer` resolve whole paths, and `#keys`, `#size`, and
|
|
14
|
+
`#each` inspect a node without parsing its values. Containers come
|
|
15
|
+
back lazy, scalars come back as plain Ruby values, and repeated
|
|
16
|
+
reads are cached. `#value` (also `#to_h` / `#to_a`) materializes a
|
|
17
|
+
subtree under the usual parse options (`symbolize_names`, `freeze`,
|
|
18
|
+
...). Pass a frozen string and creating the view is practically
|
|
19
|
+
free, even on megabyte documents. Malformed content raises on first
|
|
20
|
+
read, not at wrap time.
|
|
21
|
+
|
|
22
|
+
## [0.1.0] - 2026-07-16
|
|
2
23
|
|
|
3
24
|
Initial release.
|
|
4
25
|
|
data/README.md
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
- It is **faster** than gem json and every
|
|
11
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
12
|
faster than Yajl—[see Benchmarks](#benchmarks).
|
|
13
|
+
- It has **lazy documents**: `NOSJ.lazy` wraps a document and parses a value only when you touch it—repeated access costs nanoseconds, and everything you never read is never parsed.
|
|
14
|
+
- It has a **partial parsing mode**: JSON Pointer lookups that pull single values out of big documents in microseconds, skipping everything else.
|
|
15
|
+
- It has **file APIs**: parse, generate, dig, and lazy-wrap files directly—no throwaway file-sized Ruby String, and the partial modes memory-map the file so unread pages never even leave the disk.
|
|
13
16
|
- It comes **precompiled** (platform gems built with per-platform optimizations,
|
|
14
17
|
nothing to compile on install).
|
|
15
|
-
-
|
|
16
|
-
- Same API and option names as gem json.
|
|
18
|
+
- Otherwise, same API and option names as gem json.
|
|
17
19
|
|
|
18
20
|
**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
21
|
|
|
@@ -83,17 +85,23 @@ NOSJ.generate(obj) # indent, space, object_nl, ...,
|
|
|
83
85
|
NOSJ.pretty_generate(obj) # ascii_only, script_safe, strict
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`NOSJ.parse`, which already leads every parser above:
|
|
88
|
+
**Lazy documents.** `NOSJ.lazy` wraps a document in a lazy view: read
|
|
89
|
+
a field and only that path is parsed—containers stay lazy, scalars
|
|
90
|
+
arrive as plain Ruby values, and repeated reads are cached:
|
|
90
91
|
|
|
91
92
|
```ruby
|
|
92
|
-
NOSJ.
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
doc = NOSJ.lazy(json)
|
|
94
|
+
doc["users"][3]["name"] # parses only this path
|
|
95
|
+
doc.dig("meta", "count") # a whole path in one fused resolution
|
|
96
|
+
doc["users"].size # counted without materializing anything
|
|
97
|
+
doc["users"][3].value # materialize one subtree (parse options apply)
|
|
95
98
|
```
|
|
96
99
|
|
|
100
|
+
Pass a frozen string and creating the view is practically free—
|
|
101
|
+
nanoseconds, even on a megabyte document. Malformed content raises
|
|
102
|
+
when it is first read, not at wrap time.
|
|
103
|
+
|
|
104
|
+
|
|
97
105
|
**Partial parsing.** Pull values out of a document without
|
|
98
106
|
materializing the rest—skipped content is stepped over at SIMD block
|
|
99
107
|
speed, so a lookup costs what it skips, not what the document weighs:
|
|
@@ -114,11 +122,38 @@ at the far end of a 570 KB document costs ~71µs, still 13× faster
|
|
|
114
122
|
than parse-then-dig. Misses return nil; matched subtrees materialize
|
|
115
123
|
with the same options as `parse` (`symbolize_names:`, `freeze:`).
|
|
116
124
|
|
|
125
|
+
**Files.** Every mode has a file-native form, so a document never
|
|
126
|
+
round-trips through a throwaway Ruby String:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
NOSJ.load_file("config.json") # 1.3× File.read + parse
|
|
130
|
+
NOSJ.write_file("out.json", obj) # generate straight to disk
|
|
131
|
+
NOSJ.dig_file("huge.json", "users", 3, "name") # never reads the rest
|
|
132
|
+
NOSJ.at_pointer_file("huge.json", "/meta/count")
|
|
133
|
+
doc = NOSJ.load_lazy_file("huge.json") # lazy view over a memory map
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The partial and lazy forms memory-map the file, so pages you never
|
|
137
|
+
read are never loaded from disk. Missing files raise the usual
|
|
138
|
+
`Errno` exceptions. Measured numbers live in
|
|
139
|
+
[Benchmarks → File APIs](#file-apis).
|
|
140
|
+
|
|
141
|
+
**Validation without parsing.** `NOSJ.valid?` runs the full
|
|
142
|
+
parser—tokenizers, string decode, number validation—into a null sink
|
|
143
|
+
and allocates no Ruby objects at all. It is 2-4× faster than
|
|
144
|
+
`NOSJ.parse`, which already leads every parser above:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
NOSJ.valid?('{"a":1}') #=> true
|
|
148
|
+
NOSJ.valid?('{"a":}') #=> false
|
|
149
|
+
NOSJ.valid?(src, max_nesting: false) # same options as parse
|
|
150
|
+
```
|
|
151
|
+
|
|
117
152
|
## Benchmarks
|
|
118
153
|
|
|
119
154
|
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
155
|
|
|
121
|
-
Parse
|
|
156
|
+
### Parse
|
|
122
157
|
|
|
123
158
|
| file | nosj (i/s) | json | Oj | FastJsonparser | RapidJSON | Yajl |
|
|
124
159
|
|---|---:|---:|---:|---:|---:|---:|
|
|
@@ -136,7 +171,7 @@ Parse:
|
|
|
136
171
|
| tolstoy | **8.9k** | ×1.79 | ×1.96 | ×2.29 | ×2.10 | ×17.31 |
|
|
137
172
|
| twitter | **1.1k** | ×1.09 | ×1.83 | ×2.25 | ×2.61 | ×5.40 |
|
|
138
173
|
|
|
139
|
-
Generate
|
|
174
|
+
### Generate
|
|
140
175
|
|
|
141
176
|
| file | nosj (i/s) | json | Oj | RapidJSON | Yajl |
|
|
142
177
|
|---|---:|---:|---:|---:|---:|
|
|
@@ -157,6 +192,23 @@ Generate:
|
|
|
157
192
|
\* canada-generate is a statistical tie with the json gem (within
|
|
158
193
|
measurement error).
|
|
159
194
|
|
|
195
|
+
### File APIs
|
|
196
|
+
|
|
197
|
+
twitter.json (570 KB) from a warm page cache, medians of 7 alternating
|
|
198
|
+
rounds against the plain-Ruby composition on the same parser (Apple
|
|
199
|
+
Silicon dev box, Ruby 4.0.6 + YJIT, PGO build, 2026-07-16):
|
|
200
|
+
|
|
201
|
+
| operation | µs/op | vs the Ruby way |
|
|
202
|
+
|---|---:|---|
|
|
203
|
+
| `NOSJ.load_file` (parse the whole file) | 948 | ×1.33 vs `NOSJ.parse(File.read(path))` |
|
|
204
|
+
| `NOSJ.dig_file` (one deep field) | 246 | ×5.2 vs read + parse + dig |
|
|
205
|
+
| `NOSJ.load_lazy_file` + one field | 257 | ×5.0 vs read + parse + dig |
|
|
206
|
+
|
|
207
|
+
`NOSJ.write_file` measures at parity with
|
|
208
|
+
`File.write(NOSJ.generate(obj))` on this box—file-write timings swing
|
|
209
|
+
too much for an honest multiplier; what it saves is the intermediate
|
|
210
|
+
file-sized Ruby String.
|
|
211
|
+
|
|
160
212
|
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
213
|
|
|
162
214
|
## Switching from the json gem
|
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/lazy.rb
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NOSJ
|
|
4
|
+
# A lazy view of one JSON container inside a document created with
|
|
5
|
+
# {NOSJ.lazy}. Nothing is parsed until it is touched: indexing walks
|
|
6
|
+
# the raw bytes to the requested child (skipping siblings at SIMD
|
|
7
|
+
# block speed), returns another Lazy node for containers, and
|
|
8
|
+
# materializes plain Ruby values for scalars. Resolved children are
|
|
9
|
+
# cached, so repeated access is free and object identity is stable.
|
|
10
|
+
#
|
|
11
|
+
# Validation is as-you-go: skipped content is bracket-balance
|
|
12
|
+
# checked and resolved targets fully validated, so a malformed region
|
|
13
|
+
# raises when an access first walks it, not at {NOSJ.lazy} time.
|
|
14
|
+
#
|
|
15
|
+
# Nodes hold their own stable copy of the document (shared across the
|
|
16
|
+
# whole node tree), so mutating the source string later is harmless.
|
|
17
|
+
#
|
|
18
|
+
# @example Pull two fields out of a big document
|
|
19
|
+
# doc = NOSJ.lazy(huge_json)
|
|
20
|
+
# doc["users"][3]["name"] # only this path is parsed
|
|
21
|
+
# doc.dig("meta", "count")
|
|
22
|
+
#
|
|
23
|
+
# @example Materialize a subtree
|
|
24
|
+
# doc["users"][3].value #=> {"name" => ..., ...}
|
|
25
|
+
class Lazy
|
|
26
|
+
include Enumerable
|
|
27
|
+
|
|
28
|
+
# Resolves one child. String and Symbol keys index objects; Integer
|
|
29
|
+
# indices index arrays (negative indices resolve to +nil+, as in
|
|
30
|
+
# {NOSJ.dig}). Containers come back as further Lazy nodes, scalars
|
|
31
|
+
# as plain Ruby values, misses as +nil+. Results are cached on the
|
|
32
|
+
# node.
|
|
33
|
+
#
|
|
34
|
+
# @param token [String, Symbol, Integer]
|
|
35
|
+
# @return [NOSJ::Lazy, Object, nil]
|
|
36
|
+
def [](token)
|
|
37
|
+
cache = (@children ||= {})
|
|
38
|
+
cache.fetch(token) { cache[token] = __get(token) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Hash#dig-shaped access, fused into a single resolution: the whole
|
|
42
|
+
# path resolves in one walk of this node's bytes, no intermediate
|
|
43
|
+
# nodes. Semantics match {NOSJ.dig}: misses, negative indices, and
|
|
44
|
+
# steps into scalars resolve to +nil+. Not cached.
|
|
45
|
+
#
|
|
46
|
+
# @param path [Array<String, Symbol, Integer>]
|
|
47
|
+
# @return [NOSJ::Lazy, Object, nil]
|
|
48
|
+
def dig(first, *rest)
|
|
49
|
+
__dig([first, *rest])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Resolves an RFC 6901 JSON Pointer within this node's subtree,
|
|
53
|
+
# with the same lazy/materialize behavior as {#[]}. Not cached.
|
|
54
|
+
#
|
|
55
|
+
# @param pointer [String] e.g. <tt>"/users/3/name"</tt>
|
|
56
|
+
# @return [NOSJ::Lazy, Object, nil]
|
|
57
|
+
def at_pointer(pointer)
|
|
58
|
+
__at_pointer(pointer)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Materializes this node's whole subtree as plain Ruby values,
|
|
62
|
+
# under the options given to {NOSJ.lazy} (+symbolize_names+,
|
|
63
|
+
# +freeze+, ...).
|
|
64
|
+
#
|
|
65
|
+
# @return [Hash, Array]
|
|
66
|
+
def value
|
|
67
|
+
__materialize
|
|
68
|
+
end
|
|
69
|
+
alias_method :materialize, :value
|
|
70
|
+
|
|
71
|
+
# @return [Hash] the materialized subtree
|
|
72
|
+
# @raise [TypeError] on an array node
|
|
73
|
+
def to_h
|
|
74
|
+
raise TypeError, "to_h on a JSON array" unless object?
|
|
75
|
+
|
|
76
|
+
__materialize
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Array] the materialized subtree
|
|
80
|
+
# @raise [TypeError] on an object node
|
|
81
|
+
def to_a
|
|
82
|
+
raise TypeError, "to_a on a JSON object" unless array?
|
|
83
|
+
|
|
84
|
+
__materialize
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean] whether this node is a JSON object
|
|
88
|
+
def object?
|
|
89
|
+
__kind == :object
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] whether this node is a JSON array
|
|
93
|
+
def array?
|
|
94
|
+
__kind == :array
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The object's keys (always Strings), read in one walk without
|
|
98
|
+
# materializing any values.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<String>]
|
|
101
|
+
# @raise [TypeError] on an array node
|
|
102
|
+
def keys
|
|
103
|
+
__keys
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Entry count (object pairs or array elements), read in one walk
|
|
107
|
+
# without materializing anything.
|
|
108
|
+
#
|
|
109
|
+
# @return [Integer]
|
|
110
|
+
def size
|
|
111
|
+
__size
|
|
112
|
+
end
|
|
113
|
+
alias_method :length, :size
|
|
114
|
+
alias_method :count, :size
|
|
115
|
+
|
|
116
|
+
# @return [Boolean]
|
|
117
|
+
def empty?
|
|
118
|
+
size.zero?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Iterates direct children, resolved in one walk: object nodes
|
|
122
|
+
# yield +[key, child]+ pairs (like Hash), array nodes yield
|
|
123
|
+
# children. Containers arrive as Lazy nodes, scalars as values.
|
|
124
|
+
#
|
|
125
|
+
# @return [Enumerator] when no block is given
|
|
126
|
+
def each(&block)
|
|
127
|
+
return enum_for(:each) unless block
|
|
128
|
+
|
|
129
|
+
if object?
|
|
130
|
+
__children.each { |pair| yield pair }
|
|
131
|
+
else
|
|
132
|
+
__children.each { |child| yield child }
|
|
133
|
+
end
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @return [String] kind and span size, without touching content
|
|
138
|
+
def inspect
|
|
139
|
+
"#<NOSJ::Lazy #{__kind} (#{__byte_size} bytes)>"
|
|
140
|
+
end
|
|
141
|
+
alias_method :to_s, :inspect
|
|
142
|
+
|
|
143
|
+
private :__get, :__dig, :__at_pointer, :__materialize, :__kind,
|
|
144
|
+
:__byte_size, :__keys, :__size, :__children
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Wraps a JSON document for lazy, on-demand access: returns a
|
|
148
|
+
# {NOSJ::Lazy} node for a container root, or the materialized value
|
|
149
|
+
# for a scalar root. The document bytes are copied once; no parsing
|
|
150
|
+
# happens beyond locating the root value.
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# doc = NOSJ.lazy('{"users":[{"name":"ada"},{"name":"grace"}]}')
|
|
154
|
+
# doc["users"][1]["name"] #=> "grace" — the rest is never parsed
|
|
155
|
+
#
|
|
156
|
+
# @param source [String] the JSON document (UTF-8 or US-ASCII)
|
|
157
|
+
# @param opts [Hash, nil] {NOSJ.parse} options applied whenever a
|
|
158
|
+
# value materializes: +symbolize_names+, +freeze+, +max_nesting+,
|
|
159
|
+
# +allow_nan+, +allow_trailing_comma+
|
|
160
|
+
# @return [NOSJ::Lazy, Object]
|
|
161
|
+
# @raise [RuntimeError] when the document root is malformed
|
|
162
|
+
def self.lazy(source, opts = nil)
|
|
163
|
+
lazy_native(source, opts)
|
|
164
|
+
end
|
|
165
|
+
end
|
data/lib/nosj/version.rb
CHANGED
data/lib/nosj.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "nosj/version"
|
|
4
4
|
require_relative "nosj/native"
|
|
5
|
+
require_relative "nosj/lazy"
|
|
5
6
|
|
|
6
7
|
# nosj is the evil twin of the +json+ gem: the same API, output bytes,
|
|
7
8
|
# option names, and error messages, backed by the Rust
|
|
8
9
|
# {https://github.com/yaroslav/nosj nosj} crate with SIMD-accelerated
|
|
9
10
|
# parsing and generation. Beyond the +json+ gem's surface it adds
|
|
10
|
-
# zero-allocation validation ({.valid?})
|
|
11
|
-
# ({.dig}, {.at_pointer}, and their batch forms)
|
|
11
|
+
# zero-allocation validation ({.valid?}), partial parsing
|
|
12
|
+
# ({.dig}, {.at_pointer}, and their batch forms), and lazy documents
|
|
13
|
+
# ({.lazy}).
|
|
12
14
|
#
|
|
13
15
|
# Options arrive as a positional Hash (the +json+ gem's own calling
|
|
14
16
|
# convention); an explicit +**kwargs+ would allocate per call.
|
|
@@ -190,4 +192,85 @@ module NOSJ
|
|
|
190
192
|
def self.at_pointers(source, pointers, opts = nil)
|
|
191
193
|
at_pointers_native(source, pointers, opts)
|
|
192
194
|
end
|
|
195
|
+
|
|
196
|
+
# Parses a JSON file, like +JSON.load_file+—except the file is read
|
|
197
|
+
# natively into a reused buffer, so no file-sized Ruby String is ever
|
|
198
|
+
# created (or garbage-collected).
|
|
199
|
+
#
|
|
200
|
+
# @example
|
|
201
|
+
# NOSJ.load_file("config.json", symbolize_names: true)
|
|
202
|
+
#
|
|
203
|
+
# @param path [String] the file to parse (UTF-8)
|
|
204
|
+
# @param opts [Hash, nil] the same options as {.parse}
|
|
205
|
+
# @return [Object] the parsed value tree
|
|
206
|
+
# @raise [SystemCallError] +Errno::ENOENT+ and friends, like File.read
|
|
207
|
+
# @raise [RuntimeError] when the document is malformed or not UTF-8
|
|
208
|
+
def self.load_file(path, opts = nil)
|
|
209
|
+
load_file_native(path, opts)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Generates +obj+ as JSON and writes it to +path+, streaming the
|
|
213
|
+
# generator's buffer straight to disk—no intermediate Ruby String.
|
|
214
|
+
#
|
|
215
|
+
# @example
|
|
216
|
+
# NOSJ.write_file("out.json", {"a" => [1, true]}) #=> 14
|
|
217
|
+
# NOSJ.write_file("pretty.json", obj, indent: " ", object_nl: "\n")
|
|
218
|
+
#
|
|
219
|
+
# @param path [String] the file to (over)write
|
|
220
|
+
# @param obj [Object] the value tree to generate
|
|
221
|
+
# @param opts [Hash, nil] the same options as {.generate}
|
|
222
|
+
# @return [Integer] the number of bytes written, like File.write
|
|
223
|
+
# @raise [SystemCallError] +Errno::ENOENT+ and friends, like File.write
|
|
224
|
+
# @raise [GeneratorError] like {.generate}
|
|
225
|
+
def self.write_file(path, obj, opts = nil)
|
|
226
|
+
write_file_native(path, obj, opts)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Wraps a JSON file as a lazy document ({.lazy} for files): the file
|
|
230
|
+
# is memory-mapped read-only, so beyond one sequential UTF-8 check,
|
|
231
|
+
# pages you never read are never loaded from disk. The mapping lives
|
|
232
|
+
# as long as any node on it; the file must not be modified while it
|
|
233
|
+
# is in use.
|
|
234
|
+
#
|
|
235
|
+
# @example
|
|
236
|
+
# doc = NOSJ.load_lazy_file("huge.json")
|
|
237
|
+
# doc["users"][3]["name"] # touches only these pages
|
|
238
|
+
#
|
|
239
|
+
# @param path [String] the file to wrap (UTF-8)
|
|
240
|
+
# @param opts [Hash, nil] {.parse} options applied on materialization
|
|
241
|
+
# @return [NOSJ::Lazy, Object]
|
|
242
|
+
# @raise [SystemCallError] +Errno::ENOENT+ and friends
|
|
243
|
+
# @raise [RuntimeError] when the file is not UTF-8 or the root is malformed
|
|
244
|
+
def self.load_lazy_file(path, opts = nil)
|
|
245
|
+
load_lazy_file_native(path, opts)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# {.at_pointer} against a file: memory-maps it, resolves the pointer,
|
|
249
|
+
# materializes only the matched subtree, and never reads the rest
|
|
250
|
+
# into Ruby.
|
|
251
|
+
#
|
|
252
|
+
# @example
|
|
253
|
+
# NOSJ.at_pointer_file("huge.json", "/users/3/name")
|
|
254
|
+
#
|
|
255
|
+
# @param path [String] the file to query (UTF-8)
|
|
256
|
+
# @param pointer [String] an RFC 6901 JSON Pointer
|
|
257
|
+
# @param opts [Hash, nil] materialization options
|
|
258
|
+
# @return [Object, nil] nil when the pointer misses
|
|
259
|
+
# @raise [ArgumentError] for malformed pointers
|
|
260
|
+
def self.at_pointer_file(path, pointer, opts = nil)
|
|
261
|
+
at_pointer_file_native(path, pointer, opts)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# {.dig} against a file: the Hash#dig-shaped counterpart of
|
|
265
|
+
# {.at_pointer_file}. Negative indices resolve to nil.
|
|
266
|
+
#
|
|
267
|
+
# @example
|
|
268
|
+
# NOSJ.dig_file("huge.json", "users", 3, "name")
|
|
269
|
+
#
|
|
270
|
+
# @param path [String] the file to query (UTF-8)
|
|
271
|
+
# @param path_elements [Array<String, Symbol, Integer>]
|
|
272
|
+
# @return [Object, nil]
|
|
273
|
+
def self.dig_file(path, *path_elements)
|
|
274
|
+
dig_file_native(path, path_elements)
|
|
275
|
+
end
|
|
193
276
|
end
|
data/sig/nosj.rbs
CHANGED
|
@@ -47,6 +47,60 @@ module NOSJ
|
|
|
47
47
|
|
|
48
48
|
def self.at_pointers: (String source, Array[String] pointers, ?opts opts) -> Array[value]
|
|
49
49
|
|
|
50
|
+
def self.lazy: (String source, ?opts opts) -> (Lazy | value)
|
|
51
|
+
|
|
52
|
+
def self.load_file: (String path, ?opts opts) -> value
|
|
53
|
+
|
|
54
|
+
def self.write_file: (String path, untyped obj, ?opts opts) -> Integer
|
|
55
|
+
|
|
56
|
+
def self.load_lazy_file: (String path, ?opts opts) -> (Lazy | value)
|
|
57
|
+
|
|
58
|
+
def self.at_pointer_file: (String path, String pointer, ?opts opts) -> value
|
|
59
|
+
|
|
60
|
+
def self.dig_file: (String path, *path_element path_elements) -> value
|
|
61
|
+
|
|
62
|
+
# A lazy view of one JSON container: children resolve on first
|
|
63
|
+
# access (containers as further Lazy nodes, scalars as values,
|
|
64
|
+
# misses as nil) and are cached on the node.
|
|
65
|
+
class Lazy
|
|
66
|
+
include Enumerable[untyped]
|
|
67
|
+
|
|
68
|
+
def []: (path_element token) -> (Lazy | value)
|
|
69
|
+
|
|
70
|
+
def dig: (path_element first, *path_element rest) -> (Lazy | value)
|
|
71
|
+
|
|
72
|
+
def at_pointer: (String pointer) -> (Lazy | value)
|
|
73
|
+
|
|
74
|
+
def value: () -> value
|
|
75
|
+
|
|
76
|
+
alias materialize value
|
|
77
|
+
|
|
78
|
+
def to_h: () -> Hash[String | Symbol, value]
|
|
79
|
+
|
|
80
|
+
def to_a: () -> Array[value]
|
|
81
|
+
|
|
82
|
+
def object?: () -> bool
|
|
83
|
+
|
|
84
|
+
def array?: () -> bool
|
|
85
|
+
|
|
86
|
+
def keys: () -> Array[String]
|
|
87
|
+
|
|
88
|
+
def size: () -> Integer
|
|
89
|
+
|
|
90
|
+
alias length size
|
|
91
|
+
|
|
92
|
+
alias count size
|
|
93
|
+
|
|
94
|
+
def empty?: () -> bool
|
|
95
|
+
|
|
96
|
+
def each: () { (untyped) -> void } -> self
|
|
97
|
+
| () -> Enumerator[untyped, self]
|
|
98
|
+
|
|
99
|
+
def inspect: () -> String
|
|
100
|
+
|
|
101
|
+
alias to_s inspect
|
|
102
|
+
end
|
|
103
|
+
|
|
50
104
|
# Defined by `require "nosj/multi_json"`. The runtime superclass is
|
|
51
105
|
# multi_json's Adapter (whose namespace differs across multi_json
|
|
52
106
|
# versions), so it is not declared here.
|
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
|
+
version: 0.2.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Markin
|
|
@@ -11,7 +11,9 @@ date: 1980-01-02 00:00:00.000000000 Z
|
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: 'gem nosj is an extremely fast, json-gem-compatible JSON parser and generator
|
|
13
13
|
for Ruby: Rust and SIMD via the first-party nosj crate, precompiled platform gems
|
|
14
|
-
with per-platform PGO, partial parsing (JSON Pointer, single and batch),
|
|
14
|
+
with per-platform PGO, partial parsing (JSON Pointer, single and batch), lazy documents
|
|
15
|
+
that parse a value only when you touch it, file APIs that parse, generate, and query
|
|
16
|
+
files natively (memory-mapped, so unread pages never leave the disk), allocation-free
|
|
15
17
|
validation, and a one-line JSON module drop-in.'
|
|
16
18
|
email:
|
|
17
19
|
- yaroslav@markin.net
|
|
@@ -28,6 +30,7 @@ files:
|
|
|
28
30
|
- lib/nosj/3.4/nosj.so
|
|
29
31
|
- lib/nosj/4.0/nosj.so
|
|
30
32
|
- lib/nosj/json.rb
|
|
33
|
+
- lib/nosj/lazy.rb
|
|
31
34
|
- lib/nosj/multi_json.rb
|
|
32
35
|
- lib/nosj/native.rb
|
|
33
36
|
- lib/nosj/version.rb
|