oj 3.16.3 → 3.16.5

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: 3a6b15904386e7010fa071ecc27b8f3a6d5023c8162ea7042fc39e29c373a20a
4
- data.tar.gz: b862e59cff0ba3a15eadec34fe807d5893c9c1d1e9fcce01ced66ab28e642181
3
+ metadata.gz: 6eef89e8f6c362f4ff8c384bba7a892bff9e730a1225bb0bc986acf50ba45dd4
4
+ data.tar.gz: 2d915ffcec2b55a4a91ad2292053e847efa5aa4ee6ef24e686cadd30218d7533
5
5
  SHA512:
6
- metadata.gz: f186fb40ebcc3792b4949f81ce3b2efb22bee9dce9b44785eeca37cc27e25b79e8afa4fc5872232001d28a3d0b279fc222d5f7d2f8aaa091fe3a135246139b64
7
- data.tar.gz: 9c1536bfc15eb3fe02e92d803417ef1ccce5084036aaf8df5ae2b61995a429ab8ae00be5e9b7c79e11839d781c67523ed5bc534df36a40b59dcc61db6551158f
6
+ metadata.gz: 845799a9624881305218a82d4d7ceb1ee535da2192ee44e30d38cd4727739749b5c9b18d891e5b363ca0a9b8e8025ee89a66f789f0bf4338493f2c34e1f6a576
7
+ data.tar.gz: f2cad391b8de831efbf419332b5cc12f29e9cfae1a7a1cf4bf98348de722a3b400cddb4aa29a98576f38cf929061fd57f6e97424eff2bf0e33a9d5f78cfa27c1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.16.5 - 2024-08-07
4
+
5
+ - Fixed Oj::Parser so that block procedures work correctly.
6
+
7
+ ## 3.16.4 - 2024-06-08
8
+
9
+ - Fixed Oj::Parse EOF issue on larger stream input.
10
+
3
11
  ## 3.16.3 - 2023-12-11
4
12
 
5
13
  - Fixed the gemspec to allow earlier versions of the bigdecimal gem.
data/ext/oj/dump.c CHANGED
@@ -198,7 +198,18 @@ inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
198
198
  }
199
199
 
200
200
  inline static size_t rails_friendly_size(const uint8_t *str, size_t len) {
201
- return calculate_string_size(str, len, rails_friendly_chars);
201
+ long size = 0;
202
+ size_t i = len;
203
+ uint8_t hi = 0;
204
+
205
+ for (; 0 < i; str++, i--) {
206
+ size += rails_friendly_chars[*str];
207
+ hi |= *str & 0x80;
208
+ }
209
+ if (0 == hi) {
210
+ return size - len * (size_t)'0';
211
+ }
212
+ return -(size - len * (size_t)'0');
202
213
  }
203
214
 
