Module: SmarterJSON
- Defined in:
- lib/smarter_json.rb,
lib/smarter_json/errors.rb,
lib/smarter_json/parser.rb,
lib/smarter_json/version.rb,
lib/smarter_json/generator.rb,
ext/smarter_json/smarter_json.c
Defined Under Namespace
Classes: EncodingError, Error, GenerateError, Generator, ParseError, Parser
Constant Summary collapse
- HAS_ACCELERATION =
respond_to?(:parse_c)
- VERSION =
"0.5.1"
Class Method Summary collapse
-
.generate(obj, options = {}) ⇒ Object
SmarterJSON.generate(obj, options = {}) — write a Ruby value as JSON.
- .parse_c(input, opts) ⇒ Object
-
.process(input, options = {}, &block) ⇒ Object
SmarterJSON.process(input, options = {}) — the main entry point.
-
.process_file(path, options = {}, &block) ⇒ Object
SmarterJSON.process_file(path, options = {}) — open a file and process it.
Class Method Details
.generate(obj, options = {}) ⇒ Object
SmarterJSON.generate(obj, options = {}) — write a Ruby value as JSON.
:json (default) — standard JSON. Hash -> object, Array -> array,
scalar -> scalar. Always valid, interoperable JSON.
:ndjson — newline-delimited JSON. An Array writes one element per
line; any other value writes as a single line. The
inverse of process reading NDJSON back into an Array.
Symbol keys/values are emitted as strings; BigDecimal as a JSON number. Unsupported types (Time, custom objects) and non-finite Floats raise SmarterJSON::Error. Returns a String.
20 21 22 |
# File 'lib/smarter_json/generator.rb', line 20 def generate(obj, = {}) Generator.new().generate(obj) end |
.parse_c(input, opts) ⇒ Object
1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 |
# File 'ext/smarter_json/smarter_json.c', line 1336
static VALUE fj_parse_c(VALUE self, VALUE input, VALUE opts) {
fj_state st;
VALUE value, enc_opt, dk;
Check_Type(input, T_STRING);
enc_opt = rb_hash_aref(opts, ID2SYM(rb_intern("encoding")));
if (!NIL_P(enc_opt)) {
input = rb_funcall(rb_str_dup(input), rb_intern("force_encoding"), 1, enc_opt);
}
if (!RTEST(rb_funcall(input, rb_intern("valid_encoding?"), 0))) {
VALUE name = rb_funcall(rb_funcall(input, rb_intern("encoding"), 0), rb_intern("name"), 0);
VALUE msg = rb_sprintf("invalid byte sequence for %" PRIsVALUE, name);
rb_exc_raise(rb_funcall(cEncodingError, rb_intern("new"), 3, msg, Qnil, Qnil));
}
st.buf = RSTRING_PTR(input);
st.len = RSTRING_LEN(input);
st.pos = 0;
st.enc = rb_enc_get(input);
st.depth = 0;
#ifdef HAVE_RB_ENC_INTERNED_STR
fj_kc_slot kcache[FJ_KCACHE_SIZE];
memset(kcache, 0, sizeof(kcache));
st.kcache = kcache;
#else
st.kcache = NULL;
#endif
st.symbolize_keys = RTEST(rb_hash_aref(opts, ID2SYM(rb_intern("symbolize_keys"))));
dk = rb_hash_aref(opts, ID2SYM(rb_intern("duplicate_key")));
st.dup_first_wins = (dk == ID2SYM(rb_intern("first_wins")));
st.dup_raise = (dk == ID2SYM(rb_intern("raise")));
{
VALUE bd = rb_hash_aref(opts, ID2SYM(rb_intern("bigdecimal_load")));
if (bd == ID2SYM(rb_intern("float"))) st.bigdecimal_load = 0;
else if (bd == ID2SYM(rb_intern("bigdecimal"))) st.bigdecimal_load = 2;
else st.bigdecimal_load = 1; /* :auto (default), including nil */
}
if (st.len >= 3 && (unsigned char)st.buf[0] == 0xEF &&
(unsigned char)st.buf[1] == 0xBB && (unsigned char)st.buf[2] == 0xBF) {
st.pos = 3;
}
/* With a block: yield each top-level value until EOF (JSONL / NDJSON /
* concatenated). Same loop as the Ruby each_value path, on the C parser. */
if (rb_block_given_p()) {
for (;;) {
fj_skip_ws_comments(&st);
if (fj_eof(&st)) break;
rb_yield(fj_parse_iter(&st, fj_implicit_root_ahead(&st)));
}
return Qnil;
}
/* No block: auto-detect the document count for free — it is the same "is there
* trailing content after the first value?" check that used to raise. 0 documents
* -> nil; 1 document -> the value itself (single-document hot path, no Array
* allocated); 2+ documents (NDJSON / JSONL / concatenated / whitespace-separated)
* -> an Array of every top-level value. Commas do NOT separate documents (only
* whitespace / newline / concatenation do), so a bracketless comma list still
* raises in fj_parse_iter — the unsupported implicit-root array. */
fj_skip_ws_comments(&st);
if (fj_eof(&st)) return Qnil;
value = fj_parse_iter(&st, fj_implicit_root_ahead(&st));
fj_skip_ws_comments(&st);
if (fj_eof(&st)) return value;
{
VALUE arr = rb_ary_new();
rb_ary_push(arr, value);
do {
rb_ary_push(arr, fj_parse_iter(&st, fj_implicit_root_ahead(&st)));
fj_skip_ws_comments(&st);
} while (!fj_eof(&st));
return arr;
}
}
|
.process(input, options = {}, &block) ⇒ Object
SmarterJSON.process(input, options = {}) — the main entry point.
‘input` is either a String of JSON content or an IO to read from. (A String is always content, never a filename — use process_file for paths.) The values in `options` override Parser::DEFAULT_OPTIONS.
Without a block: returns nil (zero documents), the value (one document), or an Array of the values (two or more — NDJSON / JSONL / concatenated / whitespace- separated). :acceleration (default true) selects the C extension when compiled and loaded (SmarterJSON::HAS_ACCELERATION); otherwise the pure-Ruby parser.
With a block: yields each top-level document as it is parsed, and returns nil. For an IO this streams document-by-document in bounded memory — it reads the stream as newline-delimited documents (NDJSON / JSONL), one per line.
23 24 25 26 27 28 29 30 31 |
# File 'lib/smarter_json/parser.rb', line 23 def process(input, = {}, &block) if input.is_a?(String) process_content(input, , &block) elsif input.respond_to?(:read) block ? stream_io(input, , &block) : process_content(input.read, ) else raise ArgumentError, "SmarterJSON.process expects a String or an IO, got #{input.class}" end end |
.process_file(path, options = {}, &block) ⇒ Object
SmarterJSON.process_file(path, options = {}) — open a file and process it.
The :encoding option labels the file’s encoding (default “UTF-8”); it does NOT trigger a transcoding pass — the parser works on the bytes in their native encoding and emits string values with the same encoding tag. With a block, streams document-by-document straight from disk in bounded memory (never loading the whole file); the documents are read as newline-delimited (NDJSON / JSONL), one per line.
41 42 43 44 45 46 47 48 |
# File 'lib/smarter_json/parser.rb', line 41 def process_file(path, = {}, &block) encoding = .fetch(:encoding, "UTF-8") if block File.open(path, "r:#{encoding}") { |io| stream_io(io, , &block) } else process_content(File.read(path, encoding: encoding), ) end end |