Module: NOSJ

Defined in:
lib/nosj.rb,
lib/nosj/json.rb,
lib/nosj/lazy.rb,
lib/nosj/version.rb,
lib/nosj/multi_json.rb,
sig/nosj.rbs

Overview

Public interfaces only. The native layer (NOSJ.parse_native and friends) and the JSON drop-in's patched JSON singleton methods are implementation detail and deliberately unsigned.

Defined Under Namespace

Modules: JSONDropIn Classes: Error, GeneratorError, Lazy, MultiJsonAdapter, NestingError

Constant Summary collapse

VERSION =

The gem version.

Returns:

  • (String)
"0.2.0"

Class Method Summary collapse

Class Method Details

.at_pointer(source, pointer, opts = nil) ⇒ Object?

Partial parsing by JSON Pointer (with the standard +~0+/+~1+ escapes). The matched subtree materializes under the same options as parse.

Examples:

NOSJ.at_pointer(json, "/users/3/name")  #=> "grace" or nil

Parameters:

  • source (String)

    the JSON document

  • pointer (String)

    a JSON Pointer (empty string = whole document)

  • opts (Hash, nil) (defaults to: nil)

    materialization options

Returns:

  • (Object, nil)

    the matched value, or nil when the pointer does not resolve

Raises:

  • (ArgumentError)

    for a malformed pointer (non-empty without a leading /)



176
177
178
# File 'lib/nosj.rb', line 176

def self.at_pointer(source, pointer, opts = nil)
  at_pointer_native(source, pointer, opts)
end

.at_pointer_file(path, pointer, opts = nil) ⇒ Object?

at_pointer against a file: memory-maps it, resolves the pointer, materializes only the matched subtree, and never reads the rest into Ruby.

Examples:

NOSJ.at_pointer_file("huge.json", "/users/3/name")

Parameters:

  • path (String)

    the file to query (UTF-8)

  • pointer (String)

    an RFC 6901 JSON Pointer

  • opts (Hash, nil) (defaults to: nil)

    materialization options

Returns:

  • (Object, nil)

    nil when the pointer misses

Raises:

  • (ArgumentError)

    for malformed pointers



260
261
262
# File 'lib/nosj.rb', line 260

def self.at_pointer_file(path, pointer, opts = nil)
  at_pointer_file_native(path, pointer, opts)
end

.at_pointers(source, pointers, opts = nil) ⇒ Array<Object, nil>

Batch at_pointer: the pointer-string counterpart of dig_many, resolving the whole set in one pass.

Examples:

NOSJ.at_pointers(json, ["/users/3/name", "/meta/count"])
#=> ["grace", 42]

Parameters:

  • source (String)

    the JSON document

  • pointers (Array<String>)

    JSON Pointers, one per result

  • opts (Hash, nil) (defaults to: nil)

    materialization options

Returns:

  • (Array<Object, nil>)

    positionally aligned with pointers

Raises:

  • (ArgumentError)

    for malformed pointers



192
193
194
# File 'lib/nosj.rb', line 192

def self.at_pointers(source, pointers, opts = nil)
  at_pointers_native(source, pointers, opts)
end

.dig(source, *path) ⇒ Object?

Partial parsing, Hash#dig-shaped: extracts one value from a JSON string without materializing the rest of the document. Skipped content is stepped over at SIMD block speed, so a lookup costs what it skips, not what the document weighs.

Examples:

NOSJ.dig(json, "users", 3, "name")  #=> "grace" or nil

Parameters:

  • source (String)

    the JSON document

  • path (Array<String, Symbol, Integer>)

    object keys and array indices; unlike Array#dig, negative indices return nil (JSON Pointer has no equivalent)

Returns:

  • (Object, nil)

    the matched value, or nil when the path does not resolve

Raises:

  • (ArgumentError)

    for path elements that are not Strings, Symbols, or Integers



135
136
137
# File 'lib/nosj.rb', line 135

def self.dig(source, *path)
  dig_native(source, path)
end

.dig_file(path, *path_elements) ⇒ Object?

dig against a file: the Hash#dig-shaped counterpart of at_pointer_file. Negative indices resolve to nil.

