oj 2.18.5 → 3.0.0

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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -226
  3. data/ext/oj/circarray.c +0 -25
  4. data/ext/oj/circarray.h +0 -25
  5. data/ext/oj/code.c +227 -0
  6. data/ext/oj/code.h +40 -0
  7. data/ext/oj/compat.c +126 -38
  8. data/ext/oj/custom.c +1097 -0
  9. data/ext/oj/dump.c +658 -2376
  10. data/ext/oj/dump.h +92 -0
  11. data/ext/oj/dump_compat.c +937 -0
  12. data/ext/oj/dump_leaf.c +254 -0
  13. data/ext/oj/dump_object.c +810 -0
  14. data/ext/oj/dump_rails.c +329 -0
  15. data/ext/oj/dump_strict.c +416 -0
  16. data/ext/oj/err.c +0 -25
  17. data/ext/oj/err.h +8 -2
  18. data/ext/oj/fast.c +24 -24
  19. data/ext/oj/mimic_json.c +817 -0
  20. data/ext/oj/mimic_rails.c +806 -0
  21. data/ext/oj/mimic_rails.h +17 -0
  22. data/ext/oj/object.c +18 -72
  23. data/ext/oj/odd.c +0 -25
  24. data/ext/oj/odd.h +2 -27
  25. data/ext/oj/oj.c +655 -1503
  26. data/ext/oj/oj.h +93 -40
  27. data/ext/oj/parse.c +99 -46
  28. data/ext/oj/parse.h +12 -26
  29. data/ext/oj/reader.c +1 -25
  30. data/ext/oj/reader.h +3 -25
  31. data/ext/oj/resolve.c +9 -11
  32. data/ext/oj/resolve.h +2 -2
  33. data/ext/oj/rxclass.c +133 -0
  34. data/ext/oj/rxclass.h +27 -0
  35. data/ext/oj/saj.c +4 -25
  36. data/ext/oj/scp.c +3 -25
  37. data/ext/oj/sparse.c +89 -13
  38. data/ext/oj/stream_writer.c +301 -0
  39. data/ext/oj/strict.c +4 -27
  40. data/ext/oj/string_writer.c +480 -0
  41. data/ext/oj/val_stack.h +6 -2
  42. data/lib/oj.rb +1 -23
  43. data/lib/oj/easy_hash.rb +12 -4
  44. data/lib/oj/json.rb +172 -0
  45. data/lib/oj/mimic.rb +123 -18
  46. data/lib/oj/state.rb +131 -0
  47. data/lib/oj/version.rb +1 -1
  48. data/pages/Advanced.md +22 -0
  49. data/pages/Compatibility.md +25 -0
  50. data/pages/Custom.md +23 -0
  51. data/pages/Encoding.md +65 -0
  52. data/pages/JsonGem.md +79 -0
  53. data/pages/Modes.md +140 -0
  54. data/pages/Options.md +250 -0
  55. data/pages/Rails.md +60 -0
  56. data/pages/Security.md +20 -0
  57. data/test/activesupport4/decoding_test.rb +105 -0
  58. data/test/activesupport4/encoding_test.rb +531 -0
  59. data/test/activesupport4/test_helper.rb +41 -0
  60. data/test/activesupport5/decoding_test.rb +125 -0
  61. data/test/activesupport5/encoding_test.rb +483 -0
  62. data/test/activesupport5/encoding_test_cases.rb +90 -0
  63. data/test/activesupport5/test_helper.rb +50 -0
  64. data/test/activesupport5/time_zone_test_helpers.rb +24 -0
  65. data/test/json_gem/json_addition_test.rb +216 -0
  66. data/test/json_gem/json_common_interface_test.rb +143 -0
  67. data/test/json_gem/json_encoding_test.rb +109 -0
  68. data/test/json_gem/json_ext_parser_test.rb +20 -0
  69. data/test/json_gem/json_fixtures_test.rb +35 -0
  70. data/test/json_gem/json_generator_test.rb +383 -0
  71. data/test/json_gem/json_generic_object_test.rb +90 -0
  72. data/test/json_gem/json_parser_test.rb +470 -0
  73. data/test/json_gem/json_string_matching_test.rb +42 -0
  74. data/test/json_gem/test_helper.rb +18 -0
  75. data/test/perf_compat.rb +30 -28
  76. data/test/perf_object.rb +1 -1
  77. data/test/perf_strict.rb +18 -1
  78. data/test/sample.rb +0 -1
  79. data/test/test_compat.rb +169 -93
  80. data/test/test_custom.rb +355 -0
  81. data/test/test_file.rb +0 -8
  82. data/test/test_null.rb +376 -0
  83. data/test/test_object.rb +268 -3
  84. data/test/test_scp.rb +22 -1
  85. data/test/test_strict.rb +160 -4
  86. data/test/test_various.rb +52 -620
  87. data/test/tests.rb +14 -0
  88. data/test/tests_mimic.rb +14 -0
  89. data/test/tests_mimic_addition.rb +7 -0
  90. metadata +89 -47
  91. data/test/activesupport_datetime_test.rb +0 -23
  92. data/test/bug.rb +0 -51
  93. data/test/bug2.rb +0 -10
  94. data/test/bug3.rb +0 -46
  95. data/test/bug_fast.rb +0 -32
  96. data/test/bug_load.rb +0 -24
  97. data/test/crash.rb +0 -111
  98. data/test/curl/curl_oj.rb +0 -46
  99. data/test/curl/get_oj.rb +0 -24
  100. data/test/curl/just_curl.rb +0 -31
  101. data/test/curl/just_oj.rb +0 -51
  102. data/test/example.rb +0 -11
  103. data/test/foo.rb +0 -24
  104. data/test/io.rb +0 -48
  105. data/test/isolated/test_mimic_rails_datetime.rb +0 -27
  106. data/test/mod.rb +0 -16
  107. data/test/rails.rb +0 -50
  108. data/test/russian.rb +0 -18
  109. data/test/struct.rb +0 -29
  110. data/test/test_serializer.rb +0 -59
  111. data/test/write_timebars.rb +0 -31