204
215
  const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, int *lenp) {
@@ -750,8 +761,9 @@ void oj_dump_raw_json(VALUE obj, int depth, Out out) {
750
761
  void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
751
762
  size_t size;
752
763
  char *cmap;
753
- const char *orig = str;
754
- bool has_hi = false;
764
+ const char *orig = str;
765
+ bool has_hi = false;
766
+ bool do_unicode_validation = false;
755
767
 
756
768
  switch (out->opts->escape_mode) {
757
769
  case NLEsc:
@@ -772,8 +784,9 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
772
784
  size = xss_friendly_size((uint8_t *)str, cnt);
773
785
  break;
774
786
  case JXEsc:
775
- cmap = hixss_friendly_chars;
776
- size = hixss_friendly_size((uint8_t *)str, cnt);
787
+ cmap = hixss_friendly_chars;
788
+ size = hixss_friendly_size((uint8_t *)str, cnt);
789
+ do_unicode_validation = true;
777
790
  break;
778
791
  case RailsXEsc: {
779
792
  long sz;
@@ -786,12 +799,22 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
786
799
  } else {
787
800
  size = (size_t)sz;
788
801
  }
802
+ do_unicode_validation = true;
789
803
  break;
790
804
  }
791
- case RailsEsc:
805
+ case RailsEsc: {
806
+ long sz;
792
807
  cmap = rails_friendly_chars;
793
- size = rails_friendly_size((uint8_t *)str, cnt);
808
+ sz = rails_friendly_size((uint8_t *)str, cnt);
809
+ if (sz < 0) {
810
+ has_hi = true;
811
+ size = (size_t)-sz;
812
+ } else {
813
+ size = (size_t)sz;
814
+ }
815
+ do_unicode_validation = true;
794
816
  break;
817
+ }
795
818
  case JSONEsc:
796
819
  default: cmap = hibit_friendly_chars; size = hibit_friendly_size((uint8_t *)str, cnt);
797
820
  }
@@ -822,7 +845,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
822
845
  for (; str < end; str++) {
823
846
  switch (cmap[(uint8_t)*str]) {
824
847
  case '1':
825
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && check_start <= str) {
848
+ if (do_unicode_validation && check_start <= str) {
826
849
  if (0 != (0x80 & (uint8_t)*str)) {
827
850
  if (0xC0 == (0xC0 & (uint8_t)*str)) {
828
851
  check_start = check_unicode(str, end, orig);
@@ -846,8 +869,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
846
869
  }
847
870
  break;
848
871
  case '3': // Unicode
849
- if (0xe2 == (uint8_t)*str && (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) &&
850
- 2 <= end - str) {
872
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
851
873
  if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
852
874
  str = dump_unicode(str, end, out, orig);
853
875
  } else {
@@ -866,8 +888,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
866
888
  APPEND_CHARS(out->cur, "\\u00", 4);
867
889
  dump_hex((uint8_t)*str, out);
868
890
  } else {
869
- if (0xe2 == (uint8_t)*str &&
870
- (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 2 <= end - str) {
891
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
871
892
  if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
872
893
  str = dump_unicode(str, end, out, orig);
873
894
  } else {
@@ -884,8 +905,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
884
905
  }
885
906
  *out->cur++ = '"';
886
907
  }
887
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 0 < str - orig &&
888
- 0 != (0x80 & *(str - 1))) {
908
+ if (do_unicode_validation && 0 < str - orig && 0 != (0x80 & *(str - 1))) {
889
909
  uint8_t c = (uint8_t) * (str - 1);
890
910
  int i;
891
911
  int scnt = (int)(str - orig);
data/ext/oj/extconf.rb CHANGED
@@ -29,10 +29,9 @@ dflags = {
29
29
  have_func('rb_gc_mark_movable')
30
30
  have_func('stpcpy')
31
31
  have_func('pthread_mutex_init')
32
+ have_func('getrlimit', 'sys/resource.h')
32
33
  have_func('rb_enc_interned_str')
33
34
  have_func('rb_ext_ractor_safe', 'ruby.h')
34
- # rb_hash_bulk_insert is deep down in a header not included in normal build and that seems to fool have_func.
35
- have_func('rb_hash_bulk_insert', 'ruby.h') unless '2' == version[0] && '6' == version[1]
36
35
 
37
36
  dflags['OJ_DEBUG'] = true unless ENV['OJ_DEBUG'].nil?
38
37
 
data/ext/oj/fast.c CHANGED
@@ -40,7 +40,7 @@ typedef struct _doc {
40
40
  Leaf *where; // points to current location
41
41
  Leaf where_path[MAX_STACK]; // points to head of path
42
42
  char *json;
43
- unsigned long size; // number of leaves/branches in the doc
43
+ unsigned long size; // number of leaves/branches in the doc
44
44
  VALUE self;
45
45
  Batch batches;
46
46
  struct _batch batch0;
@@ -573,7 +573,7 @@ static char *read_quoted_value(ParseInfo pi) {
573
573
  char *h = pi->s; // head
574
574
  char *t = h; // tail
575
575
 
576
- h++; // skip quote character
576
+ h++; // skip quote character
577
577
  t++;
578
578
  value = h;
579
579
  for (; '"' != *h; h++, t++) {
@@ -765,7 +765,7 @@ static VALUE parse_json(VALUE clas, char *json, bool given) {
765
765
  pi.s = pi.str;
766
766
  doc_init(doc);
767
767
  pi.doc = doc;
768
- #if IS_WINDOWS
768
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
769
769
  // assume a 1M stack and give half to ruby
770
770
  pi.stack_min = (void *)((char *)&pi - (512L * 1024L));
771
771
  #else
data/ext/oj/oj.c CHANGED
@@ -36,6 +36,7 @@ ID oj_as_json_id;
36
36
  ID oj_begin_id;
37
37
  ID oj_bigdecimal_id;
38
38
  ID oj_end_id;
39
+ ID oj_eofq_id;
39
40
  ID oj_exclude_end_id;
40
41
  ID oj_error_id;
41
42
  ID oj_file_id;
@@ -1849,6 +1850,7 @@ void Init_oj(void) {
1849
1850
  oj_begin_id = rb_intern("begin");
1850
1851
  oj_bigdecimal_id = rb_intern("BigDecimal");
1851
1852
  oj_end_id = rb_intern("end");
1853
+ oj_eofq_id = rb_intern("eof?");
1852
1854
  oj_error_id = rb_intern("error");
1853
1855
  oj_exclude_end_id = rb_intern("exclude_end?");
1854
1856
  oj_file_id = rb_intern("file?");
data/ext/oj/oj.h CHANGED
@@ -334,6 +334,7 @@ extern ID oj_as_json_id;
334
334
  extern ID oj_begin_id;
335
335
  extern ID oj_bigdecimal_id;
336
336
  extern ID oj_end_id;
337
+ extern ID oj_eofq_id;
337
338
  extern ID oj_error_id;
338
339
  extern ID oj_exclude_end_id;
339
340
  extern ID oj_file_id;
data/ext/oj/parser.c CHANGED
@@ -1114,9 +1114,6 @@ static void parse(ojParser p, const byte *json) {
1114
1114
  p->map = trail_map;
1115
1115
  }
1116
1116
  }
1117
- if (0 < p->depth) {
1118
- parse_error(p, "parse error, not closed");
1119
- }
1120
1117
  if (0 == p->depth) {
1121
1118
  switch (p->map[256]) {
1122
1119
  case '0':
@@ -1394,6 +1391,12 @@ static VALUE load(VALUE self) {
1394
1391
  if (0 < RSTRING_LEN(rbuf)) {
1395
1392
  parse(p, (byte *)StringValuePtr(rbuf));
1396
1393
  }
1394
+ if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
1395
+ if (0 < p->depth) {
1396
+ parse_error(p, "parse error, not closed");
1397
+ }
1398
+ break;
1399
+ }
1397
1400
  }
1398
1401
  return Qtrue;
1399
1402
  }
data/ext/oj/parser.h CHANGED
@@ -32,9 +32,9 @@ typedef struct _num {
32
32
  long double dub;
33
33
  int64_t fixnum; // holds all digits
34
34
  uint32_t len;
35
- int16_t div; // 10^div
35
+ int16_t div; // 10^div
36
36
  int16_t exp;
37
- uint8_t shift; // shift of fixnum to get decimal
37
+ uint8_t shift; // shift of fixnum to get decimal
38
38
  bool neg;
39
39
  bool exp_neg;
40
40
  // for numbers as strings, reuse buf
data/ext/oj/reader.c CHANGED
@@ -101,7 +101,7 @@ int oj_reader_read(Reader reader) {
101
101
  } else {
102
102
  shift = reader->pro - reader->head - 1; // leave one character so we can backup one
103
103
  }
104
- if (0 >= shift) { /* no space left so allocate more */
104
+ if (0 >= shift) { /* no space left so allocate more */
105
105
  const char *old = reader->head;
106
106
  size_t size = reader->end - reader->head + BUF_PAD;
107
107
 
data/ext/oj/saj.c CHANGED
@@ -578,7 +578,7 @@ static void saj_parse(VALUE handler, char *json) {
578
578
  /* initialize parse info */
579
579
  pi.str = json;
580
580
  pi.s = json;
581
- #if IS_WINDOWS
581
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
582
582
  pi.stack_min = (void *)((char *)&obj - (512L * 1024L)); /* assume a 1M stack and give half to ruby */
583
583
  #else
584
584
  {
@@ -587,7 +587,7 @@ static void saj_parse(VALUE handler, char *json) {
587
587
  if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) {
588
588
  pi.stack_min = (void *)((char *)&obj - (lim.rlim_cur / 4 * 3)); /* let 3/4ths of the stack be used only */
589
589
  } else {
590
- pi.stack_min = 0; /* indicates not to check stack limit */
590
+ pi.stack_min = 0; /* indicates not to check stack limit */
591
591
  }
592
592
  }
593
593
  #endif
data/ext/oj/usual.c CHANGED
@@ -281,7 +281,6 @@ static void close_object(ojParser p) {
281
281
  VALUE *head = d->vhead + c->vi + 1;
282
282
  volatile VALUE obj = rb_hash_new();
283
283
 
284
- #if HAVE_RB_HASH_BULK_INSERT
285
284
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
286
285
  *vp = d->get_key(p, kp);
287
286
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -289,18 +288,15 @@ static void close_object(ojParser p) {
289
288
  }
290
289
  }
291
290
  rb_hash_bulk_insert(d->vtail - head, head, obj);
292
- #else
293
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
294
- rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
295
- if (sizeof(kp->buf) <= (size_t)kp->len) {
296
- OJ_R_FREE(kp->key);
297
- }
298
- }
299
- #endif
300
291
  d->ktail = d->khead + c->ki;
292
+
301
293
  d->vtail = head;
302
294
  head--;
303
295
  *head = obj;
296
+ if (1 == d->vtail - d->vhead && rb_block_given_p()) {
297
+ d->vtail = d->vhead;
298
+ rb_yield(obj);
299
+ }
304
300
  }
305
301
 
306
302
  static void close_object_class(ojParser p) {
@@ -341,7 +337,6 @@ static void close_object_create(ojParser p) {
341
337
  head++;
342
338
  if (Qnil == d->hash_class) {
343
339
  obj = rb_hash_new();
344
- #if HAVE_RB_HASH_BULK_INSERT
345
340
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
346
341
  *vp = d->get_key(p, kp);
347
342
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -349,14 +344,6 @@ static void close_object_create(ojParser p) {
349
344
  }
350
345
  }
351
346
  rb_hash_bulk_insert(d->vtail - head, head, obj);
352
- #else
353
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
354
- rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
355
- if (sizeof(kp->buf) <= (size_t)kp->len) {
356
- OJ_R_FREE(kp->key);
357
- }
358
- }
359
- #endif
360
347
  } else {
361
348
  obj = rb_class_new_instance(0, NULL, d->hash_class);
362
349
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
@@ -373,7 +360,6 @@ static void close_object_create(ojParser p) {
373
360
  if (!d->ignore_json_create && rb_respond_to(clas, oj_json_create_id)) {
374
361
  volatile VALUE arg = rb_hash_new();
375
362
 
376
- #if HAVE_RB_HASH_BULK_INSERT
377
363
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
378
364
  *vp = d->get_key(p, kp);
379
365
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -381,14 +367,6 @@ static void close_object_create(ojParser p) {
381
367
  }
382
368
  }
383
369
  rb_hash_bulk_insert(d->vtail - head, head, arg);
384
- #else
385
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
386
- rb_hash_aset(arg, d->get_key(p, kp), *(vp + 1));
387
- if (sizeof(kp->buf) <= (size_t)kp->len) {
388
- OJ_R_FREE(kp->key);
389
- }
390
- }
391
- #endif
392
370
  obj = rb_funcall(clas, oj_json_create_id, 1, arg);
393
371
  } else {
394
372
  obj = rb_class_new_instance(0, NULL, clas);
@@ -599,7 +577,18 @@ static VALUE result(ojParser p) {
599
577
  Usual d = (Usual)p->ctx;
600
578
 
601
579
  if (d->vhead < d->vtail) {
602
- return *d->vhead;
580
+ long cnt = d->vtail - d->vhead;
581
+ volatile VALUE ary;
582
+ volatile VALUE *vp;
583
+
584
+ if (1 == cnt) {
585
+ return *d->vhead;
586
+ }
587
+ ary = rb_ary_new();
588
+ for (vp = d->vhead; vp < d->vtail; vp++) {
589
+ rb_ary_push(ary, *vp);
590
+ }
591
+ return ary;
603
592
  }
604
593
  if (d->raise_on_empty) {
605
594
  rb_raise(oj_parse_error_class, "empty string");
data/lib/oj/mimic.rb CHANGED
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  require 'bigdecimal'
4
- begin
5
- require 'ostruct'
6
- rescue Exception
7
- # ignore
8
- end
4
+ require 'ostruct'
9
5
 
10
6
  module Oj
11
7
 
data/lib/oj/schandler.rb CHANGED
@@ -64,13 +64,14 @@ module Oj
64
64
  #
65
65
  # hash_end
66
66
  #
67
- # When a hash key is encountered the hash_key method is called with the parsed
68
- # hash value key. The return value from the call is then used as the key in
69
- # the key-value pair that follows.
67
+ # At the end of a JSON object element the hash_end() callback is called if
68
+ # public.
70
69
  #
71
70
  # hash_key
72
71
  #
73
- # At the end of a JSON object element the hash_end() callback is called if public.
72
+ # When a hash key is encountered the hash_key() method is called with the
73
+ # parsed hash value key. The return value from the call is then used as the
74
+ # key in the key-value pair that follows.
74
75
  #
75
76
  # hash_set
76
77
  #
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.3'
3
+ VERSION = '3.16.5'
4
4
  end
@@ -74,42 +74,77 @@ class TestJSONEncoding < ActiveSupport::TestCase
74
74
  ActiveSupport.escape_html_entities_in_json = false
75
75
  end
76
76
 
77
- def test_utf8_string_encoded_properly
78
- # The original test seems to expect that
79
- # ActiveSupport.escape_html_entities_in_json reverts to true even after
80
- # being set to false. I haven't been able to figure that out so the value is
81
- # set to true, the default, before running the test. This might be wrong but
82
- # for now it will have to do.
83
- ActiveSupport.escape_html_entities_in_json = true
84
- result = ActiveSupport::JSON.encode("€2.99")
85
- assert_equal '"€2.99"', result
86
- assert_equal(Encoding::UTF_8, result.encoding)
87
-
88
- result = ActiveSupport::JSON.encode("✎☺")
89
- assert_equal '"✎☺"', result
90
- assert_equal(Encoding::UTF_8, result.encoding)
77
+ def test_hash_keys_encoding_without_escaping
78
+ assert_equal "{\"<>\":\"<>\"}", ActiveSupport::JSON.encode("<>" => "<>")
91
79
  end
92
80
 
93
- def test_non_utf8_string_transcodes
94
- s = "二".encode("Shift_JIS")
95
- result = ActiveSupport::JSON.encode(s)
96
- assert_equal '""', result
97
- assert_equal Encoding::UTF_8, result.encoding
81
+ module UnicodeTests
82
+ def test_utf8_string_encoded_properly
83
+ result = ActiveSupport::JSON.encode("€2.99")
84
+ assert_equal '"€2.99"', result
85
+ assert_equal(Encoding::UTF_8, result.encoding)
86
+
87
+ result = ActiveSupport::JSON.encode("✎☺")
88
+ assert_equal '"✎☺"', result
89
+ assert_equal(Encoding::UTF_8, result.encoding)
90
+ end
91
+
92
+ def test_non_utf8_string_transcodes
93
+ s = "二".encode("Shift_JIS")
94
+ result = ActiveSupport::JSON.encode(s)
95
+ assert_equal '"二"', result
96
+ assert_equal Encoding::UTF_8, result.encoding
97
+ end
98
+
99
+ def test_wide_utf8_chars
100
+ w = "𠜎"
101
+ result = ActiveSupport::JSON.encode(w)
102
+ assert_equal '"𠜎"', result
103
+ end
104
+
105
+ def test_wide_utf8_roundtrip
106
+ hash = { string: "𐒑" }
107
+ json = ActiveSupport::JSON.encode(hash)
108
+ decoded_hash = ActiveSupport::JSON.decode(json)
109
+ assert_equal "𐒑", decoded_hash["string"]
110
+ end
111
+
112
+ def test_invalid_encoding_raises
113
+ s = "\xAE\xFF\x9F"
114
+ refute s.valid_encoding?
115
+
116
+ # n.b. this raises EncodingError, because we didn't call Oj.mimic_JSON in the test setup; but,
117
+ # if you do that (even indirectly through Oj.optimize_rails), then this raises a
118
+ # JSON::GeneratorError instead of an EncodingError.
119
+ assert_raises(EncodingError) do
120
+ ActiveSupport::JSON.encode([s])
121
+ end
122
+ end
98
123
  end
99
124
 
100
- def test_wide_utf8_chars
101
- w = "𠜎"
102
- result = ActiveSupport::JSON.encode(w)
103
- assert_equal '"𠜎"', result
125
+ module UnicodeTestsWithEscapingOn
126
+ def setup
127
+ ActiveSupport.escape_html_entities_in_json = true
128
+ end
129
+
130
+ def teardown
131
+ ActiveSupport.escape_html_entities_in_json = false
132
+ end
133
+
134
+ include UnicodeTests
104
135
  end
105
136
 
106
- def test_wide_utf8_roundtrip
107
- hash = { string: "𐒑" }
108
- json = ActiveSupport::JSON.encode(hash)
109
- decoded_hash = ActiveSupport::JSON.decode(json)
110
- assert_equal "𐒑", decoded_hash["string"]
137
+ module UnicodeTestsWithEscapingOff
138
+ def setup
139
+ ActiveSupport.escape_html_entities_in_json = false
140
+ end
141
+
142
+ include UnicodeTests
111
143
  end
112
144
 
145
+ include UnicodeTestsWithEscapingOn
146
+ include UnicodeTestsWithEscapingOff
147
+
113
148
  def test_hash_key_identifiers_are_always_quoted
114
149
  values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
115
150
  assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
@@ -8,9 +8,18 @@ require "active_support/time"
8
8
  require_relative "time_zone_test_helpers"
9
9
  require_relative "encoding_test_cases"
10
10
 
11
+ require 'oj'
12
+ # Sets the ActiveSupport encoder to be Oj and also wraps the setting of globals.
13
+ Oj::Rails.set_encoder()
14
+ Oj::Rails.optimize()
15
+
11
16
  class TestJSONEncoding < ActiveSupport::TestCase
12
17
  include TimeZoneTestHelpers
13
18
 
19
+ def test_is_actually_oj
20
+ assert_equal Oj::Rails::Encoder, ActiveSupport.json_encoder
21
+ end
22
+
14
23
  def sorted_json(json)
15
24
  if json.start_with?("{") && json.end_with?("}")
16
25
  "{" + json[1..-2].split(",").sort.join(",") + "}"
@@ -61,36 +70,77 @@ class TestJSONEncoding < ActiveSupport::TestCase
61
70
  ActiveSupport.escape_html_entities_in_json = false
62
71
  end
63
72
 
64
- def test_utf8_string_encoded_properly
65
- result = ActiveSupport::JSON.encode("€2.99")
66
- assert_equal '"€2.99"', result
67
- assert_equal(Encoding::UTF_8, result.encoding)
68
-
69
- result = ActiveSupport::JSON.encode("✎☺")
70
- assert_equal '"✎☺"', result
71
- assert_equal(Encoding::UTF_8, result.encoding)
73
+ def test_hash_keys_encoding_without_escaping
74
+ assert_equal "{\"<>\":\"<>\"}", ActiveSupport::JSON.encode("<>" => "<>")
72
75
  end
73
76
 
74
- def test_non_utf8_string_transcodes
75
- s = "二".encode("Shift_JIS")
76
- result = ActiveSupport::JSON.encode(s)
77
- assert_equal '""', result
78
- assert_equal Encoding::UTF_8, result.encoding
77
+ module UnicodeTests
78
+ def test_utf8_string_encoded_properly
79
+ result = ActiveSupport::JSON.encode("€2.99")
80
+ assert_equal '"€2.99"', result
81
+ assert_equal(Encoding::UTF_8, result.encoding)
82
+
83
+ result = ActiveSupport::JSON.encode("✎☺")
84
+ assert_equal '"✎☺"', result
85
+ assert_equal(Encoding::UTF_8, result.encoding)
86
+ end
87
+
88
+ def test_non_utf8_string_transcodes
89
+ s = "二".encode("Shift_JIS")
90
+ result = ActiveSupport::JSON.encode(s)
91
+ assert_equal '"二"', result
92
+ assert_equal Encoding::UTF_8, result.encoding
93
+ end
94
+
95
+ def test_wide_utf8_chars
96
+ w = "𠜎"
97
+ result = ActiveSupport::JSON.encode(w)
98
+ assert_equal '"𠜎"', result
99
+ end
100
+
101
+ def test_wide_utf8_roundtrip
102
+ hash = { string: "𐒑" }
103
+ json = ActiveSupport::JSON.encode(hash)
104
+ decoded_hash = ActiveSupport::JSON.decode(json)
105
+ assert_equal "𐒑", decoded_hash["string"]
106
+ end
107
+
108
+ def test_invalid_encoding_raises
109
+ s = "\xAE\xFF\x9F"
110
+ refute s.valid_encoding?
111
+
112
+ # n.b. this raises EncodingError, because we didn't call Oj.mimic_JSON in the test setup; but,
113
+ # if you do that (even indirectly through Oj.optimize_rails), then this raises a
114
+ # JSON::GeneratorError instead of an EncodingError.
115
+ assert_raises(EncodingError) do
116
+ ActiveSupport::JSON.encode([s])
117
+ end
118
+ end
79
119
  end
80
120
 
81
- def test_wide_utf8_chars
82
- w = "𠜎"
83
- result = ActiveSupport::JSON.encode(w)
84
- assert_equal '"𠜎"', result
121
+ module UnicodeTestsWithEscapingOn
122
+ def setup
123
+ ActiveSupport.escape_html_entities_in_json = true
124
+ end
125
+
126
+ def teardown
127
+ ActiveSupport.escape_html_entities_in_json = false
128
+ end
129
+
130
+ include UnicodeTests
85
131
  end
86
132
 
87
- def test_wide_utf8_roundtrip
88
- hash = { string: "𐒑" }
89
- json = ActiveSupport::JSON.encode(hash)
90
- decoded_hash = ActiveSupport::JSON.decode(json)
91
- assert_equal "𐒑", decoded_hash["string"]
133
+ module UnicodeTestsWithEscapingOff
134
+ def setup
135
+ ActiveSupport.escape_html_entities_in_json = false
136
+ end
137
+
138
+ include UnicodeTests
92
139
  end
93
140
 
141
+ include UnicodeTestsWithEscapingOn
142
+ include UnicodeTestsWithEscapingOff
143
+
94
144
  def test_hash_key_identifiers_are_always_quoted
95
145
  values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
96
146
  assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
data/test/foo.rb CHANGED
@@ -5,10 +5,22 @@ $LOAD_PATH << '.'
5
5
  $LOAD_PATH << File.join(__dir__, '../lib')
6
6
  $LOAD_PATH << File.join(__dir__, '../ext')
7
7
 
8
- require 'json'
9
8
  require 'oj'
10
- require 'oj/json'
11
9
 
12
- Oj.mimic_JSON
10
+ reader, writer = IO.pipe
13
11
 
14
- JSON.parse("[]")
12
+ thread =
13
+ Thread.new do
14
+ 5.times do |id|
15
+ Oj.to_stream(writer, { "id" => id })
16
+ sleep(1)
17
+ end
18
+
19
+ writer.close
20
+ end
21
+
22
+ p = Oj::Parser.new(:usual)
23
+ p.load(reader) { |data| puts "#{Time.now} -- ID: #{data["id"]}" }
24
+
25
+ reader.close
26
+ thread.join
@@ -114,6 +114,30 @@ class UsualTest < Minitest::Test
114
114
  assert_equal(Float, doc.class)
115
115
  end
116
116
 
117
+ def test_multi_parse
118
+ p = Oj::Parser.new(:usual)
119
+ out = []
120
+ p.parse('{"a":1}{"b":{"x":2}} {"c":3}') { |j| out.push(j) }
121
+ assert_equal([{'a'=>1}, {'b'=>{'x'=>2}},{'c'=>3}], out)
122
+ end
123
+
124
+ def test_multi_load
125
+ p = Oj::Parser.new(:usual)
126
+ out = []
127
+ r, w = IO.pipe
128
+ thread = Thread.new do
129
+ ['{"a":1}', '{"b":{"x"', ':2}}{"c":', '3}'].each { |seg|
130
+ w.write(seg)
131
+ sleep(0.1)
132
+ }
133
+ w.close
134
+ end
135
+ p.load(r) { |j| out.push(j) }
136
+ r.close
137
+ thread.join
138
+ assert_equal([{'a'=>1}, {'b'=>{'x'=>2}},{'c'=>3}], out)
139
+ end
140
+
117
141
  def test_omit_null
118
142
  p = Oj::Parser.new(:usual)
119
143
  p.omit_null = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.16.3
4
+ version: 3.16.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-11 00:00:00.000000000 Z
11
+ date: 2024-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ostruct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: minitest
29
43
  requirement: !ruby/object:Gem::Requirement