Examples:

NOSJ.dig_file("huge.json", "users", 3, "name")

Parameters:

  • path (String)

    the file to query (UTF-8)

  • path_elements (Array<String, Symbol, Integer>)

Returns:

  • (Object, nil)


273
274
275
# File 'lib/nosj.rb', line 273

def self.dig_file(path, *path_elements)
  dig_file_native(path, path_elements)
end

.dig_many(source, paths, opts = nil) ⇒ Array<Object, nil>

Batch dig: many paths resolved in ONE pass over the document. The walk descends only into subtrees some path still needs, so a batch costs about as much as its single deepest lookup.

On malformed documents a batch may raise where a single dig would return nil: one pass scans every byte some path needs.

Examples:

NOSJ.dig_many(json, [["users", 3, "name"], ["meta", "count"]])
#=> ["grace", 42]

Parameters:

  • source (String)

    the JSON document

  • paths (Array<Array<String, Symbol, Integer>>)

    one dig path per result

  • opts (Hash, nil) (defaults to: nil)

    materialization options (parse's symbolize_names, freeze, ...)

Returns:

  • (Array<Object, nil>)

    positionally aligned with paths

Raises:

  • (ArgumentError)

    for malformed paths



157
158
159
# File 'lib/nosj.rb', line 157

def self.dig_many(source, paths, opts = nil)
  dig_many_native(source, paths, opts)
end

.generate(obj, opts = nil) ⇒ String

Generates JSON, JSON.generate-compatible: identical output bytes, including the gem's exact float formatting. Implemented natively (no Ruby forwarder frame; the definition lives in the extension).

Examples:

NOSJ.generate({"a" => [1, true]})  #=> '{"a":[1,true]}'

Parameters:

  • obj (Object)

    the value tree to serialize

  • opts (Hash, nil) (defaults to: nil)

    indent, space, space_before, object_nl, array_nl, max_nesting (Integer or false), allow_nan, ascii_only, script_safe (alias escape_slash), strict, depth, buffer_initial_length

Returns:

  • (String)

    the JSON document

Raises:

  • (GeneratorError)

    for non-finite floats without allow_nan, unsupported objects under strict, or broken string encodings

  • (NestingError)

    when nesting exceeds max_nesting



# File 'lib/nosj.rb', line 66

.lazy(source, opts = nil) ⇒ NOSJ::Lazy, Object

Wraps a JSON document for lazy, on-demand access: returns a Lazy node for a container root, or the materialized value for a scalar root. The document bytes are copied once; no parsing happens beyond locating the root value.

Examples:

doc = NOSJ.lazy('{"users":[{"name":"ada"},{"name":"grace"}]}')
doc["users"][1]["name"]  #=> "grace" — the rest is never parsed

Parameters:

  • source (String)

    the JSON document (UTF-8 or US-ASCII)

  • opts (Hash, nil) (defaults to: nil)

    parse options applied whenever a value materializes: symbolize_names, freeze, max_nesting, allow_nan, allow_trailing_comma

Returns:

Raises:

  • (RuntimeError)

    when the document root is malformed



162
163
164
# File 'lib/nosj/lazy.rb', line 162

def self.lazy(source, opts = nil)
  lazy_native(source, opts)
end

.load_file(path, opts = nil) ⇒ Object

Parses a JSON file, like +JSON.load_file+—except the file is read natively into a reused buffer, so no file-sized Ruby String is ever created (or garbage-collected).

Examples:

NOSJ.load_file("config.json", symbolize_names: true)

Parameters:

  • path (String)

    the file to parse (UTF-8)

  • opts (Hash, nil) (defaults to: nil)

    the same options as parse

Returns:

  • (Object)

    the parsed value tree

Raises:

  • (SystemCallError)

    Errno::ENOENT and friends, like File.read

  • (RuntimeError)

    when the document is malformed or not UTF-8



208
209
210
# File 'lib/nosj.rb', line 208

def self.load_file(path, opts = nil)
  load_file_native(path, opts)
end

.load_lazy_file(path, opts = nil) ⇒ NOSJ::Lazy, Object

Wraps a JSON file as a lazy document (lazy for files): the file is memory-mapped read-only, so beyond one sequential UTF-8 check, pages you never read are never loaded from disk. The mapping lives as long as any node on it; the file must not be modified while it is in use.

Examples:

doc = NOSJ.load_lazy_file("huge.json")
doc["users"][3]["name"]   # touches only these pages

Parameters:

  • path (String)

    the file to wrap (UTF-8)

  • opts (Hash, nil) (defaults to: nil)

    parse options applied on materialization

Returns:

Raises:

  • (SystemCallError)

    Errno::ENOENT and friends

  • (RuntimeError)

    when the file is not UTF-8 or the root is malformed



244
245
246
# File 'lib/nosj.rb', line 244

def self.load_lazy_file(path, opts = nil)
  load_lazy_file_native(path, opts)
end

.parse(source, opts = nil) ⇒ Object

Parses a JSON document, JSON.parse-compatible: same values, same option names, same behavior, byte-for-byte.

The json gem's legacy object-deserialization options (+object_class+, array_class, decimal_class, create_additions) are deliberately unsupported and raise ArgumentError.

Examples:

NOSJ.parse('{"a":[1,true]}')                      #=> {"a" => [1, true]}
NOSJ.parse('{"a":1}', symbolize_names: true)      #=> {a: 1}

Parameters:

  • source (String)

    the JSON document (UTF-8 or US-ASCII)

  • opts (Hash, nil) (defaults to: nil)

    symbolize_names, freeze, max_nesting (Integer or false for unlimited), allow_nan, allow_trailing_comma

Returns:

  • (Object)

    the parsed value tree

Raises:

  • (RuntimeError)

    when the document is malformed or not UTF-8

  • (ArgumentError)

    for unsupported options



62
63
64
# File 'lib/nosj.rb', line 62

def self.parse(source, opts = nil)
  parse_native(source, opts)
end

.pretty_generate(obj, opts = nil) ⇒ String

Generates human-readable JSON, JSON.pretty_generate-compatible (two-space indent, newlines between elements). Options override the pretty defaults and are otherwise the same as generate.

Parameters:

  • obj (Object)

    the value tree to serialize

  • opts (Hash, nil) (defaults to: nil)

Returns:

  • (String)

    the pretty-printed JSON document



93
94
95
96
# File 'lib/nosj.rb', line 93

def self.pretty_generate(obj, opts = nil)
  opts = opts.nil? ? PRETTY_GENERATE_OPTS : PRETTY_GENERATE_OPTS.merge(opts)
  generate_native(obj, opts)
end

.valid?(source, opts = nil) ⇒ Boolean

Validates a document without building any Ruby values: the full parser (tokenizers, string decode, number validation) runs into a null sink, 1.8-2.5x faster than parse.

Returns true iff NOSJ.parse(source, opts) would succeed: parse refusals (malformed JSON, wrong encoding, too-deep nesting) are false, while option and argument-type errors still raise exactly like parse.

Examples:

NOSJ.valid?('{"a":1}')  #=> true
NOSJ.valid?('{"a":}')   #=> false

Parameters:

  • source (String)

    the JSON document

  • opts (Hash, nil) (defaults to: nil)

    same options as parse

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    for unsupported options



115
116
117
# File 'lib/nosj.rb', line 115

def self.valid?(source, opts = nil)
  valid_native(source, opts)
end

.write_file(path, obj, opts = nil) ⇒ Integer

Generates obj as JSON and writes it to path, streaming the generator's buffer straight to disk—no intermediate Ruby String.

Examples:

NOSJ.write_file("out.json", {"a" => [1, true]})   #=> 14
NOSJ.write_file("pretty.json", obj, indent: "  ", object_nl: "\n")

Parameters:

  • path (String)

    the file to (over)write

  • obj (Object)

    the value tree to generate

  • opts (Hash, nil) (defaults to: nil)

    the same options as generate

Returns:

  • (Integer)

    the number of bytes written, like File.write

Raises:



225
226
227
# File 'lib/nosj.rb', line 225

def self.write_file(path, obj, opts = nil)
  write_file_native(path, obj, opts)
end