oj 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +17 -23
  3. data/README.md +74 -425
  4. data/ext/oj/buf.h +103 -0
  5. data/ext/oj/cache8.c +4 -0
  6. data/ext/oj/circarray.c +68 -0
  7. data/ext/oj/circarray.h +23 -0
  8. data/ext/oj/code.c +227 -0
  9. data/ext/oj/code.h +40 -0
  10. data/ext/oj/compat.c +243 -0
  11. data/ext/oj/custom.c +1097 -0
  12. data/ext/oj/dump.c +766 -1534
  13. data/ext/oj/dump.h +92 -0
  14. data/ext/oj/dump_compat.c +937 -0
  15. data/ext/oj/dump_leaf.c +254 -0
  16. data/ext/oj/dump_object.c +810 -0
  17. data/ext/oj/dump_rails.c +329 -0
  18. data/ext/oj/dump_strict.c +416 -0
  19. data/ext/oj/encode.h +51 -0
  20. data/ext/oj/err.c +57 -0
  21. data/ext/oj/err.h +70 -0
  22. data/ext/oj/extconf.rb +17 -7
  23. data/ext/oj/fast.c +213 -180
  24. data/ext/oj/hash.c +163 -0
  25. data/ext/oj/hash.h +46 -0
  26. data/ext/oj/hash_test.c +512 -0
  27. data/ext/oj/mimic_json.c +817 -0
  28. data/ext/oj/mimic_rails.c +806 -0
  29. data/ext/oj/mimic_rails.h +17 -0
  30. data/ext/oj/object.c +752 -0
  31. data/ext/oj/odd.c +230 -0
  32. data/ext/oj/odd.h +44 -0
  33. data/ext/oj/oj.c +1288 -929
  34. data/ext/oj/oj.h +240 -69
  35. data/ext/oj/parse.c +1014 -0
  36. data/ext/oj/parse.h +92 -0
  37. data/ext/oj/reader.c +223 -0
  38. data/ext/oj/reader.h +151 -0
  39. data/ext/oj/resolve.c +127 -0
  40. data/ext/oj/{cache.h → resolve.h} +6 -13
  41. data/ext/oj/rxclass.c +133 -0
  42. data/ext/oj/rxclass.h +27 -0
  43. data/ext/oj/saj.c +77 -175
  44. data/ext/oj/scp.c +224 -0
  45. data/ext/oj/sparse.c +911 -0
  46. data/ext/oj/stream_writer.c +301 -0
  47. data/ext/oj/strict.c +162 -0
  48. data/ext/oj/string_writer.c +480 -0
  49. data/ext/oj/val_stack.c +98 -0
  50. data/ext/oj/val_stack.h +188 -0
  51. data/lib/oj/active_support_helper.rb +41 -0
  52. data/lib/oj/bag.rb +6 -10
  53. data/lib/oj/easy_hash.rb +52 -0
  54. data/lib/oj/json.rb +172 -0
  55. data/lib/oj/mimic.rb +260 -5
  56. data/lib/oj/saj.rb +13 -10
  57. data/lib/oj/schandler.rb +142 -0
  58. data/lib/oj/state.rb +131 -0
  59. data/lib/oj/version.rb +1 -1
  60. data/lib/oj.rb +11 -23
  61. data/pages/Advanced.md +22 -0
  62. data/pages/Compatibility.md +25 -0
  63. data/pages/Custom.md +23 -0
  64. data/pages/Encoding.md +65 -0
  65. data/pages/JsonGem.md +79 -0
  66. data/pages/Modes.md +140 -0
  67. data/pages/Options.md +250 -0
  68. data/pages/Rails.md +60 -0
  69. data/pages/Security.md +20 -0
  70. data/test/_test_active.rb +76 -0
  71. data/test/_test_active_mimic.rb +96 -0
  72. data/test/_test_mimic_rails.rb +126 -0
  73. data/test/activesupport4/decoding_test.rb +105 -0
  74. data/test/activesupport4/encoding_test.rb +531 -0
  75. data/test/activesupport4/test_helper.rb +41 -0
  76. data/test/activesupport5/decoding_test.rb +125 -0
  77. data/test/activesupport5/encoding_test.rb +483 -0
  78. data/test/activesupport5/encoding_test_cases.rb +90 -0
  79. data/test/activesupport5/test_helper.rb +50 -0
  80. data/test/activesupport5/time_zone_test_helpers.rb +24 -0
  81. data/test/helper.rb +27 -0
  82. data/test/isolated/shared.rb +310 -0
  83. data/test/isolated/test_mimic_after.rb +13 -0
  84. data/test/isolated/test_mimic_alone.rb +12 -0
  85. data/test/isolated/test_mimic_as_json.rb +45 -0
  86. data/test/isolated/test_mimic_before.rb +13 -0
  87. data/test/isolated/test_mimic_define.rb +28 -0
  88. data/test/isolated/test_mimic_rails_after.rb +22 -0
  89. data/test/isolated/test_mimic_rails_before.rb +21 -0
  90. data/test/isolated/test_mimic_redefine.rb +15 -0
  91. data/test/json_gem/json_addition_test.rb +216 -0
  92. data/test/json_gem/json_common_interface_test.rb +143 -0
  93. data/test/json_gem/json_encoding_test.rb +109 -0
  94. data/test/json_gem/json_ext_parser_test.rb +20 -0
  95. data/test/json_gem/json_fixtures_test.rb +35 -0
  96. data/test/json_gem/json_generator_test.rb +383 -0
  97. data/test/json_gem/json_generic_object_test.rb +90 -0
  98. data/test/json_gem/json_parser_test.rb +470 -0
  99. data/test/json_gem/json_string_matching_test.rb +42 -0
  100. data/test/json_gem/test_helper.rb +18 -0
  101. data/test/perf_compat.rb +130 -0
  102. data/test/perf_fast.rb +9 -9
  103. data/test/perf_file.rb +64 -0
  104. data/test/{perf_obj.rb → perf_object.rb} +24 -10
  105. data/test/perf_scp.rb +151 -0
  106. data/test/perf_strict.rb +32 -113
  107. data/test/sample.rb +2 -3
  108. data/test/test_compat.rb +474 -0
  109. data/test/test_custom.rb +355 -0
  110. data/test/test_debian.rb +53 -0
  111. data/test/test_fast.rb +66 -16
  112. data/test/test_file.rb +237 -0
  113. data/test/test_gc.rb +49 -0
  114. data/test/test_hash.rb +29 -0
  115. data/test/test_null.rb +376 -0
  116. data/test/test_object.rb +1010 -0
  117. data/test/test_saj.rb +16 -16
  118. data/test/test_scp.rb +417 -0
  119. data/test/test_strict.rb +410 -0
  120. data/test/test_various.rb +815 -0
  121. data/test/test_writer.rb +308 -0
  122. data/test/tests.rb +9 -902
  123. data/test/tests_mimic.rb +14 -0
  124. data/test/tests_mimic_addition.rb +7 -0
  125. metadata +253 -38
  126. data/ext/oj/cache.c +0 -148
  127. data/ext/oj/foo.rb +0 -6
  128. data/ext/oj/load.c +0 -1049
  129. data/test/a.rb +0 -38
  130. data/test/perf1.rb +0 -64
  131. data/test/perf2.rb +0 -76
  132. data/test/perf_obj_old.rb +0 -213
  133. data/test/test_mimic.rb +0 -208
@@ -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
+ }