oj 2.18.3 → 2.18.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc95283102d735d5d75c5e028656180772d68503
4
- data.tar.gz: a50e4cafdbd11e8af300dd1aa7ff05a0904904eb
3
+ metadata.gz: 875790efa9c4efd0fd2cc2e1b6ae5d09ffb55541
4
+ data.tar.gz: 3f321cf28069efd50b6f168e55e86445f2987f40
5
5
  SHA512:
6
- metadata.gz: b9165d68d3e5d0aec5a49c1fdf436361d6051ae9b7d07493a4b5db4d38c4dc711f8ebb5a2691963b38c8db564e5616553a82d0e9d2bb4606f155d8f99d90aed2
7
- data.tar.gz: 8c720169ce7c784c96645e7535c18f72f6dd27775a52a8d9d5d57219f12eedbbdd9d2b69521e5005505ea78a28496dc22554cdb0af62e5a7d7ceda37e6cc4274
6
+ metadata.gz: 7b42a692164cecf6b9767b2089d6699bb90fa54fa875be97d55a464f9e26c8455c71cc6b5df964473b941237c5784b247cb5c31c367206b593df007ecaa8eb7a
7
+ data.tar.gz: 49861fedc710aa30613c11f3e4ac521caea95355ffc917ea23ab2812a730e200ae3742d927ac693bd7b947032f21750482ab8a6fadd44d3307f06d1f646b404b
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Oj gem
2
- [![Build Status](https://secure.travis-ci.org/ohler55/oj.png?branch=master)](http://travis-ci.org/ohler55/oj)
2
+ [![Build Status](https://img.shields.io/travis/ohler55/oj/master.svg)](http://travis-ci.org/ohler55/oj?branch=master) ![Gem](https://img.shields.io/gem/v/oj.svg) ![Gem](https://img.shields.io/gem/dt/oj.svg)
3
3
 
4
4
  A fast JSON parser and Object marshaller as a Ruby gem.
5
5
 
@@ -74,110 +74,127 @@ actual default options but on a copy of the Hash:
74
74
  Oj.default_options = {:mode => :compat }
75
75
  ```
76
76
 
77
- * `:mode` [Symbol] mode for dumping and loading JSON. **Important format details [here](http://www.ohler.com/dev/oj_misc/encoding_format.html).**
77
+ ### Common (for serializer and parser) options
78
78
 
79
- - `:object` mode will dump any `Object` as a JSON `Object` with keys that
80
- match the Ruby `Object`'s variable names without the '@' prefix
81
- character. This mode has the best performance and is the default.
79
+ * `:mode` [Symbol] mode for dumping and loading JSON. **Important format details [here](http://www.ohler.com/dev/oj_misc/encoding_format.html)**
82
80
 
83
- - `:strict` mode will only allow the 7 basic JSON types to be serialized. Any
84
- other `Object` will raise an `Exception`.
81
+ - `:object` mode will dump any `Object` as a JSON `Object` with keys that
82
+ match the Ruby `Object`'s variable names without the '@' prefix
83
+ character. This mode has the best performance and is the default
85
84
 
86
- - `:null` mode replaces any `Object` that is not one of the JSON types with a JSON `null`.
85
+ - `:strict` mode will only allow the 7 basic JSON types to be serialized. Any
86
+ other `Object` will raise an `Exception`
87
87
 
88
- - `:compat` mode attempts to be compatible with other systems. It will
89
- serialize any `Object`, but will check to see if the `Object` implements an
90
- `to_hash` or `to_json` method. If either exists, that method is used for
91
- serializing the `Object`. Since `as_json` is more flexible and produces
92
- more consistent output, it is preferred over the `to_json` method. If
93
- neither the `to_json` or `to_hash` methods exists, then the Oj internal
94
- `Object` variable encoding is used.
88
+ - `:null` mode replaces any `Object` that is not one of the JSON types with a JSON `null`
89
+
90
+ - `:compat` mode attempts to be compatible with other systems. It will
91
+ serialize any `Object`, but will check to see if the `Object` implements an
92
+ `to_hash` or `to_json` method. If either exists, that method is used for
93
+ serializing the `Object`. Since `as_json` is more flexible and produces
94
+ more consistent output, it is preferred over the `to_json` method. If
95
+ neither the `to_json` or `to_hash` methods exists, then the Oj internal
96
+ `Object` variable encoding is used
97
+
98
+ * `:time_format` [Symbol] time format when dumping in :compat and :object mode
99
+
100
+ - `:unix` time is output as a decimal number in seconds since epoch including
101
+ fractions of a second.
102
+
103
+ - `:unix_zone` similar to the `:unix` format but with the timezone encoded in
104
+ the exponent of the decimal number of seconds since epoch.
105
+
106
+ - `:xmlschema` time is output as a string that follows the XML schema definition.
107
+
108
+ - `:ruby` time is output as a string formatted using the Ruby `to_s` conversion.
109
+
110
+ * `:second_precision` [Fixnum] number of digits after the decimal when dumping
111
+ the seconds portion of time
112
+
113
+ * `:bigdecimal_as_decimal` [Boolean] dump BigDecimal as a decimal number
114
+ or as a String
115
+
116
+ * `:create_id` [String] create id for json compatible object encoding,
117
+ default is `json_create`.
118
+
119
+ * `:allow_gc` [Boolean] allow or prohibit GC during parsing, default is
120
+ true (allow).
121
+
122
+ * `:quirks_mode` [Boolean] Allow single JSON values instead of
123
+ documents, default is true (allow).
124
+
125
+ * `:allow_invalid_unicode` [Boolean] Allow invalid unicode, default is
126
+ false (don't allow)
127
+
128
+ * `:omit_nil` [Boolean] If true, Hash and Object attributes with nil values
129
+ are omitted
130
+
131
+ ### Serializer options
95
132
 
96
133
  * `:indent` [Fixnum] number of spaces to indent each element in an JSON
97
134
  document, zero is no newline between JSON elements, negative indicates no
98
135
  newline between top level JSON elements in a stream
99
136
 
100
- * `:circular` [Boolean] support circular references while dumping.
101
-
137
+ * `:circular` [Boolean] support circular references while dumping
138
+
139
+ * `:escape_mode` [Symbol] determines the characters to escape
140
+
141
+ - `:newline` allows unescaped newlines in the output
142
+
143
+ - `:json` follows the JSON specification. This is the default mode
144
+
145
+ - `:xss_safe` escapes HTML and XML characters such as `&` and `<`
146
+
147
+ - `:ascii` escapes all non-ascii or characters with the hi-bit set
148
+
149
+ * `:use_to_json` [Boolean] call `to_json()` methods on dump, default is
150
+ false
151
+
152
+ * `:use_as_json` [Boolean] call `as_json()` methods on dump, default is
153
+ false
154
+
155
+ * `:nan` [Symbol] How to dump Infinity, -Infinity, and
156
+ NaN in :null, :strict, and :compat mode. Default is :auto
157
+
158
+ - `:null` places a null
159
+
160
+ - `:huge` places a huge number
161
+
162
+ - `:word` places Infinity or NaN
163
+
164
+ - `:raise` raises and exception
165
+
166
+ - `:auto` uses default for each mode which are `:raise` for `:strict`, `:null` for `:null`, and `:word` for `:compat`
167
+
168
+ ### Parser options
169
+
102
170
  * `:auto_define` [Boolean] automatically define classes if they do not
103
- exist.
104
-
105
- * `:symbol_keys` [Boolean] use symbols instead of strings for hash keys.
106
-
107
- * `:escape_mode` [Symbol] determines the characters to escape.
171
+ exist
108
172
 
109
- - `:newline` allows unescaped newlines in the output.
173
+ * `:symbol_keys` [Boolean] use symbols instead of strings for hash keys
110
174
 
111
- - `:json` follows the JSON specification. This is the default mode.
112
-
113
- - `:xss_safe` escapes HTML and XML characters such as `&` and `<`.
114
-
115
- - `:ascii` escapes all non-ascii or characters with the hi-bit set.
116
-
117
175
  * `:class_cache` [Boolean] cache classes for faster parsing (if
118
176
  dynamically modifying classes or reloading classes then don't use this)
119
-
120
- * `:time_format` [Symbol] time format when dumping in :compat and :object mode
121
-
122
- - `:unix` time is output as a decimal number in seconds since epoch including
123
- fractions of a second.
124
-
125
- - `:unix_zone` similar to the `:unix` format but with the timezone encoded in
126
- the exponent of the decimal number of seconds since epoch.
127
-
128
- - `:xmlschema` time is output as a string that follows the XML schema definition.
129
-
130
- - `:ruby` time is output as a string formatted using the Ruby `to_s` conversion.
131
177
 
132
- * `:bigdecimal_as_decimal` [Boolean] dump BigDecimal as a decimal number
133
- or as a String
134
-
135
178
  * `:bigdecimal_load` [Symbol] load decimals as BigDecimal instead of as a
136
- Float.
179
+ Float
180
+
181
+ - `:bigdecimal` convert all decimal numbers to BigDecimal
182
+
183
+ - `:float` convert all decimal numbers to Float
184
+
185
+ - `:auto` the most precise for the number of digits is used
137
186
 
138
- - `:bigdecimal` convert all decimal numbers to BigDecimal.
139
-
140
- - `:float` convert all decimal numbers to Float.
141
-
142
- - `:auto` the most precise for the number of digits is used.
143
-
144
- * `:create_id` [String] create id for json compatible object encoding,
145
- default is `json_create`.
146
-
147
- * `:second_precision` [Fixnum] number of digits after the decimal when dumping
148
- the seconds portion of time
149
-
150
187
  * `:float_precision` [Fixnum] number of digits of precision when dumping
151
188
  floats, 0 indicates use Ruby
152
-
153
- * `:use_to_json` [Boolean] call to_json() methods on dump, default is
154
- false
155
-
156
- * `:use_as_json` [Boolean] call as_json() methods on dump, default is
157
- false
158
-
189
+
159
190
  * `:nilnil` [Boolean] if true a nil input to load will return nil and
160
191
  not raise an Exception
161
-
162
- * `:allow_gc` [Boolean] allow or prohibit GC during parsing, default is
163
- true (allow).
164
-
165
- * `:quirks_mode` [Boolean] Allow single JSON values instead of
166
- documents, default is true (allow).
167
192
 
168
- * `:nan` [:null|:huge|:word|:raise|:auto] How to dump Infinity, -Infinity, and
169
- NaN in null, strict, and compat mode. :null places a null, :huge places a huge
170
- number, :word places Infinity or NaN, :raise raises and exception, :auto uses
171
- default for each mode which are :raise for :strict, :null for :null, and :word
172
- for :compat. Default is :auto.
173
-
174
- * `:allow_invalid_unicode` [Boolean] Allow invalid unicode, default is
175
- false (don't allow)
176
-
177
- * `:hash_class` [Class] Class to use instead of Hash on load
193
+ * `:empty_string` [Boolean] if true an empty input will not raise an
194
+ Exception, default is true (allow). When Oj.mimic_JSON is used,
195
+ default is false (raise exception when empty string is encountered)
178
196
 
179
- * `:omit_nil` [Boolean] If true, Hash and Object attributes with nil values
180
- are omitted
197
+ * `:hash_class` [Class] Class to use instead of Hash on load
181
198
 
182
199
  ## Releases
183
200
 
@@ -187,7 +204,7 @@ See [CHANGELOG.md](CHANGELOG.md)
187
204
 
188
205
  **Ruby**
189
206
 
190
- Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1, 2.2, 2.3 and RBX.
207
+ Oj is compatible with Ruby 1.9.3, 2.0.0, 2.1, 2.2, 2.3, 2.4 and RBX.
191
208
  Support for JRuby has been removed as JRuby no longer supports C extensions and
192
209
  there are bugs in the older versions that are not being fixed.
193
210
 
@@ -0,0 +1,759 @@
1
+ /* dump_object.c
2
+ * Copyright (c) 2012, 2017, Peter Ohler
3
+ * All rights reserved.
4
+ */
5
+
6
+ #include "dump.h"
7
+
8
+ static void
9
+ dump_time(VALUE obj, Out out) {
10
+ switch (out->opts->time_format) {
11
+ case RubyTime: oj_dump_obj_to_s(obj, out); break;
12
+ case XmlTime: oj_dump_xml_time(obj, out); break;
13
+ case UnixZTime: oj_dump_time(obj, out, 1); break;
14
+ case UnixTime:
15
+ default: oj_dump_time(obj, out, 0); break;
16
+ }
17
+ }
18
+
19
+ static int
20
+ hash_cb(VALUE key, VALUE value, Out out) {
21
+ int depth = out->depth;
22
+
23
+ if (out->omit_nil && Qnil == value) {
24
+ return ST_CONTINUE;
25
+ }
26
+ if (!out->opts->dump_opts.use) {
27
+ assure_size(out, depth * out->indent + 1);
28
+ fill_indent(out, depth);
29
+ } else {
30
+ assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1);
31
+ if (0 < out->opts->dump_opts.hash_size) {
32
+ strcpy(out->cur, out->opts->dump_opts.hash_nl);
33
+ out->cur += out->opts->dump_opts.hash_size;
34
+ }
35
+ if (0 < out->opts->dump_opts.indent_size) {
36
+ int i;
37
+ for (i = depth; 0 < i; i--) {
38
+ strcpy(out->cur, out->opts->dump_opts.indent_str);
39
+ out->cur += out->opts->dump_opts.indent_size;
40
+ }
41
+ }
42
+ }
43
+ switch (rb_type(key)) {
44
+ case T_STRING:
45
+ oj_dump_str(key, 0, out, false);
46
+ break;
47
+ case T_SYMBOL:
48
+ oj_dump_sym(key, 0, out, false);
49
+ break;
50
+ default:
51
+ /*rb_raise(rb_eTypeError, "In :compat mode all Hash keys must be Strings or Symbols, not %s.\n", rb_class2name(rb_obj_class(key)));*/
52
+ oj_dump_str(rb_funcall(key, oj_to_s_id, 0), 0, out, false);
53
+ break;
54
+ }
55
+ if (!out->opts->dump_opts.use) {
56
+ *out->cur++ = ':';
57
+ } else {
58
+ assure_size(out, out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2);
59
+ if (0 < out->opts->dump_opts.before_size) {
60
+ strcpy(out->cur, out->opts->dump_opts.before_sep);
61
+ out->cur += out->opts->dump_opts.before_size;
62
+ }
63
+ *out->cur++ = ':';
64
+ if (0 < out->opts->dump_opts.after_size) {
65
+ strcpy(out->cur, out->opts->dump_opts.after_sep);
66
+ out->cur += out->opts->dump_opts.after_size;
67
+ }
68
+ }
69
+ oj_dump_compat_val(value, depth, out, true);
70
+ out->depth = depth;
71
+ *out->cur++ = ',';
72
+
73
+ return ST_CONTINUE;
74
+ }
75
+
76
+ static void
77
+ dump_hash_class(VALUE obj, VALUE clas, int depth, Out out) {
78
+ int cnt;
79
+ size_t size;
80
+
81
+ cnt = (int)RHASH_SIZE(obj);
82
+ size = depth * out->indent + 2;
83
+ assure_size(out, 2);
84
+ if (0 == cnt) {
85
+ *out->cur++ = '{';
86
+ *out->cur++ = '}';
87
+ } else {
88
+ long id = oj_check_circular(obj, out);
89
+
90
+ if (0 > id) {
91
+ return;
92
+ }
93
+ *out->cur++ = '{';
94
+ if (0 < id) {
95
+ assure_size(out, size + 16);
96
+ fill_indent(out, depth + 1);
97
+ *out->cur++ = '"';
98
+ *out->cur++ = '^';
99
+ *out->cur++ = 'i';
100
+ *out->cur++ = '"';
101
+ *out->cur++ = ':';
102
+ dump_ulong(id, out);
103
+ *out->cur++ = ',';
104
+ }
105
+ out->depth = depth + 1;
106
+ rb_hash_foreach(obj, hash_cb, (VALUE)out);
107
+ if (',' == *(out->cur - 1)) {
108
+ out->cur--; // backup to overwrite last comma
109
+ }
110
+ if (!out->opts->dump_opts.use) {
111
+ assure_size(out, size);
112
+ fill_indent(out, depth);
113
+ } else {
114
+ size = depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1;
115
+ assure_size(out, size);
116
+ if (0 < out->opts->dump_opts.hash_size) {
117
+ strcpy(out->cur, out->opts->dump_opts.hash_nl);
118
+ out->cur += out->opts->dump_opts.hash_size;
119
+ }
120
+ if (0 < out->opts->dump_opts.indent_size) {
121
+ int i;
122
+
123
+ for (i = depth; 0 < i; i--) {
124
+ strcpy(out->cur, out->opts->dump_opts.indent_str);
125
+ out->cur += out->opts->dump_opts.indent_size;
126
+ }
127
+ }
128
+ }
129
+ *out->cur++ = '}';
130
+ }
131
+ *out->cur = '\0';
132
+ }
133
+
134
+ static void
135
+ dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
136
+ dump_hash_class(obj, rb_obj_class(obj), depth, out);
137
+ }
138
+
139
+ #if HAS_IVAR_HELPERS
140
+ static int
141
+ dump_attr_cb(ID key, VALUE value, Out out) {
142
+ int depth = out->depth;
143
+ size_t size = depth * out->indent + 1;
144
+ const char *attr = rb_id2name(key);
145
+
146
+ if (out->omit_nil && Qnil == value) {
147
+ return ST_CONTINUE;
148
+ }
149
+ #if HAS_EXCEPTION_MAGIC
150
+ if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
151
+ return ST_CONTINUE;
152
+ }
153
+ #endif
154
+ assure_size(out, size);
155
+ fill_indent(out, depth);
156
+ if ('@' == *attr) {
157
+ attr++;
158
+ oj_dump_cstr(attr, strlen(attr), 0, 0, out);
159
+ } else {
160
+ char buf[32];
161
+
162
+ *buf = '~';
163
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
164
+ buf[sizeof(buf) - 1] = '\0';
165
+ oj_dump_cstr(buf, strlen(buf), 0, 0, out);
166
+ }
167
+ *out->cur++ = ':';
168
+ oj_dump_comp_val(value, depth, out, 0, 0, true);
169
+ out->depth = depth;
170
+ *out->cur++ = ',';
171
+
172
+ return ST_CONTINUE;
173
+ }
174
+ #endif
175
+
176
+ static void
177
+ dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
178
+ size_t size = 0;
179
+ int d2 = depth + 1;
180
+ int cnt;
181
+
182
+ assure_size(out, 2);
183
+ *out->cur++ = '{';
184
+ {
185
+ #if HAS_IVAR_HELPERS
186
+ cnt = (int)rb_ivar_count(obj);
187
+ #else
188
+ volatile VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0);
189
+ VALUE *np = RARRAY_PTR(vars);
190
+ ID vid;
191
+ const char *attr;
192
+ int i;
193
+ int first = 1;
194
+
195
+ cnt = (int)RARRAY_LEN(vars);
196
+ #endif
197
+ if (Qundef != clas && 0 < cnt) {
198
+ *out->cur++ = ',';
199
+ }
200
+ if (0 == cnt && Qundef == clas) {
201
+ // Might be something special like an Enumerable.
202
+ if (Qtrue == rb_obj_is_kind_of(obj, oj_enumerable_class)) {
203
+ out->cur--;
204
+ oj_dump_compat_val(rb_funcall(obj, rb_intern("entries"), 0), depth, out, false);
205
+ return;
206
+ }
207
+ }
208
+ out->depth = depth + 1;
209
+ #if HAS_IVAR_HELPERS
210
+ rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out);
211
+ if (',' == *(out->cur - 1)) {
212
+ out->cur--; // backup to overwrite last comma
213
+ }
214
+ #else
215
+ size = d2 * out->indent + 1;
216
+ for (i = cnt; 0 < i; i--, np++) {
217
+ VALUE value;
218
+
219
+ vid = rb_to_id(*np);
220
+ attr = rb_id2name(vid);
221
+ value = rb_ivar_get(obj, vid);
222
+ if (out->omit_nil && Qnil == value) {
223
+ continue;
224
+ }
225
+ if (first) {
226
+ first = 0;
227
+ } else {
228
+ *out->cur++ = ',';
229
+ }
230
+ assure_size(out, size);
231
+ fill_indent(out, d2);
232
+ if ('@' == *attr) {
233
+ attr++;
234
+ oj_dump_cstr(attr, strlen(attr), 0, 0, out);
235
+ } else {
236
+ char buf[32];
237
+
238
+ *buf = '~';
239
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
240
+ buf[sizeof(buf) - 1] = '\0';
241
+ oj_dump_cstr(buf, strlen(attr) + 1, 0, 0, out);
242
+ }
243
+ *out->cur++ = ':';
244
+ oj_dump_compat_val(value, d2, out, 0, 0, true);
245
+ assure_size(out, 2);
246
+ }
247
+ #endif
248
+ #if HAS_EXCEPTION_MAGIC
249
+ if (rb_obj_is_kind_of(obj, rb_eException)) {
250
+ volatile VALUE rv;
251
+
252
+ if (',' != *(out->cur - 1)) {
253
+ *out->cur++ = ',';
254
+ }
255
+ // message
256
+ assure_size(out, 2);
257
+ fill_indent(out, d2);
258
+ oj_dump_cstr("~mesg", 5, 0, 0, out);
259
+ *out->cur++ = ':';
260
+ rv = rb_funcall2(obj, rb_intern("message"), 0, 0);
261
+ oj_dump_compat_val(rv, d2, out, true);
262
+ assure_size(out, size + 2);
263
+ *out->cur++ = ',';
264
+ // backtrace
265
+ fill_indent(out, d2);
266
+ oj_dump_cstr("~bt", 3, 0, 0, out);
267
+ *out->cur++ = ':';
268
+ rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0);
269
+ oj_dump_compat_val(rv, d2, out, true);
270
+ assure_size(out, 2);
271
+ }
272
+ #endif
273
+ out->depth = depth;
274
+ }
275
+ fill_indent(out, depth);
276
+ *out->cur++ = '}';
277
+ *out->cur = '\0';
278
+ }
279
+
280
+ static void
281
+ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
282
+ if (as_ok && Yes == out->opts->to_json && rb_respond_to(obj, oj_to_hash_id)) {
283
+ volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
284
+
285
+ if (T_HASH != rb_type(h)) {
286
+ // It seems that ActiveRecord implemented to_hash so that it returns
287
+ // an Array and not a Hash. To get around that any value returned
288
+ // will be dumped.
289
+
290
+ //rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
291
+ oj_dump_compat_val(h, depth, out, false);
292
+ } else {
293
+ dump_hash_class(h, Qundef, depth, out);
294
+ }
295
+ } else if (as_ok && Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) {
296
+ volatile VALUE aj;
297
+
298
+ #if HAS_METHOD_ARITY
299
+ // Some classes elect to not take an options argument so check the arity
300
+ // of as_json.
301
+ switch (rb_obj_method_arity(obj, oj_as_json_id)) {
302
+ case 0:
303
+ aj = rb_funcall2(obj, oj_as_json_id, 0, 0);
304
+ break;
305
+ case 1:
306
+ if (1 <= out->argc) {
307
+ aj = rb_funcall2(obj, oj_as_json_id, 1, (VALUE*)out->argv);
308
+ } else {
309
+ VALUE nothing [1];
310
+
311
+ nothing[0] = Qnil;
312
+ aj = rb_funcall2(obj, oj_as_json_id, 1, nothing);
313
+ }
314
+ break;
315
+ default:
316
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
317
+ break;
318
+ }
319
+ #else
320
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
321
+ #endif
322
+ // Catch the obvious brain damaged recursive dumping.
323
+ if (aj == obj) {
324
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
325
+
326
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), false, false, out);
327
+ } else {
328
+ oj_dump_compat_val(aj, depth, out, false);
329
+ }
330
+ } else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
331
+ volatile VALUE rs;
332
+ const char *s;
333
+ int len;
334
+
335
+ rs = rb_funcall(obj, oj_to_json_id, 0);
336
+ s = rb_string_value_ptr((VALUE*)&rs);
337
+ len = (int)RSTRING_LEN(rs);
338
+
339
+ assure_size(out, len + 1);
340
+ memcpy(out->cur, s, len);
341
+ out->cur += len;
342
+ *out->cur = '\0';
343
+ } else {
344
+ VALUE clas = rb_obj_class(obj);
345
+
346
+ if (oj_bigdecimal_class == clas) {
347
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
348
+ const char *str = rb_string_value_ptr((VALUE*)&rstr);
349
+ int len = RSTRING_LEN(rstr);
350
+
351
+ if (Yes == out->opts->bigdec_as_num) {
352
+ oj_dump_raw(str, len, out);
353
+ } else if (0 == strcasecmp("Infinity", str)) {
354
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len);
355
+ oj_dump_raw(str, len, out);
356
+ } else if (0 == strcasecmp("-Infinity", str)) {
357
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len);
358
+ oj_dump_raw(str, len, out);
359
+ } else {
360
+ oj_dump_cstr(str, len, 0, 0, out);
361
+ }
362
+ #if (defined T_RATIONAL && defined RRATIONAL)
363
+ } else if (oj_datetime_class == clas || oj_date_class == clas || rb_cRational == clas) {
364
+ #else
365
+ } else if (oj_datetime_class == clas || oj_date_class == clas) {
366
+ #endif
367
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
368
+
369
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
370
+ } else {
371
+ dump_obj_attrs(obj, Qundef, 0, depth, out);
372
+ }
373
+ }
374
+ *out->cur = '\0';
375
+ }
376
+
377
+ static void
378
+ dump_array(VALUE a, int depth, Out out, bool as_ok) {
379
+ size_t size;
380
+ int i, cnt;
381
+ int d2 = depth + 1;
382
+ long id = oj_check_circular(a, out);
383
+
384
+ if (id < 0) {
385
+ return;
386
+ }
387
+ cnt = (int)RARRAY_LEN(a);
388
+ *out->cur++ = '[';
389
+ if (0 < id) {
390
+ oj_dump_nil(Qnil, 0, out, false);
391
+ }
392
+ size = 2;
393
+ assure_size(out, size);
394
+ if (0 == cnt) {
395
+ *out->cur++ = ']';
396
+ } else {
397
+ if (0 < id) {
398
+ *out->cur++ = ',';
399
+ }
400
+ if (out->opts->dump_opts.use) {
401
+ size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
402
+ } else {
403
+ size = d2 * out->indent + 2;
404
+ }
405
+ cnt--;
406
+ for (i = 0; i <= cnt; i++) {
407
+ assure_size(out, size);
408
+ if (out->opts->dump_opts.use) {
409
+ if (0 < out->opts->dump_opts.array_size) {
410
+ strcpy(out->cur, out->opts->dump_opts.array_nl);
411
+ out->cur += out->opts->dump_opts.array_size;
412
+ }
413
+ if (0 < out->opts->dump_opts.indent_size) {
414
+ int i;
415
+ for (i = d2; 0 < i; i--) {
416
+ strcpy(out->cur, out->opts->dump_opts.indent_str);
417
+ out->cur += out->opts->dump_opts.indent_size;
418
+ }
419
+ }
420
+ } else {
421
+ fill_indent(out, d2);
422
+ }
423
+ oj_dump_comp_val(rb_ary_entry(a, i), d2, out, 0, 0, true);
424
+ if (i < cnt) {
425
+ *out->cur++ = ',';
426
+ }
427
+ }
428
+ size = depth * out->indent + 1;
429
+ assure_size(out, size);
430
+ if (out->opts->dump_opts.use) {
431
+ //printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, out->opts->dump_opts->indent);
432
+ if (0 < out->opts->dump_opts.array_size) {
433
+ strcpy(out->cur, out->opts->dump_opts.array_nl);
434
+ out->cur += out->opts->dump_opts.array_size;
435
+ }
436
+ if (0 < out->opts->dump_opts.indent_size) {
437
+ int i;
438
+
439
+ for (i = depth; 0 < i; i--) {
440
+ strcpy(out->cur, out->opts->dump_opts.indent_str);
441
+ out->cur += out->opts->dump_opts.indent_size;
442
+ }
443
+ }
444
+ } else {
445
+ fill_indent(out, depth);
446
+ }
447
+ *out->cur++ = ']';
448
+ }
449
+ *out->cur = '\0';
450
+ }
451
+
452
+ static void
453
+ dump_struct(VALUE obj, int depth, Out out, bool as_ok) {
454
+ if (as_ok && Yes == out->opts->to_json && rb_respond_to(obj, oj_to_hash_id)) {
455
+ volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
456
+
457
+ if (T_HASH != rb_type(h)) {
458
+ // It seems that ActiveRecord implemented to_hash so that it
459
+ // returns an Array and not a Hash. To get around that any value
460
+ // returned will be dumped.
461
+
462
+ //rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
463
+ oj_dump_comp_val(h, depth, out, 0, 0, false);
464
+ }
465
+ dump_hash_class(h, Qundef, depth, out);
466
+ } else if (as_ok && Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) {
467
+ volatile VALUE aj;
468
+
469
+ #if HAS_METHOD_ARITY
470
+ // Some classes elect to not take an options argument so check the arity
471
+ // of as_json.
472
+ switch (rb_obj_method_arity(obj, oj_as_json_id)) {
473
+ case 0:
474
+ aj = rb_funcall2(obj, oj_as_json_id, 0, 0);
475
+ break;
476
+ case 1:
477
+ if (1 <= out->argc) {
478
+ aj = rb_funcall2(obj, oj_as_json_id, 1, out->argv);
479
+ } else {
480
+ VALUE nothing [1];
481
+
482
+ nothing[0] = Qnil;
483
+ aj = rb_funcall2(obj, oj_as_json_id, 1, nothing);
484
+ }
485
+ break;
486
+ default:
487
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
488
+ break;
489
+ }
490
+ #else
491
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
492
+ #endif
493
+ // Catch the obvious brain damaged recursive dumping.
494
+ if (aj == obj) {
495
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
496
+
497
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), false, false, out);
498
+ } else {
499
+ oj_dump_compat_val(aj, depth, out, false);
500
+ }
501
+ } else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
502
+ volatile VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
503
+ const char *s;
504
+ int len;
505
+
506
+ s = rb_string_value_ptr((VALUE*)&rs);
507
+ len = (int)RSTRING_LEN(rs);
508
+ assure_size(out, len);
509
+ memcpy(out->cur, s, len);
510
+ out->cur += len;
511
+ } else {
512
+ VALUE clas = rb_obj_class(obj);
513
+ VALUE ma = Qnil;
514
+ VALUE v;
515
+ char num_id[32];
516
+ int i;
517
+ int d2 = depth + 1;
518
+ int d3 = d2 + 1;
519
+ size_t size = d2 * out->indent + d3 * out->indent + 3;
520
+ const char *name;
521
+ int cnt;
522
+ size_t len;
523
+
524
+ assure_size(out, size);
525
+ if (clas == rb_cRange) {
526
+ *out->cur++ = '"';
527
+ oj_dump_comp_val(rb_funcall(obj, oj_begin_id, 0), d3, out, 0, 0, false);
528
+ assure_size(out, 3);
529
+ *out->cur++ = '.';
530
+ *out->cur++ = '.';
531
+ if (Qtrue == rb_funcall(obj, oj_exclude_end_id, 0)) {
532
+ *out->cur++ = '.';
533
+ }
534
+ oj_dump_comp_val(rb_funcall(obj, oj_end_id, 0), d3, out, 0, 0, false);
535
+ *out->cur++ = '"';
536
+
537
+ return;
538
+ }
539
+ *out->cur++ = '{';
540
+ fill_indent(out, d2);
541
+ size = d3 * out->indent + 2;
542
+ #if HAS_STRUCT_MEMBERS
543
+ ma = rb_struct_s_members(clas);
544
+ #endif
545
+
546
+ #ifdef RSTRUCT_LEN
547
+ #if UNIFY_FIXNUM_AND_BIGNUM
548
+ cnt = (int)NUM2LONG(RSTRUCT_LEN(obj));
549
+ #else // UNIFY_FIXNUM_AND_INTEGER
550
+ cnt = (int)RSTRUCT_LEN(obj);
551
+ #endif // UNIFY_FIXNUM_AND_INTEGER
552
+ #else
553
+ // This is a bit risky as a struct in C ruby is not the same as a Struct
554
+ // class in interpreted Ruby so length() may not be defined.
555
+ cnt = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0));
556
+ #endif
557
+ for (i = 0; i < cnt; i++) {
558
+ #ifdef RSTRUCT_LEN
559
+ v = RSTRUCT_GET(obj, i);
560
+ #else
561
+ v = rb_struct_aref(obj, INT2FIX(i));
562
+ #endif
563
+ if (ma != Qnil) {
564
+ name = rb_id2name(SYM2ID(rb_ary_entry(ma, i)));
565
+ len = strlen(name);
566
+ } else {
567
+ len = snprintf(num_id, sizeof(num_id), "%d", i);
568
+ name = num_id;
569
+ }
570
+ assure_size(out, size + len + 3);
571
+ fill_indent(out, d3);
572
+ *out->cur++ = '"';
573
+ memcpy(out->cur, name, len);
574
+ out->cur += len;
575
+ *out->cur++ = '"';
576
+ *out->cur++ = ':';
577
+ oj_dump_comp_val(v, d3, out, 0, 0, true);
578
+ *out->cur++ = ',';
579
+ }
580
+ out->cur--;
581
+ *out->cur++ = '}';
582
+ *out->cur = '\0';
583
+ }
584
+ }
585
+
586
+ static void
587
+ dump_data(VALUE obj, int depth, Out out, bool as_ok) {
588
+ VALUE clas = rb_obj_class(obj);
589
+
590
+ if (as_ok && Yes == out->opts->to_json && rb_respond_to(obj, oj_to_hash_id)) {
591
+ volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
592
+
593
+ if (T_HASH != rb_type(h)) {
594
+ // It seems that ActiveRecord implemented to_hash so that it returns
595
+ // an Array and not a Hash. To get around that any value returned
596
+ // will be dumped.
597
+
598
+ //rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
599
+ oj_dump_compat_val(h, depth, out, false);
600
+ }
601
+ dump_hash_class(h, Qundef, depth, out);
602
+ } else if (Yes == out->opts->bigdec_as_num && oj_bigdecimal_class == clas) {
603
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
604
+ const char *str = rb_string_value_ptr((VALUE*)&rstr);
605
+ int len = RSTRING_LEN(rstr);
606
+
607
+ if (0 == strcasecmp("Infinity", str)) {
608
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len);
609
+ oj_dump_raw(str, len, out);
610
+ } else if (0 == strcasecmp("-Infinity", str)) {
611
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len);
612
+ oj_dump_raw(str, len, out);
613
+ } else {
614
+ oj_dump_raw(str, len, out);
615
+ }
616
+ } else if (as_ok && Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) {
617
+ volatile VALUE aj;
618
+
619
+ #if HAS_METHOD_ARITY
620
+ // Some classes elect to not take an options argument so check the arity
621
+ // of as_json.
622
+ switch (rb_obj_method_arity(obj, oj_as_json_id)) {
623
+ case 0:
624
+ aj = rb_funcall2(obj, oj_as_json_id, 0, 0);
625
+ break;
626
+ case 1:
627
+ if (1 <= out->argc) {
628
+ aj = rb_funcall2(obj, oj_as_json_id, 1, out->argv);
629
+ } else {
630
+ VALUE nothing [1];
631
+
632
+ nothing[0] = Qnil;
633
+ aj = rb_funcall2(obj, oj_as_json_id, 1, nothing);
634
+ }
635
+ break;
636
+ default:
637
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
638
+ break;
639
+ }
640
+ #else
641
+ aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
642
+ #endif
643
+ // Catch the obvious brain damaged recursive dumping.
644
+ if (aj == obj) {
645
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
646
+
647
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
648
+ } else {
649
+ oj_dump_comp_val(aj, depth, out, 0, 0, false);
650
+ }
651
+ } else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
652
+ volatile VALUE rs;
653
+ const char *s;
654
+ int len;
655
+
656
+ rs = rb_funcall(obj, oj_to_json_id, 0);
657
+ s = rb_string_value_ptr((VALUE*)&rs);
658
+ len = (int)RSTRING_LEN(rs);
659
+
660
+ assure_size(out, len + 1);
661
+ memcpy(out->cur, s, len);
662
+ out->cur += len;
663
+ *out->cur = '\0';
664
+ } else {
665
+ if (rb_cTime == clas) {
666
+ dump_time(obj, out);
667
+ } else if (oj_bigdecimal_class == clas) {
668
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
669
+ const char *str = rb_string_value_ptr((VALUE*)&rstr);
670
+ int len = RSTRING_LEN(rstr);
671
+
672
+ if (0 == strcasecmp("Infinity", str)) {
673
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len);
674
+ oj_dump_raw(str, len, out);
675
+ } else if (0 == strcasecmp("-Infinity", str)) {
676
+ str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len);
677
+ oj_dump_raw(str, len, out);
678
+ } else {
679
+ oj_dump_cstr(str, len, 0, 0, out);
680
+ }
681
+ } else if (oj_datetime_class == clas) {
682
+ volatile VALUE rstr;
683
+
684
+ switch (out->opts->time_format) {
685
+ case XmlTime:
686
+ rstr = rb_funcall(obj, rb_intern("xmlschema"), 1, INT2FIX(out->opts->sec_prec));
687
+ break;
688
+ case UnixZTime:
689
+ case UnixTime:
690
+ case RubyTime:
691
+ default:
692
+ rstr = rb_funcall(obj, oj_to_s_id, 0);
693
+ }
694
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
695
+ } else {
696
+ volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
697
+
698
+ oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
699
+ }
700
+ }
701
+ }
702
+
703
+ static void
704
+ dump_regexp(VALUE obj, int depth, Out out, bool as_ok) {
705
+ oj_dump_obj_to_s(obj, out);
706
+ }
707
+
708
+ static void
709
+ dump_complex(VALUE obj, int depth, Out out, bool as_ok) {
710
+ oj_dump_obj_to_s(obj, out);
711
+ }
712
+
713
+ static void
714
+ dump_rational(VALUE obj, int depth, Out out, bool as_ok) {
715
+ oj_dump_obj_to_s(obj, out);
716
+ }
717
+
718
+ static DumpFunc custom_funcs[] = {
719
+ NULL, // RUBY_T_NONE = 0x00,
720
+ dump_obj, // RUBY_T_OBJECT = 0x01,
721
+ oj_dump_class, // RUBY_T_CLASS = 0x02,
722
+ oj_dump_class, // RUBY_T_MODULE = 0x03,
723
+ oj_dump_float, // RUBY_T_FLOAT = 0x04,
724
+ oj_dump_str, // RUBY_T_STRING = 0x05,
725
+ dump_regexp, // RUBY_T_REGEXP = 0x06,
726
+ dump_array, // RUBY_T_ARRAY = 0x07,
727
+ dump_hash, // RUBY_T_HASH = 0x08,
728
+ dump_struct, // RUBY_T_STRUCT = 0x09,
729
+ oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a,
730
+ NULL, // RUBY_T_FILE = 0x0b,
731
+ dump_data, // RUBY_T_DATA = 0x0c,
732
+ NULL, // RUBY_T_MATCH = 0x0d,
733
+ dump_complex, // RUBY_T_COMPLEX = 0x0e,
734
+ dump_rational, // RUBY_T_RATIONAL = 0x0f,
735
+ NULL, // 0x10
736
+ oj_dump_nil, // RUBY_T_NIL = 0x11,
737
+ oj_dump_true, // RUBY_T_TRUE = 0x12,
738
+ oj_dump_false, // RUBY_T_FALSE = 0x13,
739
+ oj_dump_sym, // RUBY_T_SYMBOL = 0x14,
740
+ oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15,
741
+ };
742
+
743
+ void
744
+ oj_dump_custom_val(VALUE obj, int depth, Out out, bool as_ok) {
745
+ int type = rb_type(obj);
746
+
747
+ if (MAX_DEPTH < depth) {
748
+ rb_raise(rb_eNoMemError, "Too deeply nested.\n");
749
+ }
750
+ if (0 < type && type <= RUBY_T_FIXNUM) {
751
+ DumpFunc f = custom_funcs[type];
752
+
753
+ if (NULL != f) {
754
+ f(obj, depth, out, true);
755
+ return;
756
+ }
757
+ }
758
+ oj_dump_nil(Qnil, depth, out, false);
759
+ }