oj 3.16.16 → 3.16.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07c7a6ebc6baf4f02b93760ed3d7440cdc9b2af85d0976e525f34978ab823c45
4
- data.tar.gz: ed5a5a2e16f8df836a098de0da60657688049cfcf820d3142b4122302ae4e946
3
+ metadata.gz: 8cea6645d77bacff66ea8a69d5c5211aa2e9366decc1e8dec909cc83f08933af
4
+ data.tar.gz: d286d46148ef014003a9a23a87cf33da532e09a6ce0228f4e5b47eff963d985f
5
5
  SHA512:
6
- metadata.gz: d8f5c73fbc30a2985c2352e466f8ef9762f831d15b9c0a8b9394d66a5613910fd5aca4c4bdc49c40230128d791244ad67f6f9852c4778e89828a4358bb997c11
7
- data.tar.gz: dbc5fe50d1ad89cb23918c950ac849052a95defb7e5509d7653236eb3569c811685e2c4c6546a8ab38c5a346aad0ccdd9770c855bc53f96c0c5312c7d61fab3e
6
+ metadata.gz: a66845146062e764671ed0556076590ecc6507f04e21613ed5514df9e19e35c5e48b2fe9095cd7376fd82376a9b055aa8fc9e17e74c2bfaaa80ba1cebef0ddfe
7
+ data.tar.gz: 8b4af7c9cc6fadf664ff643c081783921e05f793d03c512bcf178e47173fc0e620f851382cb423c99769f01fd6080ad376c663a781faa14249cfc66009a182d2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.16.17 - 2026-04-12
4
+
5
+ - Rails optimize for Hash and Array now overrides `as_json` for those
6
+ classes. Note that when either is optimized with `Oj.optimize_rails`
7
+ the `Array.as_json` and `Hash.as_json` will not be called.
8
+
9
+ - Add support for the rails encoder `:only` and `:except` options.
10
+
11
+ - Handle unterminated strings in usual parser (#1002)
12
+
13
+ - Fix read() not handling partial reads for large files (#1004)
14
+
15
+ - Raise error for incomplete primitive literals (#1005)
16
+
3
17
  ## 3.16.16 - 2026-03-13
4
18
 
5
19
  - Not closed arrays and objects are reported corrected in the usual parser.
data/ext/oj/custom.c CHANGED
@@ -287,6 +287,11 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
287
287
  if (out->omit_nil && Qnil == value) {
288
288
  return ST_CONTINUE;
289
289
  }
290
+ if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
291
+ if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
292
+ return ST_CONTINUE;
293
+ }
294
+ }
290
295
  if (!out->opts->dump_opts.use) {
291
296
  assure_size(out, depth * out->indent + 1);
292
297
  fill_indent(out, depth);
data/ext/oj/dump.c CHANGED
@@ -257,7 +257,30 @@ inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) {
257
257
  size += tmp;
258
258
  }
259
259
 
260
+ #ifdef HAVE_FAST_MEMCPY
261
+ size_t total = 0;
262
+ if (len - i > 4) {
263
+ size += (len - i);
264
+ unsigned char buf[sizeof(uint8x16_t)];
265
+ memset(buf, ' ', sizeof(buf));
266
+ fast_memcpy16(buf, str, len - i);
267
+
268
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)buf);
269
+ uint8x16_t tmp1 = vqtbl4q_u8(hibit_friendly_chars_neon[0], chunk);
270
+ uint8x16_t tmp2 = vqtbl4q_u8(hibit_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
271
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
272
+ uint8_t tmp = vaddvq_u8(result);
273
+
274
+ size += tmp;
275
+
276
+ total = size;
277
+ } else {
278
+ total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
279
+ }
280
+ #else
260
281
  size_t total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
282
+ #endif
283
+
261
284
  return total;
262
285
  #elif defined(HAVE_SIMD_SSE4_2)
263
286
  if (SIMD_Impl == SIMD_SSE42) {
@@ -967,11 +990,12 @@ typedef struct _neon_match_result {
967
990
  uint8x16_t needs_escape;
968
991
  bool has_some_hibit;
969
992
  bool do_unicode_validation;
993
+ uint64_t escape_mask;
970
994
  } neon_match_result;
971
995
 
972
996
  static inline FORCE_INLINE neon_match_result
973
997
  neon_update(const char *str, uint8x16x4_t *cmap_neon, int neon_table_size, bool do_unicode_validation, bool has_hi) {
974
- neon_match_result result = {.has_some_hibit = false, .do_unicode_validation = false};
998
+ neon_match_result result = {.has_some_hibit = false, .do_unicode_validation = false, .escape_mask = 0};
975
999
 
976
1000
  uint8x16_t chunk = vld1q_u8((const unsigned char *)str);
977
1001
  uint8x16_t tmp1 = vqtbl4q_u8(cmap_neon[0], chunk);
@@ -987,6 +1011,9 @@ neon_update(const char *str, uint8x16x4_t *cmap_neon, int neon_table_size, bool
987
1011
  result.has_some_hibit = vmaxvq_u8(has_some_hibit) != 0;
988
1012
  result.do_unicode_validation = has_hi && do_unicode_validation && result.has_some_hibit;
989
1013
  }
1014
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(vmvnq_u8(vceqq_u8(result.needs_escape, vdupq_n_u8(0)))), 4);
1015
+ const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
1016
+ result.escape_mask = mask & 0x8888888888888888ull;
990
1017
  return result;
991
1018
  }