@@ -0,0 +1,806 @@
1
+ /* mimic_rails.c
2
+ * Copyright (c) 2017, Peter Ohler
3
+ * All rights reserved.
4
+ */
5
+
6
+ #include "dump.h"
7
+ #include "mimic_rails.h"
8
+ #include "encode.h"
9
+
10
+ // TBD keep static array of strings and functions to help with rails optimization
11
+ typedef struct _Encoder {
12
+ struct _ROptTable ropts;
13
+ struct _Options opts;
14
+ VALUE arg;
15
+ } *Encoder;
16
+
17
+ extern VALUE Oj;
18
+
19
+ static struct _ROptTable ropts = { 0, 0, NULL };
20
+
21
+ static VALUE encoder_class = Qnil;
22
+ static bool escape_html = true;
23
+ static bool xml_time = true;
24
+
25
+ static ROpt create_opt(ROptTable rot, VALUE clas);
26
+
27
+ ROpt
28
+ oj_rails_get_opt(ROptTable rot, VALUE clas) {
29
+ if (NULL == rot) {
30
+ rot = &ropts;
31
+ }
32
+ if (0 < rot->len) {
33
+ int lo = 0;
34
+ int hi = rot->len - 1;
35
+ int mid;
36
+ VALUE v;
37
+
38
+ if (clas < rot->table->clas || rot->table[hi].clas < clas) {
39
+ return NULL;
40
+ }
41
+ if (rot->table[lo].clas == clas) {
42
+ return rot->table;
43
+ }
44
+ if (rot->table[hi].clas == clas) {
45
+ return &rot->table[hi];
46
+ }
47
+ while (2 <= hi - lo) {
48
+ mid = (hi + lo) / 2;
49
+ v = rot->table[mid].clas;
50
+ if (v == clas) {
51
+ return &rot->table[mid];
52
+ }
53
+ if (v < clas) {
54
+ lo = mid;
55
+ } else {
56
+ hi = mid;
57
+ }
58
+ }
59
+ }
60
+ return NULL;
61
+ }
62
+
63
+ static ROptTable
64
+ copy_opts(ROptTable src, ROptTable dest) {
65
+ dest->len = src->len;
66
+ dest->alen = src->alen;
67
+ if (NULL == src->table) {
68
+ dest->table = NULL;
69
+ } else {
70
+ dest->table = ALLOC_N(struct _ROpt, dest->alen);
71
+ memcpy(dest->table, src->table, sizeof(struct _ROpt) * dest->alen);
72
+ }
73
+ return NULL;
74
+ }
75
+
76
+ static int
77
+ dump_attr_cb(ID key, VALUE value, Out out) {
78
+ int depth = out->depth;
79
+ size_t size = depth * out->indent + 1;
80
+ const char *attr = rb_id2name(key);
81
+
82
+ #if HAS_EXCEPTION_MAGIC
83
+ if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
84
+ return ST_CONTINUE;
85
+ }
86
+ #endif
87
+ assure_size(out, size);
88
+ fill_indent(out, depth);
89
+ if ('@' == *attr) {
90
+ attr++;
91
+ oj_dump_cstr(attr, strlen(attr), 0, 0, out);
92
+ } else {
93
+ char buf[32];
94
+
95
+ *buf = '~';
96
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
97
+ buf[sizeof(buf) - 1] = '\0';
98
+ oj_dump_cstr(buf, strlen(buf), 0, 0, out);
99
+ }
100
+ *out->cur++ = ':';
101
+ oj_dump_rails_val(value, depth, out, true);
102
+ out->depth = depth;
103
+ *out->cur++ = ',';
104
+
105
+ return ST_CONTINUE;
106
+ }
107
+
108
+ static void
109
+ dump_obj_attrs(VALUE obj, int depth, Out out, bool as_ok) {
110
+ assure_size(out, 2);
111
+ *out->cur++ = '{';
112
+ out->depth = depth + 1;
113
+ rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out);
114
+ if (',' == *(out->cur - 1)) {
115
+ out->cur--; // backup to overwrite last comma
116
+ }
117
+ out->depth = depth;
118
+ fill_indent(out, depth);
119
+ *out->cur++ = '}';
120
+ *out->cur = '\0';
121
+ }
122
+
123
+ static void
124
+ dump_struct(VALUE obj, int depth, Out out, bool as_ok) {
125
+ int d3 = depth + 2;
126
+ size_t size = d3 * out->indent + 2;
127
+ size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
128
+ volatile VALUE ma;
129
+ volatile VALUE v;
130
+ int cnt;
131
+ int i;
132
+ int len;
133
+ const char *name;
134
+
135
+ #ifdef RSTRUCT_LEN
136
+ #if UNIFY_FIXNUM_AND_BIGNUM
137
+ cnt = (int)NUM2LONG(RSTRUCT_LEN(obj));
138
+ #else // UNIFY_FIXNUM_AND_INTEGER
139
+ cnt = (int)RSTRUCT_LEN(obj);
140
+ #endif // UNIFY_FIXNUM_AND_INTEGER
141
+ #else
142
+ // This is a bit risky as a struct in C ruby is not the same as a Struct
143
+ // class in interpreted Ruby so length() may not be defined.
144
+ cnt = FIX2INT(rb_funcall(obj, oj_length_id, 0));
145
+ #endif
146
+ ma = rb_struct_s_members(rb_obj_class(obj));
147
+ assure_size(out, 2);
148
+ *out->cur++ = '{';
149
+ for (i = 0; i < cnt; i++) {
150
+ name = rb_id2name(SYM2ID(rb_ary_entry(ma, i)));
151
+ len = strlen(name);
152
+ assure_size(out, size + sep_len + 6);
153
+ if (0 < i) {
154
+ *out->cur++ = ',';
155
+ }
156
+ fill_indent(out, d3);
157
+ *out->cur++ = '"';
158
+ memcpy(out->cur, name, len);
159
+ out->cur += len;
160
+ *out->cur++ = '"';
161
+ if (0 < out->opts->dump_opts.before_size) {
162
+ strcpy(out->cur, out->opts->dump_opts.before_sep);
163
+ out->cur += out->opts->dump_opts.before_size;
164
+ }
165
+ *out->cur++ = ':';
166
+ if (0 < out->opts->dump_opts.after_size) {
167
+ strcpy(out->cur, out->opts->dump_opts.after_sep);
168
+ out->cur += out->opts->dump_opts.after_size;
169
+ }
170
+ #ifdef RSTRUCT_LEN
171
+ v = RSTRUCT_GET(obj, i);
172
+ #else
173
+ v = rb_struct_aref(obj, INT2FIX(i));
174
+ #endif
175
+ oj_dump_rails_val(v, d3, out, true);
176
+ }
177
+ fill_indent(out, depth);
178
+ *out->cur++ = '}';
179
+ *out->cur = '\0';
180
+ }
181
+
182
+
183
+ static ID to_a_id = 0;
184
+
185
+ static void
186
+ dump_enumerable(VALUE obj, int depth, Out out, bool as_ok) {
187
+ if (0 == to_a_id) {
188
+ to_a_id = rb_intern("to_a");
189
+ }
190
+ oj_dump_rails_val(rb_funcall(obj, to_a_id, 0), depth, out, false);
191
+ }
192
+
193
+ static void
194
+ dump_bigdecimal(VALUE obj, int depth, Out out, bool as_ok) {
195
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
196
+ const char *str = rb_string_value_ptr((VALUE*)&rstr);
197
+
198
+ if ('I' == *str || 'N' == *str || ('-' == *str && 'I' == str[1])) {
199
+ oj_dump_nil(Qnil, depth, out, false);
200
+ } else {
201
+ oj_dump_cstr(str, RSTRING_LEN(rstr), 0, 0, out);
202
+ }
203
+ }
204
+
205
+ static void
206
+ dump_time(VALUE obj, int depth, Out out, bool as_ok) {
207
+ char buf[64];
208
+ struct tm *tm;
209
+ long one = 1000000000;
210
+ #if HAS_RB_TIME_TIMESPEC
211
+ struct timespec ts = rb_time_timespec(obj);
212
+ time_t sec = ts.tv_sec;
213
+ long nsec = ts.tv_nsec;
214
+ #else
215
+ time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
216
+ #if HAS_NANO_TIME
217
+ long long nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
218
+ #else
219
+ long long nsec = rb_num2ll(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000;
220
+ #endif
221
+ #endif
222
+ long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0));
223
+ int tzhour, tzmin;
224
+ char tzsign = '+';
225
+ int len;
226
+
227
+ if (out->end - out->cur <= 36) {
228
+ assure_size(out, 36);
229
+ }
230
+ if (9 > out->opts->sec_prec) {
231
+ int i;
232
+
233
+ // Rails does not round when reducing precision but instead floors,
234
+ for (i = 9 - out->opts->sec_prec; 0 < i; i--) {
235
+ nsec = nsec / 10;
236
+ one /= 10;
237
+ }
238
+ if (one <= nsec) {
239
+ nsec -= one;
240
+ sec++;
241
+ }
242
+ }
243
+ // 2012-01-05T23:58:07.123456000+09:00 or 2012/01/05 23:58:07 +0900
244
+ sec += tzsecs;
245
+ tm = gmtime(&sec);
246
+ #if 1
247
+ if (0 > tzsecs) {
248
+ tzsign = '-';
249
+ tzhour = (int)(tzsecs / -3600);
250
+ tzmin = (int)(tzsecs / -60) - (tzhour * 60);
251
+ } else {
252
+ tzhour = (int)(tzsecs / 3600);
253
+ tzmin = (int)(tzsecs / 60) - (tzhour * 60);
254
+ }
255
+ #else
256
+ if (0 > tm->tm_gmtoff) {
257
+ tzsign = '-';
258
+ tzhour = (int)(tm->tm_gmtoff / -3600);
259
+ tzmin = (int)(tm->tm_gmtoff / -60) - (tzhour * 60);
260
+ } else {
261
+ tzhour = (int)(tm->tm_gmtoff / 3600);
262
+ tzmin = (int)(tm->tm_gmtoff / 60) - (tzhour * 60);
263
+ }
264
+ #endif
265
+ if (!xml_time) {
266
+ len = sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d %c%02d%02d",
267
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
268
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tzsign, tzhour, tzmin);
269
+ } else if (0 == out->opts->sec_prec) {
270
+ if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) {
271
+ len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ",
272
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
273
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
274
+ } else {
275
+ len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
276
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
277
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
278
+ tzsign, tzhour, tzmin);
279
+ }
280
+ } else if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) {
281
+ char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ";
282
+
283
+ len = 30;
284
+ if (9 > out->opts->sec_prec) {
285
+ format[32] = '0' + out->opts->sec_prec;
286
+ len -= 9 - out->opts->sec_prec;
287
+ }
288
+ len = sprintf(buf, format,
289
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
290
+ tm->tm_hour, tm->tm_min, tm->tm_sec, nsec);
291
+ } else {
292
+ char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d";
293
+
294
+ len = 35;
295
+ if (9 > out->opts->sec_prec) {
296
+ format[32] = '0' + out->opts->sec_prec;
297
+ len -= 9 - out->opts->sec_prec;
298
+ }
299
+ len = sprintf(buf, format,
300
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
301
+ tm->tm_hour, tm->tm_min, tm->tm_sec, nsec,
302
+ tzsign, tzhour, tzmin);
303
+ }
304
+ oj_dump_cstr(buf, len, 0, 0, out);
305
+ }
306
+
307
+ static void
308
+ dump_to_s(VALUE obj, int depth, Out out, bool as_ok) {
309
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
310
+
311
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
312
+ }
313
+
314
+ typedef struct _NamedFunc {
315
+ const char *name;
316
+ DumpFunc func;
317
+ } *NamedFunc;
318
+
319
+ static struct _NamedFunc dump_map[] = {
320
+ { "BigDecimal", dump_bigdecimal },
321
+ { "Range", dump_to_s },
322
+ { "Regexp", dump_to_s },
323
+ { "Time", dump_time },
324
+ { NULL, NULL },
325
+ };
326
+
327
+ static ROpt
328
+ create_opt(ROptTable rot, VALUE clas) {
329
+ ROpt ro;
330
+ NamedFunc nf;
331
+ const char *classname = rb_class2name(clas);
332
+ int olen = rot->len;
333
+
334
+ rot->len++;
335
+ if (NULL == rot->table) {
336
+ rot->alen = 256;
337
+ rot->table = ALLOC_N(struct _ROpt, rot->alen);
338
+ memset(rot->table, 0, sizeof(struct _ROpt) * rot->alen);
339
+ } else if (rot->alen <= rot->len) {
340
+ rot->alen *= 2;
341
+ REALLOC_N(rot->table, struct _ROpt, rot->alen);
342
+ memset(rot->table + olen, 0, sizeof(struct _ROpt) * olen);
343
+ }
344
+ if (0 == olen) {
345
+ ro = rot->table;
346
+ } else if (rot->table[olen - 1].clas < clas) {
347
+ ro = &rot->table[olen];
348
+ } else {
349
+ int i;
350
+
351
+ for (i = 0, ro = rot->table; i < olen; i++, ro++) {
352
+ if (clas < ro->clas) {
353
+ memmove(ro + 1, ro, sizeof(struct _ROpt) * (olen - i));
354
+ break;
355
+ }
356
+ }
357
+ }
358
+ ro->clas = clas;
359
+ ro->on = true;
360
+ ro->dump = dump_obj_attrs;
361
+ for (nf = dump_map; NULL != nf->name; nf++) {
362
+ if (0 == strcmp(nf->name, classname)) {
363
+ ro->dump = nf->func;
364
+ break;
365
+ }
366
+ }
367
+ if (ro->dump == dump_obj_attrs) {
368
+ if (Qtrue == rb_class_inherited_p(clas, rb_cStruct)) { // check before enumerable
369
+ ro->dump = dump_struct;
370
+ } else if (Qtrue == rb_class_inherited_p(clas, rb_mEnumerable)) {
371
+ ro->dump = dump_enumerable;
372
+ } else if (Qtrue == rb_class_inherited_p(clas, rb_eException)) {
373
+ ro->dump = dump_to_s;
374
+ }
375
+ }
376
+ return NULL;
377
+ }
378
+
379
+ static void
380
+ encoder_free(void *ptr) {
381
+ if (NULL != ptr) {
382
+ Encoder e = (Encoder)ptr;
383
+
384
+ if (NULL != e->ropts.table) {
385
+ xfree(e->ropts.table);
386
+ }
387
+ xfree(ptr);
388
+ }
389
+ }
390
+
391
+ static void
392
+ encoder_mark(void *ptr) {
393
+ if (NULL != ptr) {
394
+ Encoder e = (Encoder)ptr;
395
+
396
+ if (Qnil != e->arg) {
397
+ rb_gc_mark(e->arg);
398
+ }
399
+ }
400
+ }
401
+
402
+ /* Document-method: new
403
+ * call-seq: new(options=nil)
404
+ *
405
+ * Creates a new Encoder.
406
+ * - *options* [_Hash_] formatting options
407
+ */
408
+ static VALUE
409
+ encoder_new(int argc, VALUE *argv, VALUE self) {
410
+ Encoder e = ALLOC(struct _Encoder);
411
+
412
+ e->opts = oj_default_options;
413
+ e->arg = Qnil;
414
+ copy_opts(&ropts, &e->ropts);
415
+
416
+ if (1 <= argc && Qnil != *argv) {
417
+ oj_parse_options(*argv, &e->opts);
418
+ e->arg = *argv;
419
+ }
420
+ return Data_Wrap_Struct(encoder_class, encoder_mark, encoder_free, e);
421
+ }
422
+
423
+ static void
424
+ optimize(int argc, VALUE *argv, ROptTable rot, bool on) {
425
+ ROpt ro;
426
+
427
+ if (0 == argc) {
428
+ int i;
429
+
430
+ for (i = 0; i < rot->len; i++) {
431
+ rot->table[i].on = on;
432
+ }
433
+ }
434
+ for (; 0 < argc; argc--, argv++) {
435
+ if (rb_cHash == *argv) {
436
+ oj_rails_hash_opt = on;
437
+ } else if (rb_cArray == *argv) {
438
+ oj_rails_array_opt = on;
439
+ } else if (NULL != (ro = oj_rails_get_opt(rot, *argv)) ||
440
+ NULL != (ro = create_opt(rot, *argv))) {
441
+ ro->on = on;
442
+ }
443
+ // TBD recurse if there are subclasses
444
+ }
445
+ }
446
+
447
+ /* Document-method optimize
448
+ * call-seq: optimize(*classes)
449
+ *
450
+ * Use Oj rails optimized routines to encode the specified classes. This
451
+ * ignores the as_json() method on the class and uses an internal encoding
452
+ * instead. Passing in no classes indicates all should use the optimized
453
+ * version of encoding for all previously optimized classes. Passing in the
454
+ * Object class set a global switch that will then use the optimized behavior
455
+ * for all classes.
456
+ *
457
+ * - *classes* [_Class_] a list of classes to optimize
458
+ */
459
+ static VALUE
460
+ encoder_optimize(int argc, VALUE *argv, VALUE self) {
461
+ Encoder e = (Encoder)DATA_PTR(self);
462
+
463
+ optimize(argc, argv, &e->ropts, true);
464
+
465
+ return Qnil;
466
+ }
467
+
468
+ /* Document-method: optimize
469
+ * call-seq: optimize(*classes)
470
+ *
471
+ * Use Oj rails optimized routines to encode the specified classes. This
472
+ * ignores the as_json() method on the class and uses an internal encoding
473
+ * instead. Passing in no classes indicates all should use the optimized
474
+ * version of encoding for all previously optimized classes. Passing in the
475
+ * Object class set a global switch that will then use the optimized behavior
476
+ * for all classes.
477
+ *
478
+ * - *classes* [_Class_] a list of classes to optimize
479
+ */
480
+ static VALUE
481
+ rails_optimize(int argc, VALUE *argv, VALUE self) {
482
+ optimize(argc, argv, &ropts, true);
483
+
484
+ return Qnil;
485
+ }
486
+
487
+ /* Document-method: deoptimize
488
+ * call-seq: deoptimize(*classes)
489
+ *
490
+ * Turn off Oj rails optimization on the specified classes.
491
+ *
492
+ * - *classes* [_Class_] a list of classes to deoptimize
493
+ */
494
+ static VALUE
495
+ encoder_deoptimize(int argc, VALUE *argv, VALUE self) {
496
+ Encoder e = (Encoder)DATA_PTR(self);
497
+
498
+ optimize(argc, argv, &e->ropts, false);
499
+
500
+ return Qnil;
501
+ }
502
+
503
+ /* Document-method: deoptimize
504
+ * call-seq: deoptimize(*classes)
505
+ *
506
+ * Turn off Oj rails optimization on the specified classes.
507
+ *
508
+ * - *classes* [_Class_] a list of classes to deoptimize
509
+ */
510
+ static VALUE
511
+ rails_deoptimize(int argc, VALUE *argv, VALUE self) {
512
+ optimize(argc, argv, &ropts, false);
513
+
514
+ return Qnil;
515
+ }
516
+
517
+ /* Document-method:optimized?
518
+ * call-seq: optimized?(clas)
519
+ *
520
+ * - *clas* [_Class_] Class to check
521
+ *
522
+ * @return true if the class is being optimized for rails and false otherwise
523
+ */
524
+ static VALUE
525
+ encoder_optimized(VALUE self, VALUE clas) {
526
+ Encoder e = (Encoder)DATA_PTR(self);
527
+ ROpt ro = oj_rails_get_opt(&e->ropts, clas);
528
+
529
+ if (NULL == ro) {
530
+ return Qfalse;
531
+ }
532
+ return (ro->on) ? Qtrue : Qfalse;
533
+ }
534
+
535
+ /* Document-method: optimized?
536
+ * call-seq: optimized?(clas)
537
+ *
538
+ * Returns true if the specified Class is being optimized.
539
+ */
540
+ static VALUE
541
+ rails_optimized(VALUE self, VALUE clas) {
542
+ ROpt ro = oj_rails_get_opt(&ropts, clas);
543
+
544
+ if (NULL == ro) {
545
+ return Qfalse;
546
+ }
547
+ return (ro->on) ? Qtrue : Qfalse;
548
+ }
549
+
550
+ typedef struct _OO {
551
+ Out out;
552
+ VALUE obj;
553
+ } *OO;
554
+
555
+ static VALUE
556
+ protect_dump(VALUE ov) {
557
+ OO oo = (OO)ov;
558
+
559
+ oj_dump_rails_val(oo->obj, 0, oo->out, true);
560
+
561
+ return Qnil;
562
+ }
563
+
564
+ static VALUE
565
+ encode(VALUE obj, ROptTable ropts, Options opts, int argc, VALUE *argv) {
566
+ char buf[4096];
567
+ struct _Out out;
568
+ struct _Options copts = *opts;
569
+ VALUE rstr = Qnil;
570
+ struct _OO oo;
571
+ int line = 0;
572
+
573
+ oo.out = &out;
574
+ oo.obj = obj;
575
+ copts.str_rx.head = NULL;
576
+ copts.str_rx.tail = NULL;
577
+ copts.mode = RailsMode;
578
+ if (escape_html) {
579
+ copts.escape_mode = JXEsc;
580
+ } else {
581
+ copts.escape_mode = RailsEsc;
582
+ }
583
+ out.buf = buf;
584
+ out.end = buf + sizeof(buf) - 10;
585
+ out.allocated = 0;
586
+ out.omit_nil = copts.dump_opts.omit_nil;
587
+ out.caller = 0;
588
+ out.cur = out.buf;
589
+ out.circ_cnt = 0;
590
+ out.opts = &copts;
591
+ out.hash_cnt = 0;
592
+ out.indent = copts.indent;
593
+ out.argc = argc;
594
+ out.argv = argv;
595
+ out.ropts = ropts;
596
+ if (Yes == copts.circular) {
597
+ oj_cache8_new(&out.circ_cache);
598
+ }
599
+ //oj_dump_rails_val(*argv, 0, &out, true);
600
+ rb_protect(protect_dump, (VALUE)&oo, &line);
601
+
602
+ if (0 == line) {
603
+ if (0 < out.indent) {
604
+ switch (*(out.cur - 1)) {
605
+ case ']':
606
+ case '}':
607
+ assure_size(&out, 2);
608
+ *out.cur++ = '\n';
609
+ default:
610
+ break;
611
+ }
612
+ }
613
+ *out.cur = '\0';
614
+
615
+ if (0 == out.buf) {
616
+ rb_raise(rb_eNoMemError, "Not enough memory.");
617
+ }
618
+ rstr = rb_str_new2(out.buf);
619
+ rstr = oj_encode(rstr);
620
+ }
621
+ if (Yes == copts.circular) {
622
+ oj_cache8_delete(out.circ_cache);
623
+ }
624
+ if (out.allocated) {
625
+ xfree(out.buf);
626
+ }
627
+ if (0 != line) {
628
+ rb_jump_tag(line);
629
+ }
630
+ return rstr;
631
+ }
632
+
633
+ /* Document-method: encode
634
+ * call-seq: encode(obj)
635
+ *
636
+ * - *obj* [_Object_] object to encode
637
+ *
638
+ * Returns encoded object as a JSON string.
639
+ */
640
+ static VALUE
641
+ encoder_encode(VALUE self, VALUE obj) {
642
+ Encoder e = (Encoder)DATA_PTR(self);
643
+
644
+ if (Qnil != e->arg) {
645
+ VALUE argv[1] = { e->arg };
646
+
647
+ return encode(obj, &e->ropts, &e->opts, 1, argv);
648
+ }
649
+ return encode(obj, &e->ropts, &e->opts, 0, NULL);
650
+ }
651
+
652
+ /* Document-method: encode
653
+ * call-seq: encode(obj, opts=nil)
654
+ *
655
+ * Encode obj as a JSON String.
656
+ *
657
+ * - *obj* [_Object_|Hash|Array] object to convert to a JSON String
658
+ * - *opts* [_Hash_] options
659
+ *
660
+ * Returns [_String_]
661
+ */
662
+ static VALUE
663
+ rails_encode(int argc, VALUE *argv, VALUE self) {
664
+ if (1 > argc) {
665
+ rb_raise(rb_eArgError, "wrong number of arguments (0 for 1).");
666
+ }
667
+ if (1 == argc) {
668
+ return encode(*argv, NULL, &oj_default_options, 0, NULL);
669
+ } else {
670
+ return encode(*argv, NULL, &oj_default_options, argc - 1, argv + 1);
671
+ }
672
+ }
673
+
674
+ static VALUE
675
+ rails_use_standard_json_time_format(VALUE self, VALUE state) {
676
+ switch (state) {
677
+ case Qtrue:
678
+ case Qfalse:
679
+ break;
680
+ case Qnil:
681
+ state = Qfalse;
682
+ break;
683
+ default:
684
+ state = Qtrue;
685
+ break;
686
+ }
687
+ rb_iv_set(self, "@use_standard_json_time_format", state);
688
+ xml_time = Qtrue == state;
689
+
690
+ return state;
691
+ }
692
+
693
+ static VALUE
694
+ rails_escape_html_entities_in_json(VALUE self, VALUE state) {
695
+ rb_iv_set(self, "@escape_html_entities_in_json", state);
696
+ escape_html = Qtrue == state;
697
+
698
+ return state;
699
+ }
700
+
701
+ static VALUE
702
+ rails_time_precision(VALUE self, VALUE prec) {
703
+ rb_iv_set(self, "@time_precision", prec);
704
+ oj_default_options.sec_prec = NUM2INT(prec);
705
+
706
+ return prec;
707
+ }
708
+
709
+ /* Document-method: set_encoder
710
+ *call-seq: set_encoder()
711
+ *
712
+ * Sets the ActiveSupport.encoder to Oj::Rails::Encoder and wraps some of the
713
+ * formatting globals used by ActiveSupport to allow the use of those globals
714
+ * in the Oj::Rails optimizations.
715
+ */
716
+ static VALUE
717
+ rails_set_encoder(VALUE self) {
718
+ VALUE active;
719
+ VALUE json;
720
+ VALUE encoding;
721
+ VALUE pv;
722
+
723
+ if (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport"))) {
724
+ active = rb_const_get_at(rb_cObject, rb_intern("ActiveSupport"));
725
+ } else {
726
+ rb_raise(rb_eStandardError, "ActiveSupport not loaded.");
727
+ }
728
+ rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
729
+
730
+ json = rb_const_get_at(active, rb_intern("JSON"));
731
+ encoding = rb_const_get_at(json, rb_intern("Encoding"));
732
+
733
+ rb_undef_method(active, "use_standard_json_time_format=");
734
+ rb_define_module_function(encoding, "use_standard_json_time_format=", rails_use_standard_json_time_format, 1);
735
+
736
+ rb_undef_method(encoding, "escape_html_entities_in_json=");
737
+ rb_define_module_function(encoding, "escape_html_entities_in_json=", rails_escape_html_entities_in_json, 1);
738
+
739
+ pv = rb_iv_get(encoding, "@time_precision");
740
+ oj_default_options.sec_prec = NUM2INT(pv);
741
+ rb_undef_method(encoding, "time_precision=");
742
+ rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
743
+
744
+ return Qnil;
745
+ }
746
+
747
+ /* Document-method: set_decoder
748
+ * call-seq: set_decoder()
749
+ *
750
+ * Sets the JSON.parse function to be the Oj::parse function which is json gem
751
+ * compatible.
752
+ */
753
+ static VALUE
754
+ rails_set_decoder(VALUE self) {
755
+ VALUE json;
756
+ VALUE json_error;
757
+
758
+ if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) {
759
+ json = rb_const_get_at(rb_cObject, rb_intern("JSON"));
760
+ } else {
761
+ json = rb_define_module("JSON");
762
+ }
763
+ if (rb_const_defined_at(json, rb_intern("JSONError"))) {
764
+ json_error = rb_const_get(json, rb_intern("JSONError"));
765
+ } else {
766
+ json_error = rb_define_class_under(json, "JSONError", rb_eStandardError);
767
+ }
768
+ if (rb_const_defined_at(json, rb_intern("ParserError"))) {
769
+ oj_json_parser_error_class = rb_const_get(json, rb_intern("ParserError"));
770
+ } else {
771
+ oj_json_parser_error_class = rb_define_class_under(json, "ParserError", json_error);
772
+ }
773
+ rb_undef_method(json, "parse");
774
+ rb_define_module_function(json, "parse", oj_mimic_parse, -1);
775
+
776
+ return Qnil;
777
+ }
778
+
779
+ /* Document-module: Oj::Rails
780
+ *
781
+ * Module that provides rails and active support compatibility.
782
+ */
783
+ /* Document-class: Oj::Rails::Encoder
784
+ *
785
+ * The Oj ActiveSupport compliant encoder.
786
+ */
787
+ void
788
+ oj_mimic_rails_init() {
789
+ VALUE rails = rb_define_module_under(Oj, "Rails");
790
+
791
+ rb_define_module_function(rails, "encode", rails_encode, -1);
792
+
793
+ encoder_class = rb_define_class_under(rails, "Encoder", rb_cObject);
794
+ rb_define_module_function(encoder_class, "new", encoder_new, -1);
795
+ rb_define_module_function(rails, "optimize", rails_optimize, -1);
796
+ rb_define_module_function(rails, "deoptimize", rails_deoptimize, -1);
797
+ rb_define_module_function(rails, "optimized?", rails_optimized, 1);
798
+
799
+ rb_define_module_function(rails, "set_encoder", rails_set_encoder, 0);
800
+ rb_define_module_function(rails, "set_decoder", rails_set_decoder, 0);
801
+
802
+ rb_define_method(encoder_class, "encode", encoder_encode, 1);
803
+ rb_define_method(encoder_class, "optimize", encoder_optimize, -1);
804
+ rb_define_method(encoder_class, "deoptimize", encoder_deoptimize, -1);
805
+ rb_define_method(encoder_class, "optimized?", encoder_optimized, 1);
806
+ }