Skip to content

Latest commit

 

History

History
144 lines (101 loc) · 7.12 KB

File metadata and controls

144 lines (101 loc) · 7.12 KB

Contents


SmarterJSON Basic Write API

Writing JSON has one entry point: SmarterJSON.generate. It turns a Ruby value into a JSON String — strict, interoperable output by default, or NDJSON when you ask for it.

SmarterJSON.generate — write a Ruby value as JSON

require "smarter_json"

SmarterJSON.generate({ "a" => 1, "b" => [2, 3] })   # => '{"a":1,"b":[2,3]}'
SmarterJSON.generate([1, 2, 3])                       # => '[1,2,3]'
SmarterJSON.generate("hi")                            # => '"hi"'
SmarterJSON.generate(42)                              # => '42'
SmarterJSON.generate(nil)                             # => 'null'

The output is always valid, strict JSON — there is no lenient write mode. (We are lenient about what we read, strict about what we write, so the output interoperates with every other JSON parser.)

How Ruby values map to JSON

Ruby JSON output
Hash object {…} — keys are stringified (Symbol keys too)
Array array […]
String quoted string, escaped (see below)
Symbol quoted string (:sym"sym")
Integer number
Float number (non-finite raises — see below)
BigDecimal number, full precision (not a string)
true / false / nil true / false / null
SmarterJSON.generate({ a: 1, b: :sym })                       # => '{"a":1,"b":"sym"}'   (Symbol key and value → strings)
SmarterJSON.generate(BigDecimal("65.613616999999977"))         # => '65.613616999999977' (a number, full precision)
SmarterJSON.generate("café\tx")                                # => '"café\tx"'          (control chars escaped, UTF-8 raw)

Strings escape ", \, and the control characters 0x00–0x1F; everything else — including multi-byte UTF-8 — is emitted raw, which is valid JSON.

What raises

generate raises SmarterJSON::Error on input it cannot represent as strict JSON:

SmarterJSON.generate(Time.now)          # raises SmarterJSON::GenerateError — unsupported type
SmarterJSON.generate(Float::INFINITY)   # raises SmarterJSON::GenerateError — non-finite Float
SmarterJSON.generate(Float::NAN)        # raises SmarterJSON::GenerateError — non-finite Float

(GenerateError is a kind of SmarterJSON::Error, so rescue SmarterJSON::Error catches it. Infinity and NaN are accepted on the read side as a leniency; to write them, pass allow_nan: true and they're emitted as NaN / Infinity / -Infinity (JSON5-style, so SmarterJSON reads them back) — otherwise non-finite values raise, since they aren't valid strict JSON.)

By default generate is strict: it only writes the types above and raises on anything else. To serialize Time, Date, or your own objects, pass coerce: true — an unsupported value is then converted by its own as_json (whose result is re-emitted, so escaping/indent/sort_keys still apply) or, failing that, to_json (spliced verbatim):

class Money
  def as_json(*)
    { "cents" => @cents, "currency" => @currency }
  end
end

SmarterJSON.generate({ "price" => Money.new(500, "USD") }, coerce: true)
# => '{"price":{"cents":500,"currency":"USD"}}'

Strict-by-default stays the default precisely so you opt in to delegating serialization rather than silently emitting an object's to_s. See Configuration Options.

Pretty-printing

By default generate produces compact output (no spaces). Pass indent: (a number of spaces per nesting level) to pretty-print:

SmarterJSON.generate({ "a" => 1, "b" => [2, 3] }, indent: 2)
# => "{\n  \"a\": 1,\n  \"b\": [\n    2,\n    3\n  ]\n}"

which prints as:

{
  "a": 1,
  "b": [
    2,
    3
  ]
}

Empty objects and arrays stay inline ({} / []) even when indenting. indent: 0 (the default) is compact output. Pretty-printing is multi-line, so it can't be combined with format: :ndjson (where each record must be a single line) — doing so raises ArgumentError. See Configuration Options.

Safe and canonical output

Three more options shape the output, and they compose with each other and with indent::

  • sort_keys: true — emit object keys in sorted order (Symbol keys sorted by their string form). Handy for canonical, diff-friendly JSON.
  • ascii_only: true — escape every non-ASCII character as \uXXXX (characters above U+FFFF become a UTF-16 surrogate pair). The default emits raw UTF-8.
  • script_safe: true — escape the / in </ and the JavaScript line separators U+2028 / U+2029, so the output is safe to embed directly in an HTML <script> tag without breaking out of it.
SmarterJSON.generate({ "b" => 2, "a" => 1 }, sort_keys: true)   # => '{"a":1,"b":2}'
SmarterJSON.generate("</script>", script_safe: true)            # => '"<\/script>"'

See Configuration Options for the full table.

Writing NDJSON

Pass format: :ndjson to write newline-delimited JSON. An Array writes one element per line; any other value writes as a single line. This is the exact inverse of reading NDJSON back into an Array.

SmarterJSON.generate([{ "id" => 1 }, { "id" => 2 }], format: :ndjson)   # => "{\"id\":1}\n{\"id\":2}\n"
SmarterJSON.generate({ "id" => 1 }, format: :ndjson)                     # => "{\"id\":1}\n"   (single value → one line)
SmarterJSON.generate([], format: :ndjson)                               # => ""              (empty array → no lines)

Note the difference from the default format: :json, where a top-level Array is written as a single JSON array ([…]), not as NDJSON. See Configuration Options for the full list of writer options.

Round-tripping

process_one reads one document back, process reads many (NDJSON) back into an Array — each is the inverse of the matching generate:

obj = { "a" => 1, "b" => [2, "three", nil, true] }
SmarterJSON.process_one(SmarterJSON.generate(obj)) == obj                              # => true

arr = [{ "id" => 1 }, { "id" => 2 }, { "id" => 3 }]
SmarterJSON.process(SmarterJSON.generate(arr, format: :ndjson)) == arr                 # => true

Check out the RSpec tests for more examples.


PREVIOUS: The Basic Read API | NEXT: Configuration Options | UP: README