992
1019
 
@@ -1189,7 +1216,16 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1189
1216
  if (is_sym) {
1190
1217
  *out->cur++ = ':';
1191
1218
  }
1219
+ #ifdef HAVE_FAST_MEMCPY
1220
+ if (cnt <= 16) {
1221
+ fast_memcpy16(out->cur, str, cnt);
1222
+ out->cur += size;
1223
+ } else {
1224
+ APPEND_CHARS(out->cur, str, cnt);
1225
+ }
1226
+ #else
1192
1227
  APPEND_CHARS(out->cur, str, cnt);
1228
+ #endif
1193
1229
  *out->cur++ = '"';
1194
1230
  } else {
1195
1231
  const char *end = str + cnt;
@@ -1223,6 +1259,17 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1223
1259
  #endif
1224
1260
 
1225
1261
  #ifdef HAVE_SIMD_NEON
1262
+
1263
+ #ifdef HAVE_FAST_MEMCPY
1264
+ #define APPEND_CHARS_SMALL(dst, src, length) \
1265
+ fast_memcpy16((dst), (src), (length)); \
1266
+ (dst) += (length);
1267
+ #define MEMCPY16 fast_memcpy16
1268
+ #else
1269
+ #define APPEND_CHARS_SMALL(dst, src, length) APPEND_CHARS(dst, str, length);
1270
+ #define MEMCPY16 memcpy
1271
+ #endif
1272
+
1226
1273
  if (use_simd) {
1227
1274
  while (str < end) {
1228
1275
  const char *chunk_ptr = NULL;
@@ -1231,8 +1278,8 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1231
1278
  chunk_start = str;
1232
1279
  chunk_end = str + sizeof(uint8x16_t);
1233
1280
  } else if ((end - str) >= SIMD_MINIMUM_THRESHOLD) {
1234
- memset(out->cur, 'A', sizeof(uint8x16_t));
1235
- memcpy(out->cur, str, (end - str));
1281
+ memset(out->cur, ' ', sizeof(uint8x16_t));
1282
+ MEMCPY16(out->cur, str, (end - str));
1236
1283
  chunk_ptr = out->cur;
1237
1284
  chunk_start = str;
1238
1285
  chunk_end = end;
@@ -1244,32 +1291,22 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1244
1291
  neon_table_size,
1245
1292
  do_unicode_validation,
1246
1293
  has_hi);
1247
- if ((result.do_unicode_validation) || vmaxvq_u8(result.needs_escape) != 0) {
1294
+ if ((result.do_unicode_validation) || result.escape_mask != 0) {
1248
1295
  SEARCH_FLUSH;
1249
- uint8x16_t actions = vaddq_u8(result.needs_escape, vdupq_n_u8('1'));
1250
- uint8_t num_matches = vaddvq_u8(vandq_u8(result.needs_escape, vdupq_n_u8(0x1)));
1296
+ bool process_each = result.do_unicode_validation;
1297
+ uint8x16_t actions = vaddq_u8(result.needs_escape, vdupq_n_u8('1'));
1251
1298
  vst1q_u8((unsigned char *)matches, actions);
1252
- bool process_each = result.do_unicode_validation || (num_matches > sizeof(uint8x16_t) / 2);
1253
1299
  // If no byte in this chunk had the high bit set then we can skip
1254
1300
  // all of the '1' bytes by directly copying them to the output.
1255
1301
  if (!process_each) {
1256
- while (str < chunk_end) {
1257
- long i = str - chunk_start;
1258
- char action;
1259
- while (str < chunk_end && (action = matches[i++]) == '1') {
1260
- *out->cur++ = *str++;
1302
+ while (result.escape_mask) {
1303
+ int esc_pos = OJ_CTZ64(result.escape_mask) >> 2;
1304
+ long run_len = esc_pos - (str - chunk_start);
1305
+ if (run_len > 0) {
1306
+ APPEND_CHARS_SMALL(out->cur, str, run_len);
1307
+ str += run_len;
1261
1308
  }
1262
- cursor = str;
1263
- if (str >= chunk_end) {
1264
- break;
1265
- }
1266
- str = process_character(action, str, end, out, orig, do_unicode_validation, &check_start);
1267
- str++;
1268
- }
1269
- } else {
1270
- while (str < chunk_end) {
1271
- long match_index = str - chunk_start;
1272
- str = process_character(matches[match_index],
1309
+ str = process_character(matches[esc_pos],
1273
1310
  str,
1274
1311
  end,
1275
1312
  out,
@@ -1277,6 +1314,23 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1277
1314
  do_unicode_validation,
1278
1315
  &check_start);
1279
1316
  str++;
1317
+ result.escape_mask &= result.escape_mask - 1;
1318
+ }
1319
+ if (str < chunk_end) {
1320
+ APPEND_CHARS_SMALL(out->cur, str, chunk_end - str);
1321
+ str = chunk_end;
1322
+ }
1323
+ } else {
1324
+ while (str < chunk_end) {
1325
+ long match_index = str - chunk_start;
1326
+ str = process_character(matches[match_index],
1327
+ str,
1328
+ end,
1329
+ out,
1330
+ orig,
1331
+ do_unicode_validation,
1332
+ &check_start);
1333
+ str++;
1280
1334
  }
1281
1335
  }
1282
1336
  cursor = str;
@@ -1317,12 +1371,12 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1317
1371
  while (str < chunk_end) {
1318
1372
  long match_index = str - chunk_start;
1319
1373
  str = process_character(matches[match_index],
1320
- str,
1321
- end,
1322
- out,
1323
- orig,
1324
- do_unicode_validation,
1325
- &check_start);
1374
+ str,
1375
+ end,
1376
+ out,
1377
+ orig,
1378
+ do_unicode_validation,
1379
+ &check_start);
1326
1380
  str++;
1327
1381
  }
1328
1382
  cursor = str;
@@ -1687,3 +1741,25 @@ size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const c
1687
1741
  }
1688
1742
  return cnt;
1689
1743
  }
1744
+
1745
+ bool oj_key_skip(VALUE key, const char *only, const char *except) {
1746
+ const char *skey;
1747
+
1748
+ switch (rb_type(key)) {
1749
+ case RUBY_T_STRING: skey = StringValueCStr(key); break;
1750
+ case RUBY_T_SYMBOL: skey = rb_id2name(rb_sym2id(key)); break;
1751
+ default: skey = NULL; break;
1752
+ }
1753
+ if (NULL != skey && '\0' != *skey) {
1754
+ size_t size = strlen(skey);
1755
+ char *buf = alloca(size + 3);
1756
+
1757
+ buf[0] = ':';
1758
+ strcpy(buf + 1, skey);
1759
+ buf[size + 1] = ':';
1760
+ buf[size + 2] = '\0';
1761
+
1762
+ return ((NULL != only && NULL == strstr(only, buf)) || (NULL != except && NULL != strstr(except, buf)));
1763
+ }
1764
+ return NULL != only;
1765
+ }
data/ext/oj/dump.h CHANGED
@@ -62,6 +62,8 @@ extern size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d,
62
62
 
63
63
  extern time_t oj_sec_from_time_hard_way(VALUE obj);
64
64
 
65
+ extern bool oj_key_skip(VALUE key, const char *only, const char *except);
66
+
65
67
  inline static void assure_size(Out out, size_t len) {
66
68
  if (out->end - out->cur <= (long)len) {
67
69
  oj_grow_out(out, len);
data/ext/oj/dump_compat.c CHANGED
@@ -605,6 +605,11 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
605
605
  if (out->omit_nil && Qnil == value) {
606
606
  return ST_CONTINUE;
607
607
  }
608
+ if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
609
+ if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
610
+ return ST_CONTINUE;
611
+ }
612
+ }
608
613
  if (!out->opts->dump_opts.use) {
609
614
  assure_size(out, depth * out->indent + 1);
610
615
  fill_indent(out, depth);
data/ext/oj/dump_object.c CHANGED
@@ -210,6 +210,11 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
210
210
  if (out->omit_nil && Qnil == value) {
211
211
  return ST_CONTINUE;
212
212
  }
213
+ if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
214
+ if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
215
+ return ST_CONTINUE;
216
+ }
217
+ }
213
218
  assure_size(out, size);
214
219
  fill_indent(out, depth);
215
220
  switch (rb_type(key)) {
data/ext/oj/dump_strict.c CHANGED
@@ -193,14 +193,19 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
193
193
  if (out->omit_nil && Qnil == value) {
194
194
  return ST_CONTINUE;
195
195
  }
196
+ if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
197
+ if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
198
+ return ST_CONTINUE;
199
+ }
200
+ }
196
201
  if (!out->opts->dump_opts.use) {
197
202
  size = depth * out->indent + 1;
198
203
  assure_size(out, size);
199
204
  fill_indent(out, depth);
200
- if (rtype == T_STRING) {
201
- oj_dump_str(key, 0, out, false);
202
- } else {
203
- oj_dump_sym(key, 0, out, false);
205
+ switch (rtype) {
206
+ case T_STRING: oj_dump_str(key, 0, out, false); break;
207
+ case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
208
+ default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
204
209
  }
205
210
  *out->cur++ = ':';
206
211
  } else {
@@ -215,10 +220,10 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
215
220
  APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
216
221
  }
217
222
  }
218
- if (rtype == T_STRING) {
219
- oj_dump_str(key, 0, out, false);
220
- } else {
221
- oj_dump_sym(key, 0, out, false);
223
+ switch (rtype) {
224
+ case T_STRING: oj_dump_str(key, 0, out, false); break;
225
+ case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
226
+ default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
222
227
  }
223
228
  size = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
224
229
  assure_size(out, size);
data/ext/oj/mimic_json.c CHANGED
@@ -735,6 +735,8 @@ static struct _options mimic_object_to_json_options = {0, // indent
735
735
  false, // omit_nil
736
736
  false, // omit_null_byte
737
737
  100, // max_depth
738
+ NULL, // only
739
+ NULL, // except
738
740
  },
739
741
  {
740
742
  // str_rx
data/ext/oj/oj.c CHANGED
@@ -120,6 +120,7 @@ static VALUE create_id_sym;
120
120
  static VALUE custom_sym;
121
121
  static VALUE empty_string_sym;
122
122
  static VALUE escape_mode_sym;
123
+ static VALUE except_sym;
123
124
  static VALUE integer_range_sym;
124
125
  static VALUE fast_sym;
125
126
  static VALUE float_prec_sym;
@@ -138,6 +139,7 @@ static VALUE null_sym;
138
139
  static VALUE object_sym;
139
140
  static VALUE omit_null_byte_sym;
140
141
  static VALUE omit_nil_sym;
142
+ static VALUE only_sym;
141
143
  static VALUE rails_sym;
142
144
  static VALUE raise_sym;
143
145
  static VALUE ruby_sym;
@@ -228,6 +230,8 @@ struct _options oj_default_options = {
228
230
  false, // omit_nil
229
231
  false, // omit_null_byte
230
232
  MAX_DEPTH, // max_depth
233
+ NULL, // only
234
+ NULL, // except
231
235
  },
232
236
  {
233
237
  // str_rx
@@ -238,6 +242,22 @@ struct _options oj_default_options = {
238
242
  NULL,
239
243
  };
240
244
 
245
+ static VALUE only_array_from_string(const char *str) {
246
+ volatile VALUE a = Qnil;
247
+
248
+ if (NULL != str && 2 < strlen(str)) {
249
+ str++;
250
+ char *cp;
251
+
252
+ a = rb_ary_new();
253
+ while (NULL != (cp = strchr(str, ':'))) {
254
+ rb_ary_push(a, rb_id2sym(rb_intern2(str, cp - str)));
255
+ str = cp + 1;
256
+ }
257
+ }
258
+ return a;
259
+ }
260
+
241
261
  /* Document-method: default_options()
242
262
  * call-seq: default_options()
243
263
  *
@@ -318,6 +338,8 @@ struct _options oj_default_options = {
318
338
  * (trace is off)
319
339
  * - *:safe* [_true,_|_false_] Safe mimic breaks JSON mimic to be safer, default
320
340
  * is false (safe is off)
341
+ * - *:only* [_nil,_|_Array_] A list of the fields to encode. All others are skipped.
342
+ * - *:except* [_nil,_|_Array_] A list of the fields to not encode. All others are encoded.
321
343
  *
322
344
  * Return [_Hash_] all current option settings.
323
345
  */
@@ -483,6 +505,9 @@ static VALUE get_def_opts(VALUE self) {
483
505
  rb_hash_aset(opts, oj_hash_class_sym, oj_default_options.hash_class);
484
506
  rb_hash_aset(opts, oj_array_class_sym, oj_default_options.array_class);
485
507
 
508
+ rb_hash_aset(opts, only_sym, only_array_from_string(oj_default_options.dump_opts.only));
509
+ rb_hash_aset(opts, except_sym, only_array_from_string(oj_default_options.dump_opts.except));
510
+
486
511
  if (NULL == oj_default_options.ignore) {
487
512
  rb_hash_aset(opts, ignore_sym, Qnil);
488
513
  } else {
@@ -625,6 +650,95 @@ bool set_yesno_options(VALUE key, VALUE value, Options copts) {
625
650
  return false;
626
651
  }
627
652
 
653
+ static const char *make_only_value(VALUE v) {
654
+ switch (rb_type(v)) {
655
+ case RUBY_T_NIL:
656
+ case RUBY_T_NONE: return NULL;
657
+ case RUBY_T_ARRAY: {
658
+ long len = rb_array_len(v);
659
+ long i;
660
+ long size = 0;
661
+ char *buf;
662
+ char *bp;
663
+
664
+ for (i = 0; i < len; i++) {
665
+ VALUE x = rb_ary_entry(v, i);
666
+
667
+ switch (rb_type(x)) {
668
+ case RUBY_T_SYMBOL:
669
+ size += strlen(rb_id2name(rb_sym2id(x)));
670
+ size++;
671
+ break;
672
+ case RUBY_T_STRING:
673
+ size += strlen(StringValueCStr(x));
674
+ size++;
675
+ break;
676
+ default: rb_raise(rb_eArgError, ":only and :except must be nil, symbol, string, or array."); break;
677
+ }
678
+ }
679
+ if (0 == size) {
680
+ return NULL;
681
+ }
682
+ buf = OJ_R_ALLOC_N(char, size + 2);
683
+ bp = buf;
684
+ *bp++ = ':';
685
+ for (i = 0; i < len; i++) {
686
+ VALUE x = rb_ary_entry(v, i);
687
+ const char *str;
688
+
689
+ switch (rb_type(x)) {
690
+ case RUBY_T_SYMBOL:
691
+ str = rb_id2name(rb_sym2id(x));
692
+ size = strlen(str);
693
+ memcpy(bp, str, size);
694
+ bp += size;
695
+ *bp++ = ':';
696
+ break;
697
+ case RUBY_T_STRING:
698
+ str = StringValueCStr(x);
699
+ size = strlen(str);
700
+ memcpy(bp, str, size);
701
+ bp += size;
702
+ *bp++ = ':';
703
+ break;
704
+ default:
705
+ // ignore
706
+ break;
707
+ }
708
+ }
709
+ *bp = '\0';
710
+
711
+ return buf;
712
+ }
713
+ case RUBY_T_STRING: {
714
+ const char *str = StringValueCStr(v);
715
+ size_t size = strlen(str);
716
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
717
+
718
+ buf[0] = ':';
719
+ strcpy(buf + 1, str);
720
+ buf[size + 1] = ':';
721
+ buf[size + 2] = '\0';
722
+
723
+ return buf;
724
+ }
725
+ case RUBY_T_SYMBOL: {
726
+ const char *str = rb_id2name(rb_sym2id(v));
727
+ size_t size = strlen(str);
728
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
729
+
730
+ buf[0] = ':';
731
+ strcpy(buf + 1, str);
732
+ buf[size + 1] = ':';
733
+ buf[size + 2] = '\0';
734
+
735
+ return buf;
736
+ }
737
+ default: rb_raise(rb_eArgError, ":only and zzz :except must be nil, symbol, string, or array."); break;
738
+ }
739
+ return NULL;
740
+ }
741
+
628
742
  static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
629
743
  Options copts = (Options)opts;
630
744
  size_t len;
@@ -981,6 +1095,18 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
981
1095
  }
982
1096
  strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
983
1097
  copts->float_fmt[RSTRING_LEN(v)] = '\0';
1098
+ } else if (only_sym == k) {
1099
+ if (NULL != copts->dump_opts.only) {
1100
+ OJ_R_FREE((void *)copts->dump_opts.only);
1101
+ copts->dump_opts.only = NULL;
1102
+ }
1103
+ copts->dump_opts.only = make_only_value(v);
1104
+ } else if (except_sym == k) {
1105
+ if (NULL != copts->dump_opts.except) {
1106
+ OJ_R_FREE((void *)copts->dump_opts.except);
1107
+ copts->dump_opts.except = NULL;
1108
+ }
1109
+ copts->dump_opts.except = make_only_value(v);
984
1110
  }
985
1111
  return ST_CONTINUE;
986
1112
  }
@@ -2133,6 +2259,10 @@ void Init_oj(void) {
2133
2259
  rb_gc_register_address(&xmlschema_sym);
2134
2260
  xss_safe_sym = ID2SYM(rb_intern("xss_safe"));
2135
2261
  rb_gc_register_address(&xss_safe_sym);
2262
+ only_sym = ID2SYM(rb_intern("only"));
2263
+ rb_gc_register_address(&only_sym);
2264
+ except_sym = ID2SYM(rb_intern("except"));
2265
+ rb_gc_register_address(&except_sym);
2136
2266
 
2137
2267
  oj_slash_string = rb_str_new2("/");
2138
2268
  rb_gc_register_address(&oj_slash_string);
data/ext/oj/oj.h CHANGED
@@ -104,21 +104,23 @@ typedef enum {
104
104
  } StreamWriterType;
105
105
 
106
106
  typedef struct _dumpOpts {
107
- bool use;
108
- char indent_str[16];
109
- char before_sep[16];
110
- char after_sep[16];
111
- char hash_nl[16];
112
- char array_nl[16];
113
- uint8_t indent_size;
114
- uint8_t before_size;
115
- uint8_t after_size;
116
- uint8_t hash_size;
117
- uint8_t array_size;
118
- char nan_dump; // NanDump
119
- bool omit_nil;
120
- bool omit_null_byte;
121
- int max_depth;
107
+ bool use;
108
+ char indent_str[16];
109
+ char before_sep[16];
110
+ char after_sep[16];
111
+ char hash_nl[16];
112
+ char array_nl[16];
113
+ uint8_t indent_size;
114
+ uint8_t before_size;
115
+ uint8_t after_size;
116
+ uint8_t hash_size;
117
+ uint8_t array_size;
118
+ char nan_dump; // NanDump
119
+ bool omit_nil;
120
+ bool omit_null_byte;
121
+ int max_depth;
122
+ const char *only;
123
+ const char *except;
122
124
  } *DumpOpts;
123
125
 
124
126
  typedef struct _options {
@@ -143,7 +145,7 @@ typedef struct _options {
143
145
  char quirks_mode; // allow single JSON values instead of documents
144
146
  char allow_invalid; // YesNo - allow invalid unicode
145
147
  char create_ok; // YesNo allow create_id
146
- char allow_nan; // YEsyNo for parsing only
148
+ char allow_nan; // YesNo for parsing only
147
149
  char trace; // YesNo
148
150
  char safe; // YesNo
149
151
  char sec_prec_set; // boolean (0 or 1)
data/ext/oj/parse.c CHANGED
@@ -209,7 +209,7 @@ static inline const char *string_scan_neon(const char *str, const char *end) {
209
209
  while (str + sizeof(uint8x16_t) <= end) {
210
210
  uint8x16_t chunk = vld1q_u8((const uint8_t *)str);
211
211
  uint8x16_t tmp = vorrq_u8(vorrq_u8(vceqq_u8(chunk, null_char), vceqq_u8(chunk, backslash)),
212
- vceqq_u8(chunk, quote));
212
+ vceqq_u8(chunk, quote));
213
213
  const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(tmp), 4);
214
214
  uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
215
215
  if (mask != 0) {
@@ -285,10 +285,10 @@ static OJ_TARGET_SSE42 const char *scan_string_SSE42(const char *str, const char
285
285
  for (; str <= safe_end_16; str += 16) {
286
286
  const __m128i string = _mm_loadu_si128((const __m128i *)str);
287
287
  const int r = _mm_cmpestri(terminate,
288
- 3,
289
- string,
290
- 16,
291
- _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT);
288
+ 3,
289
+ string,
290
+ 16,
291
+ _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT);
292
292
  if (r != 16)
293
293
  return str + r;
294
294
  }
@@ -611,7 +611,7 @@ static void read_num(ParseInfo pi) {
611
611
  ni.bigdec_load = pi->options.compat_bigdec;
612
612
  } else {
613
613
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
614
- RubyDec == pi->options.bigdec_load);
614
+ RubyDec == pi->options.bigdec_load);
615
615
  ni.bigdec_load = pi->options.bigdec_load;
616
616
  }
617
617
 
@@ -1193,11 +1193,19 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len, int yie
1193
1193
  buf = OJ_R_ALLOC_N(char, len + 1);
1194
1194
  pi->json = buf;
1195
1195
  pi->end = buf + len;
1196
- if (0 >= (cnt = read(fd, (char *)pi->json, len)) || cnt != (ssize_t)len) {
1197
- if (0 != buf) {
1198
- OJ_R_FREE(buf);
1196
+ {
1197
+ size_t total = 0;
1198
+
1199
+ while (total < len) {
1200
+ cnt = read(fd, (char *)pi->json + total, len - total);
1201
+ if (cnt <= 0) {
1202
+ if (0 != buf) {
1203
+ OJ_R_FREE(buf);
1204
+ }
1205
+ rb_raise(rb_eIOError, "failed to read from IO Object.");
1206
+ }
1207
+ total += cnt;
1199
1208
  }
1200
- rb_raise(rb_eIOError, "failed to read from IO Object.");
1201
1209
  }
1202
1210
  ((char *)pi->json)[len] = '\0';
1203
1211
  /* skip UTF-8 BOM if present */
data/ext/oj/parser.c CHANGED
@@ -635,6 +635,9 @@ static void parse(ojParser p, const byte *json) {
635
635
  if ('"' == *b) {
636
636
  p->map = colon_map;
637
637
  break;
638
+ } else if ('\0' == *b) {
639
+ parse_error(p, "quoted string not terminated");
640
+ break;
638
641
  }
639
642
  b--;
640
643
  p->map = string_map;
@@ -659,6 +662,9 @@ static void parse(ojParser p, const byte *json) {
659
662
  p->funcs[p->stack[p->depth]].add_str(p);
660
663
  p->map = (0 == p->depth) ? value_map : after_map;
661
664
  break;
665
+ } else if ('\0' == *b) {
666
+ parse_error(p, "quoted string not terminated");
667
+ break;
662
668
  }
663
669
  b--;
664
670
  p->map = string_map;
@@ -1354,6 +1360,35 @@ static VALUE parser_missing(int argc, VALUE *argv, VALUE self) {
1354
1360
  return p->option(p, key, rv);
1355
1361
  }
1356
1362
 
1363
+ static void validate_primitives_are_complete(ojParser p) {
1364
+ if (0 >= p->ri) {
1365
+ return;
1366
+ }
1367
+
1368
+ switch (p->map[256]) {
1369
+ case 'N': parse_error(p, "expected null"); break;
1370
+ case 'F': parse_error(p, "expected false"); break;
1371
+ case 'T': parse_error(p, "expected true"); break;
1372
+ }
1373
+ }
1374
+
1375
+ static void validate_non_primitives_are_complete(ojParser p) {
1376
+ if (0 >= p->depth) {
1377
+ return;
1378
+ }
1379
+
1380
+ if (OBJECT_FUN == p->stack[p->depth]) {
1381
+ parse_error(p, "Object is not closed");
1382
+ } else {
1383
+ parse_error(p, "Array is not closed");
1384
+ }
1385
+ }
1386
+
1387
+ static void validate_document_end(ojParser p) {
1388
+ validate_primitives_are_complete(p);
1389
+ validate_non_primitives_are_complete(p);
1390
+ }
1391
+
1357
1392
  /* Document-method: parse(json)
1358
1393
  * call-seq: parse(json)
1359
1394
  *
@@ -1371,13 +1406,8 @@ static VALUE parser_parse(VALUE self, VALUE json) {
1371
1406
  p->start(p);
1372
1407
  parse(p, ptr);
1373
1408
 
1374
- if (0 < p->depth) {
1375
- if (OBJECT_FUN == p->stack[p->depth]) {
1376
- parse_error(p, "Object is not closed");
1377
- } else {
1378
- parse_error(p, "Array is not closed");
1379
- }
1380
- }
1409
+ validate_document_end(p);
1410
+
1381
1411
  return p->result(p);
1382
1412
  }
1383
1413
 
data/ext/oj/rails.c CHANGED
@@ -920,7 +920,7 @@ static VALUE encode(VALUE obj, ROptTable ropts, Options opts, int argc, VALUE *a
920
920
  if (Yes == copts.circular) {
921
921
  oj_cache8_new(&out.circ_cache);
922
922
  }
923
- // dump_rails_val(*argv, 0, &out, true);
923
+
924
924
  rb_protect(protect_dump, (VALUE)&oo, &line);
925
925
 
926
926
  if (0 == line) {
@@ -1219,8 +1219,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
1219
1219
  return;
1220
1220
  }
1221
1221
  }
1222
- // if (!oj_rails_array_opt && as_ok && 0 < out->argc && rb_respond_to(a, oj_as_json_id)) {
1223
- if (as_ok && 0 < out->argc && rb_respond_to(a, oj_as_json_id)) {
1222
+ if (!oj_rails_array_opt && as_ok && rb_respond_to(a, oj_as_json_id)) {
1224
1223
  dump_as_json(a, depth, out, false);
1225
1224
  return;
1226
1225
  }
@@ -1287,18 +1286,19 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
1287
1286
  if (out->omit_nil && Qnil == value) {
1288
1287
  return ST_CONTINUE;
1289
1288
  }
1290
- if (rtype != T_STRING && rtype != T_SYMBOL) {
1291
- key = oj_safe_string_convert(key);
1292
- rtype = rb_type(key);
1289
+ if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
1290
+ if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
1291
+ return ST_CONTINUE;
1292
+ }
1293
1293
  }
1294
1294
  if (!out->opts->dump_opts.use) {
1295
1295
  size = depth * out->indent + 1;
1296
1296
  assure_size(out, size);
1297
1297
  fill_indent(out, depth);
1298
- if (rtype == T_STRING) {
1299
- oj_dump_str(key, 0, out, false);
1300
- } else {
1301
- oj_dump_sym(key, 0, out, false);
1298
+ switch (rtype) {
1299
+ case T_STRING: oj_dump_str(key, 0, out, false); break;
1300
+ case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
1301
+ default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
1302
1302
  }
1303
1303
  *out->cur++ = ':';
1304
1304
  } else {
@@ -1313,10 +1313,10 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
1313
1313
  APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
1314
1314
  }
1315
1315
  }
1316
- if (rtype == T_STRING) {
1317
- oj_dump_str(key, 0, out, false);
1318
- } else {
1319
- oj_dump_sym(key, 0, out, false);
1316
+ switch (rtype) {
1317
+ case T_STRING: oj_dump_str(key, 0, out, false); break;
1318
+ case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
1319
+ default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
1320
1320
  }
1321
1321
  size = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
1322
1322
  assure_size(out, size);
@@ -1345,7 +1345,7 @@ static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
1345
1345
  return;
1346
1346
  }
1347
1347
  }
1348
- if ((!oj_rails_hash_opt || 0 < out->argc) && as_ok && rb_respond_to(obj, oj_as_json_id)) {
1348
+ if (!oj_rails_hash_opt && as_ok && rb_respond_to(obj, oj_as_json_id)) {
1349
1349
  dump_as_json(obj, depth, out, false);
1350
1350
  return;
1351
1351
  }
data/ext/oj/saj.c CHANGED
@@ -648,8 +648,16 @@ oj_saj_parse(int argc, VALUE *argv, VALUE self) {
648
648
  len = lseek(fd, 0, SEEK_END);
649
649
  lseek(fd, 0, SEEK_SET);
650
650
  json = OJ_R_ALLOC_N(char, len + 1);
651
- if (0 >= (cnt = read(fd, json, len)) || cnt != (ssize_t)len) {
652
- rb_raise(rb_eIOError, "failed to read from IO Object.");
651
+ {
652
+ size_t total = 0;
653
+
654
+ while (total < len) {
655
+ cnt = read(fd, json + total, len - total);
656
+ if (cnt <= 0) {
657
+ rb_raise(rb_eIOError, "failed to read from IO Object.");
658
+ }
659
+ total += cnt;
660
+ }
653
661
  }
654
662
  json[len] = '\0';
655
663
  #endif
data/ext/oj/simd.h CHANGED
@@ -191,4 +191,29 @@ static inline OJ_TARGET_SSE42 __m128i vector_lookup_sse42(__m128i input, __m128i
191
191
 
192
192
  #endif
193
193
 
194
+ #ifndef __has_builtin
195
+ #define __has_builtin(x) 0
196
+ #endif
197
+
198
+ #if __has_builtin(__builtin_memcpy)
199
+ #define HAVE_FAST_MEMCPY 1
200
+
201
+ inline static void fast_memcpy16(void *dest, const void *src, size_t n) {
202
+ char *d = (char *)dest;
203
+ char *s = (char *)src;
204
+ if (n >= 8) {
205
+ __builtin_memcpy(d, s, 8);
206
+ __builtin_memcpy(d + n - 8, s + n - 8, 8);
207
+ } else if (n >= 4) {
208
+ __builtin_memcpy(d, s, 4);
209
+ __builtin_memcpy(d + n - 4, s + n - 4, 4);
210
+ } else if (n >= 2) {
211
+ __builtin_memcpy(d, s, 2);
212
+ __builtin_memcpy(d + n - 2, s + n - 2, 2);
213
+ } else if (n >= 1) {
214
+ *d = *s;
215
+ }
216
+ }
217
+ #endif
218
+
194
219
  #endif /* OJ_SIMD_H */
data/ext/oj/sparse.c CHANGED
@@ -415,7 +415,7 @@ static void read_num(ParseInfo pi) {
415
415
  ni.bigdec_load = pi->options.compat_bigdec;
416
416
  } else {
417
417
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
418
- RubyDec == pi->options.bigdec_load);
418
+ RubyDec == pi->options.bigdec_load);
419
419
  ni.bigdec_load = pi->options.bigdec_load;
420
420
  }
421
421
 
@@ -565,7 +565,7 @@ static void read_nan(ParseInfo pi) {
565
565
  ni.bigdec_load = pi->options.compat_bigdec;
566
566
  } else {
567
567
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
568
- RubyDec == pi->options.bigdec_load);
568
+ RubyDec == pi->options.bigdec_load);
569
569
  ni.bigdec_load = pi->options.bigdec_load;
570
570
  }
571
571
 
@@ -761,7 +761,7 @@ void oj_sparse2(ParseInfo pi) {
761
761
  ni.bigdec_load = pi->options.compat_bigdec;
762
762
  } else {
763
763
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
764
- RubyDec == pi->options.bigdec_load);
764
+ RubyDec == pi->options.bigdec_load);
765
765
  ni.bigdec_load = pi->options.bigdec_load;
766
766
  }
767
767
  add_num_value(pi, &ni);
data/lib/oj/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Oj
2
2
  # Current version of the module.
3
- VERSION = '3.16.16'
3
+ VERSION = '3.16.17'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.16.16
4
+ version: 3.16.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler