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.
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.)
| 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.
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.
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.
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.
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.
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 # => trueCheck out the RSpec tests for more examples.
PREVIOUS: The Basic Read API | NEXT: Configuration Options | UP: README