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 +7 -0
- data/CHANGELOG.md +10 -0
- data/Cargo.lock +375 -0
- data/Cargo.toml +27 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +13 -0
- data/README.md +214 -0
- data/ext/nosj/Cargo.toml +25 -0
- data/ext/nosj/extconf.rb +6 -0
- data/ext/nosj/src/gen/errors.rs +61 -0
- data/ext/nosj/src/gen/hash_iter.rs +53 -0
- data/ext/nosj/src/gen/keys.rs +58 -0
- data/ext/nosj/src/gen/mod.rs +171 -0
- data/ext/nosj/src/gen/opts.rs +162 -0
- data/ext/nosj/src/gen/ruby.rs +100 -0
- data/ext/nosj/src/gen/walker.rs +638 -0
- data/ext/nosj/src/lib.rs +56 -0
- data/ext/nosj/src/parse.rs +211 -0
- data/ext/nosj/src/pointer.rs +190 -0
- data/ext/nosj/src/sink.rs +370 -0
- data/ext/nosj/src/state.rs +89 -0
- data/lib/nosj/json.rb +124 -0
- data/lib/nosj/multi_json.rb +51 -0
- data/lib/nosj/native.rb +11 -0
- data/lib/nosj/version.rb +6 -0
- data/lib/nosj.rb +193 -0
- data/sig/nosj.rbs +61 -0
- metadata +92 -0
data/lib/nosj.rb
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "nosj/version"
|
|
4
|
+
require_relative "nosj/native"
|
|
5
|
+
|
|
6
|
+
# nosj is the evil twin of the +json+ gem: the same API, output bytes,
|
|
7
|
+
# option names, and error messages, backed by the Rust
|
|
8
|
+
# {https://github.com/yaroslav/nosj nosj} crate with SIMD-accelerated
|
|
9
|
+
# parsing and generation. Beyond the +json+ gem's surface it adds
|
|
10
|
+
# zero-allocation validation ({.valid?}) and partial parsing
|
|
11
|
+
# ({.dig}, {.at_pointer}, and their batch forms).
|
|
12
|
+
#
|
|
13
|
+
# Options arrive as a positional Hash (the +json+ gem's own calling
|
|
14
|
+
# convention); an explicit +**kwargs+ would allocate per call.
|
|
15
|
+
#
|
|
16
|
+
# @example The json gem API
|
|
17
|
+
# NOSJ.parse('{"a":[1,true]}') #=> {"a" => [1, true]}
|
|
18
|
+
# NOSJ.generate({"a" => [1, true]}) #=> '{"a":[1,true]}'
|
|
19
|
+
#
|
|
20
|
+
# @example Drop-in acceleration for the JSON module
|
|
21
|
+
# require "nosj/json"
|
|
22
|
+
# JSON.parse(src) # routed through nosj
|
|
23
|
+
module NOSJ
|
|
24
|
+
# Base class for nosj errors.
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
|
|
27
|
+
# Raised when a value cannot be generated (non-finite floats without
|
|
28
|
+
# +allow_nan+, unsupported objects under +strict+, broken encodings).
|
|
29
|
+
# Message-compatible with +JSON::GeneratorError+.
|
|
30
|
+
class GeneratorError < Error; end
|
|
31
|
+
|
|
32
|
+
# Raised when generation exceeds +max_nesting+. Message-compatible
|
|
33
|
+
# with +JSON::NestingError+.
|
|
34
|
+
class NestingError < Error; end
|
|
35
|
+
|
|
36
|
+
PRETTY_GENERATE_OPTS = {
|
|
37
|
+
indent: " ", space: " ", object_nl: "\n", array_nl: "\n"
|
|
38
|
+
}.freeze
|
|
39
|
+
private_constant :PRETTY_GENERATE_OPTS
|
|
40
|
+
|
|
41
|
+
# Parses a JSON document, JSON.parse-compatible: same values, same
|
|
42
|
+
# option names, same behavior, byte-for-byte.
|
|
43
|
+
#
|
|
44
|
+
# The +json+ gem's legacy object-deserialization options
|
|
45
|
+
# (+object_class+, +array_class+, +decimal_class+,
|
|
46
|
+
# +create_additions+) are deliberately unsupported and raise
|
|
47
|
+
# ArgumentError.
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# NOSJ.parse('{"a":[1,true]}') #=> {"a" => [1, true]}
|
|
51
|
+
# NOSJ.parse('{"a":1}', symbolize_names: true) #=> {a: 1}
|
|
52
|
+
#
|
|
53
|
+
# @param source [String] the JSON document (UTF-8 or US-ASCII)
|
|
54
|
+
# @param opts [Hash, nil] +symbolize_names+, +freeze+, +max_nesting+
|
|
55
|
+
# (Integer or +false+ for unlimited), +allow_nan+,
|
|
56
|
+
# +allow_trailing_comma+
|
|
57
|
+
# @return [Object] the parsed value tree
|
|
58
|
+
# @raise [RuntimeError] when the document is malformed or not UTF-8
|
|
59
|
+
# @raise [ArgumentError] for unsupported options
|
|
60
|
+
def self.parse(source, opts = nil)
|
|
61
|
+
parse_native(source, opts)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @!method self.generate(obj, opts = nil)
|
|
65
|
+
# Generates JSON, JSON.generate-compatible: identical output bytes,
|
|
66
|
+
# including the gem's exact float formatting. Implemented natively
|
|
67
|
+
# (no Ruby forwarder frame; the definition lives in the extension).
|
|
68
|
+
#
|
|
69
|
+
# @example
|
|
70
|
+
# NOSJ.generate({"a" => [1, true]}) #=> '{"a":[1,true]}'
|
|
71
|
+
#
|
|
72
|
+
# @param obj [Object] the value tree to serialize
|
|
73
|
+
# @param opts [Hash, nil] +indent+, +space+, +space_before+,
|
|
74
|
+
# +object_nl+, +array_nl+, +max_nesting+ (Integer or +false+),
|
|
75
|
+
# +allow_nan+, +ascii_only+, +script_safe+ (alias +escape_slash+),
|
|
76
|
+
# +strict+, +depth+, +buffer_initial_length+
|
|
77
|
+
# @return [String] the JSON document
|
|
78
|
+
# @raise [GeneratorError] for non-finite floats without +allow_nan+,
|
|
79
|
+
# unsupported objects under +strict+, or broken string encodings
|
|
80
|
+
# @raise [NestingError] when nesting exceeds +max_nesting+
|
|
81
|
+
|
|
82
|
+
# Generates human-readable JSON, JSON.pretty_generate-compatible
|
|
83
|
+
# (two-space indent, newlines between elements). Options override the
|
|
84
|
+
# pretty defaults and are otherwise the same as {.generate}.
|
|
85
|
+
#
|
|
86
|
+
# @param obj [Object] the value tree to serialize
|
|
87
|
+
# @param opts [Hash, nil] see {.generate}
|
|
88
|
+
# @return [String] the pretty-printed JSON document
|
|
89
|
+
# @raise [GeneratorError] (see {.generate})
|
|
90
|
+
# @raise [NestingError] (see {.generate})
|
|
91
|
+
def self.pretty_generate(obj, opts = nil)
|
|
92
|
+
opts = opts.nil? ? PRETTY_GENERATE_OPTS : PRETTY_GENERATE_OPTS.merge(opts)
|
|
93
|
+
generate_native(obj, opts)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Validates a document without building any Ruby values: the full
|
|
97
|
+
# parser (tokenizers, string decode, number validation) runs into a
|
|
98
|
+
# null sink, 1.8-2.5x faster than {.parse}.
|
|
99
|
+
#
|
|
100
|
+
# Returns true iff <code>NOSJ.parse(source, opts)</code> would
|
|
101
|
+
# succeed: parse refusals (malformed JSON, wrong encoding, too-deep
|
|
102
|
+
# nesting) are +false+, while option and argument-type errors still
|
|
103
|
+
# raise exactly like {.parse}.
|
|
104
|
+
#
|
|
105
|
+
# @example
|
|
106
|
+
# NOSJ.valid?('{"a":1}') #=> true
|
|
107
|
+
# NOSJ.valid?('{"a":}') #=> false
|
|
108
|
+
#
|
|
109
|
+
# @param source [String] the JSON document
|
|
110
|
+
# @param opts [Hash, nil] same options as {.parse}
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
# @raise [ArgumentError] for unsupported options
|
|
113
|
+
def self.valid?(source, opts = nil)
|
|
114
|
+
valid_native(source, opts)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Partial parsing, Hash#dig-shaped: extracts one value from a JSON
|
|
118
|
+
# string without materializing the rest of the document. Skipped
|
|
119
|
+
# content is stepped over at SIMD block speed, so a lookup costs what
|
|
120
|
+
# it skips, not what the document weighs.
|
|
121
|
+
#
|
|
122
|
+
# @example
|
|
123
|
+
# NOSJ.dig(json, "users", 3, "name") #=> "grace" or nil
|
|
124
|
+
#
|
|
125
|
+
# @param source [String] the JSON document
|
|
126
|
+
# @param path [Array<String, Symbol, Integer>] object keys and array
|
|
127
|
+
# indices; unlike Array#dig, negative indices return +nil+ (JSON
|
|
128
|
+
# Pointer has no equivalent)
|
|
129
|
+
# @return [Object, nil] the matched value, or +nil+ when the path
|
|
130
|
+
# does not resolve
|
|
131
|
+
# @raise [ArgumentError] for path elements that are not Strings,
|
|
132
|
+
# Symbols, or Integers
|
|
133
|
+
def self.dig(source, *path)
|
|
134
|
+
dig_native(source, path)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Batch {.dig}: many paths resolved in ONE pass over the document.
|
|
138
|
+
# The walk descends only into subtrees some path still needs, so a
|
|
139
|
+
# batch costs about as much as its single deepest lookup.
|
|
140
|
+
#
|
|
141
|
+
# On malformed documents a batch may raise where a single dig would
|
|
142
|
+
# return +nil+: one pass scans every byte some path needs.
|
|
143
|
+
#
|
|
144
|
+
# @example
|
|
145
|
+
# NOSJ.dig_many(json, [["users", 3, "name"], ["meta", "count"]])
|
|
146
|
+
# #=> ["grace", 42]
|
|
147
|
+
#
|
|
148
|
+
# @param source [String] the JSON document
|
|
149
|
+
# @param paths [Array<Array<String, Symbol, Integer>>] one dig path
|
|
150
|
+
# per result
|
|
151
|
+
# @param opts [Hash, nil] materialization options ({.parse}'s
|
|
152
|
+
# +symbolize_names+, +freeze+, ...)
|
|
153
|
+
# @return [Array<Object, nil>] positionally aligned with +paths+
|
|
154
|
+
# @raise [ArgumentError] for malformed paths
|
|
155
|
+
def self.dig_many(source, paths, opts = nil)
|
|
156
|
+
dig_many_native(source, paths, opts)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Partial parsing by JSON Pointer (with the standard +~0+/+~1+
|
|
160
|
+
# escapes). The matched subtree materializes under the same options
|
|
161
|
+
# as {.parse}.
|
|
162
|
+
#
|
|
163
|
+
# @example
|
|
164
|
+
# NOSJ.at_pointer(json, "/users/3/name") #=> "grace" or nil
|
|
165
|
+
#
|
|
166
|
+
# @param source [String] the JSON document
|
|
167
|
+
# @param pointer [String] a JSON Pointer (empty string = whole
|
|
168
|
+
# document)
|
|
169
|
+
# @param opts [Hash, nil] materialization options
|
|
170
|
+
# @return [Object, nil] the matched value, or +nil+ when the pointer
|
|
171
|
+
# does not resolve
|
|
172
|
+
# @raise [ArgumentError] for a malformed pointer (non-empty without a
|
|
173
|
+
# leading +/+)
|
|
174
|
+
def self.at_pointer(source, pointer, opts = nil)
|
|
175
|
+
at_pointer_native(source, pointer, opts)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Batch {.at_pointer}: the pointer-string counterpart of {.dig_many},
|
|
179
|
+
# resolving the whole set in one pass.
|
|
180
|
+
#
|
|
181
|
+
# @example
|
|
182
|
+
# NOSJ.at_pointers(json, ["/users/3/name", "/meta/count"])
|
|
183
|
+
# #=> ["grace", 42]
|
|
184
|
+
#
|
|
185
|
+
# @param source [String] the JSON document
|
|
186
|
+
# @param pointers [Array<String>] JSON Pointers, one per result
|
|
187
|
+
# @param opts [Hash, nil] materialization options
|
|
188
|
+
# @return [Array<Object, nil>] positionally aligned with +pointers+
|
|
189
|
+
# @raise [ArgumentError] for malformed pointers
|
|
190
|
+
def self.at_pointers(source, pointers, opts = nil)
|
|
191
|
+
at_pointers_native(source, pointers, opts)
|
|
192
|
+
end
|
|
193
|
+
end
|
data/sig/nosj.rbs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Public interfaces only. The native layer (NOSJ.parse_native and
|
|
2
|
+
# friends) and the JSON drop-in's patched JSON singleton methods are
|
|
3
|
+
# implementation detail and deliberately unsigned.
|
|
4
|
+
module NOSJ
|
|
5
|
+
VERSION: String
|
|
6
|
+
|
|
7
|
+
# Any value JSON can represent, as Ruby objects: what parse returns
|
|
8
|
+
# and what partial parsing materializes. Object keys are String
|
|
9
|
+
# (Symbol with symbolize_names). generate accepts more than this
|
|
10
|
+
# alias (anything with to_json, unless strict), hence untyped there.
|
|
11
|
+
type value = nil | bool | Integer | Float | String
|
|
12
|
+
| Array[value] | Hash[String | Symbol, value]
|
|
13
|
+
|
|
14
|
+
# Options arrive as a positional Hash (the JSON gem's own calling
|
|
15
|
+
# convention; an explicit **kwargs would allocate per call), nil when
|
|
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.
|
|
20
|
+
type opts = Hash[Symbol, untyped]?
|
|
21
|
+
|
|
22
|
+
# One NOSJ.dig path element (negative Integer indices resolve to nil).
|
|
23
|
+
type path_element = String | Symbol | Integer
|
|
24
|
+
|
|
25
|
+
class Error < StandardError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class GeneratorError < Error
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class NestingError < Error
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.parse: (String source, ?opts opts) -> value
|
|
35
|
+
|
|
36
|
+
def self.generate: (untyped obj, ?opts opts) -> String
|
|
37
|
+
|
|
38
|
+
def self.pretty_generate: (untyped obj, ?opts opts) -> String
|
|
39
|
+
|
|
40
|
+
def self.valid?: (String source, ?opts opts) -> bool
|
|
41
|
+
|
|
42
|
+
def self.dig: (String source, *path_element path) -> value
|
|
43
|
+
|
|
44
|
+
def self.dig_many: (String source, Array[Array[path_element]] paths, ?opts opts) -> Array[value]
|
|
45
|
+
|
|
46
|
+
def self.at_pointer: (String source, String pointer, ?opts opts) -> value
|
|
47
|
+
|
|
48
|
+
def self.at_pointers: (String source, Array[String] pointers, ?opts opts) -> Array[value]
|
|
49
|
+
|
|
50
|
+
# Defined by `require "nosj/multi_json"`. The runtime superclass is
|
|
51
|
+
# multi_json's Adapter (whose namespace differs across multi_json
|
|
52
|
+
# versions), so it is not declared here.
|
|
53
|
+
class MultiJsonAdapter
|
|
54
|
+
# multi_json wraps whatever the adapter's ParseError names.
|
|
55
|
+
ParseError: singleton(::JSON::ParserError)
|
|
56
|
+
|
|
57
|
+
def load: (String string, ?Hash[Symbol, untyped] options) -> value
|
|
58
|
+
|
|
59
|
+
def dump: (untyped object, ?Hash[Symbol, untyped] options) -> String
|
|
60
|
+
end
|
|
61
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: nosj
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yaroslav Markin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rb_sys
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.9.91
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.9.91
|
|
26
|
+
description: 'gem nosj is an extremely fast, json-gem-compatible JSON parser and generator
|
|
27
|
+
for Ruby: Rust and SIMD via the first-party nosj crate, precompiled platform gems
|
|
28
|
+
with per-platform PGO, partial parsing (JSON Pointer, single and batch), allocation-free
|
|
29
|
+
validation, and a one-line JSON module drop-in.'
|
|
30
|
+
email:
|
|
31
|
+
- yaroslav@markin.net
|
|
32
|
+
executables: []
|
|
33
|
+
extensions:
|
|
34
|
+
- ext/nosj/extconf.rb
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- Cargo.lock
|
|
39
|
+
- Cargo.toml
|
|
40
|
+
- LICENSE.txt
|
|
41
|
+
- NOTICE
|
|
42
|
+
- README.md
|
|
43
|
+
- ext/nosj/Cargo.toml
|
|
44
|
+
- ext/nosj/extconf.rb
|
|
45
|
+
- ext/nosj/src/gen/errors.rs
|
|
46
|
+
- ext/nosj/src/gen/hash_iter.rs
|
|
47
|
+
- ext/nosj/src/gen/keys.rs
|
|
48
|
+
- ext/nosj/src/gen/mod.rs
|
|
49
|
+
- ext/nosj/src/gen/opts.rs
|
|
50
|
+
- ext/nosj/src/gen/ruby.rs
|
|
51
|
+
- ext/nosj/src/gen/walker.rs
|
|
52
|
+
- ext/nosj/src/lib.rs
|
|
53
|
+
- ext/nosj/src/parse.rs
|
|
54
|
+
- ext/nosj/src/pointer.rs
|
|
55
|
+
- ext/nosj/src/sink.rs
|
|
56
|
+
- ext/nosj/src/state.rs
|
|
57
|
+
- lib/nosj.rb
|
|
58
|
+
- lib/nosj/json.rb
|
|
59
|
+
- lib/nosj/multi_json.rb
|
|
60
|
+
- lib/nosj/native.rb
|
|
61
|
+
- lib/nosj/version.rb
|
|
62
|
+
- sig/nosj.rbs
|
|
63
|
+
homepage: https://github.com/yaroslav/nosj-ruby
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
allowed_push_host: https://rubygems.org
|
|
68
|
+
homepage_uri: https://github.com/yaroslav/nosj-ruby
|
|
69
|
+
source_code_uri: https://github.com/yaroslav/nosj-ruby
|
|
70
|
+
changelog_uri: https://github.com/yaroslav/nosj-ruby/blob/main/CHANGELOG.md
|
|
71
|
+
bug_tracker_uri: https://github.com/yaroslav/nosj-ruby/issues
|
|
72
|
+
documentation_uri: https://rubydoc.info/gems/nosj
|
|
73
|
+
rubygems_mfa_required: 'true'
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.3.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: 3.3.11
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 4.0.16
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: An extremely fast JSON parser and generator for Ruby, written in Rust and
|
|
91
|
+
SIMD-accelerated on every platform.
|
|
92
|
+
test_files: []
|