iodine 0.7.59 → 0.8.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.
- checksums.yaml +4 -4
- data/.clang-format +15 -0
- data/.rspec +1 -0
- data/.yardopts +1 -3
- data/CHANGELOG.md +132 -12
- data/Gemfile +5 -7
- data/LICENSE.txt +1 -1
- data/README.md +147 -671
- data/Rakefile +9 -16
- data/examples/chatroom.nru +9 -0
- data/examples/client +74 -0
- data/examples/config-hello.nru +9 -0
- data/examples/config-hello.ru +11 -0
- data/examples/config-hijack.ru +13 -0
- data/examples/config-shootout.ru +43 -0
- data/examples/config.nru +120 -0
- data/examples/config.rb +37 -0
- data/examples/config.ru +42 -53
- data/examples/fake +24 -0
- data/examples/hello_neorack +137 -0
- data/examples/routes.nru +29 -0
- data/examples/routes.ru +42 -0
- data/exe/iodine +218 -258
- data/ext/iodine/extconf.rb +27 -100
- data/ext/iodine/fio-stl.h +128336 -0
- data/ext/iodine/fio-stl.md +26396 -0
- data/ext/iodine/iodine.c +213 -1393
- data/ext/iodine/iodine.h +250 -55
- data/ext/iodine/iodine_arg_helper.h +329 -0
- data/ext/iodine/iodine_caller.h +234 -25
- data/ext/iodine/iodine_cli.h +434 -0
- data/ext/iodine/iodine_compression.h +312 -0
- data/ext/iodine/iodine_connection.h +3484 -34
- data/ext/iodine/iodine_core.h +363 -0
- data/ext/iodine/iodine_crypto.h +1441 -0
- data/ext/iodine/iodine_defer.h +225 -4
- data/ext/iodine/iodine_json.h +738 -4
- data/ext/iodine/iodine_listener.h +263 -0
- data/ext/iodine/iodine_log.h +297 -0
- data/ext/iodine/iodine_minimap.h +543 -0
- data/ext/iodine/iodine_mustache.h +461 -4
- data/ext/iodine/iodine_pubsub_eng.h +729 -0
- data/ext/iodine/iodine_pubsub_history.h +547 -0
- data/ext/iodine/iodine_pubsub_msg.h +228 -0
- data/ext/iodine/iodine_redis.h +322 -0
- data/ext/iodine/iodine_store.h +418 -16
- data/ext/iodine/iodine_threads.h +279 -0
- data/ext/iodine/iodine_tls.h +515 -6
- data/ext/iodine/iodine_utils.h +923 -0
- data/iodine.gemspec +37 -42
- data/lib/iodine/benchmark.rb +1690 -0
- data/lib/iodine/documentation.rb +3085 -0
- data/lib/iodine/version.rb +17 -1
- data/lib/iodine.rb +54 -255
- data/logo.svg +36 -0
- data/mustache.md +55 -0
- data/sig/iodine.rbs +4 -0
- metadata +58 -184
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/workflows/ruby.yml +0 -42
- data/.gitignore +0 -20
- data/LIMITS.md +0 -41
- data/SPEC-PubSub-Draft.md +0 -159
- data/SPEC-WebSocket-Draft.md +0 -239
- data/bin/console +0 -22
- data/bin/info.md +0 -353
- data/bin/mustache_bench.rb +0 -100
- data/bin/poc/Gemfile.lock +0 -23
- data/bin/poc/README.md +0 -37
- data/bin/poc/config.ru +0 -66
- data/bin/poc/gemfile +0 -1
- data/bin/poc/www/index.html +0 -57
- data/examples/async_task.ru +0 -92
- data/examples/bates/README.md +0 -3
- data/examples/bates/config.ru +0 -342
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +0 -600
- data/examples/echo.ru +0 -59
- data/examples/etag.ru +0 -16
- data/examples/hello.ru +0 -29
- data/examples/pubsub_engine.ru +0 -81
- data/examples/rack3.ru +0 -12
- data/examples/redis.ru +0 -70
- data/examples/shootout.ru +0 -73
- data/examples/sub-protocols.ru +0 -90
- data/examples/tcp_client.rb +0 -66
- data/examples/x-sendfile.ru +0 -14
- data/ext/iodine/fio.c +0 -11995
- data/ext/iodine/fio.h +0 -6371
- data/ext/iodine/fio_cli.c +0 -431
- data/ext/iodine/fio_cli.h +0 -189
- data/ext/iodine/fio_json_parser.h +0 -688
- data/ext/iodine/fio_siphash.c +0 -157
- data/ext/iodine/fio_siphash.h +0 -37
- data/ext/iodine/fio_tls.h +0 -129
- data/ext/iodine/fio_tls_missing.c +0 -649
- data/ext/iodine/fio_tls_openssl.c +0 -1106
- data/ext/iodine/fio_tmpfile.h +0 -50
- data/ext/iodine/fiobj.h +0 -44
- data/ext/iodine/fiobj4fio.h +0 -21
- data/ext/iodine/fiobj_ary.c +0 -333
- data/ext/iodine/fiobj_ary.h +0 -139
- data/ext/iodine/fiobj_data.c +0 -1187
- data/ext/iodine/fiobj_data.h +0 -167
- data/ext/iodine/fiobj_hash.c +0 -409
- data/ext/iodine/fiobj_hash.h +0 -176
- data/ext/iodine/fiobj_json.c +0 -622
- data/ext/iodine/fiobj_json.h +0 -68
- data/ext/iodine/fiobj_mem.h +0 -71
- data/ext/iodine/fiobj_mustache.c +0 -317
- data/ext/iodine/fiobj_mustache.h +0 -62
- data/ext/iodine/fiobj_numbers.c +0 -344
- data/ext/iodine/fiobj_numbers.h +0 -127
- data/ext/iodine/fiobj_str.c +0 -433
- data/ext/iodine/fiobj_str.h +0 -172
- data/ext/iodine/fiobject.c +0 -620
- data/ext/iodine/fiobject.h +0 -655
- data/ext/iodine/hpack.h +0 -1923
- data/ext/iodine/http.c +0 -2743
- data/ext/iodine/http.h +0 -1002
- data/ext/iodine/http1.c +0 -910
- data/ext/iodine/http1.h +0 -29
- data/ext/iodine/http1_parser.h +0 -878
- data/ext/iodine/http_internal.c +0 -1279
- data/ext/iodine/http_internal.h +0 -237
- data/ext/iodine/http_mime_parser.h +0 -350
- data/ext/iodine/iodine_caller.c +0 -230
- data/ext/iodine/iodine_connection.c +0 -941
- data/ext/iodine/iodine_defer.c +0 -420
- data/ext/iodine/iodine_fiobj2rb.h +0 -120
- data/ext/iodine/iodine_helpers.c +0 -282
- data/ext/iodine/iodine_helpers.h +0 -12
- data/ext/iodine/iodine_http.c +0 -1181
- data/ext/iodine/iodine_http.h +0 -23
- data/ext/iodine/iodine_json.c +0 -302
- data/ext/iodine/iodine_mustache.c +0 -567
- data/ext/iodine/iodine_pubsub.c +0 -580
- data/ext/iodine/iodine_pubsub.h +0 -26
- data/ext/iodine/iodine_rack_io.c +0 -273
- data/ext/iodine/iodine_rack_io.h +0 -20
- data/ext/iodine/iodine_store.c +0 -146
- data/ext/iodine/iodine_tcp.c +0 -346
- data/ext/iodine/iodine_tcp.h +0 -13
- data/ext/iodine/iodine_tls.c +0 -261
- data/ext/iodine/mustache_parser.h +0 -1546
- data/ext/iodine/redis_engine.c +0 -957
- data/ext/iodine/redis_engine.h +0 -79
- data/ext/iodine/resp_parser.h +0 -317
- data/ext/iodine/websocket_parser.h +0 -506
- data/ext/iodine/websockets.c +0 -743
- data/ext/iodine/websockets.h +0 -185
- data/lib/iodine/connection.rb +0 -61
- data/lib/iodine/json.rb +0 -42
- data/lib/iodine/mustache.rb +0 -113
- data/lib/iodine/pubsub.rb +0 -55
- data/lib/iodine/rack_utils.rb +0 -43
- data/lib/iodine/tls.rb +0 -16
- data/lib/rack/handler/iodine.rb +0 -33
- data/logo.png +0 -0
data/ext/iodine/iodine_json.h
CHANGED
|
@@ -1,6 +1,740 @@
|
|
|
1
|
-
#ifndef
|
|
2
|
-
#define
|
|
1
|
+
#ifndef H___IODINE_JSON___H
|
|
2
|
+
#define H___IODINE_JSON___H
|
|
3
|
+
#include "iodine.h"
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/* *****************************************************************************
|
|
6
|
+
Iodine JSON - Fast JSON Parsing and Stringification
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
This module provides the Iodine::JSON Ruby module for JSON operations.
|
|
9
|
+
It's primarily used internally for WebSocket message serialization but
|
|
10
|
+
is also exposed as a public API.
|
|
11
|
+
|
|
12
|
+
Features:
|
|
13
|
+
- Fast JSON stringification (Ruby objects to JSON strings)
|
|
14
|
+
- JSON parsing (JSON strings to Ruby objects)
|
|
15
|
+
- Beautified/pretty-printed JSON output
|
|
16
|
+
- Handles nested arrays, hashes, strings, numbers, booleans, nil
|
|
17
|
+
|
|
18
|
+
Performance Notes:
|
|
19
|
+
- Stringification is fast (single memory copy from C to Ruby)
|
|
20
|
+
- Parsing is slower than alternatives (double copy: JSON->FIOBJ->Ruby)
|
|
21
|
+
- For production JSON work, consider using the 'oj' gem instead
|
|
22
|
+
|
|
23
|
+
Ruby API (Iodine::JSON):
|
|
24
|
+
- Iodine::JSON.parse(json_string) - Parse JSON to Ruby objects
|
|
25
|
+
- Iodine::JSON.stringify(object) - Convert Ruby object to JSON
|
|
26
|
+
- Iodine::JSON.dump(object) - Alias for stringify
|
|
27
|
+
- Iodine::JSON.beautify(object) - Pretty-printed JSON output
|
|
28
|
+
- Iodine::JSON.beautify_slow(object) - Alternative beautifier via FIOBJ
|
|
29
|
+
- Iodine::JSON.parse_slow(json) - Alternative parser via FIOBJ
|
|
30
|
+
***************************************************************************** */
|
|
31
|
+
|
|
32
|
+
/* *****************************************************************************
|
|
33
|
+
JSON Stringifier - Ruby to JSON String Conversion
|
|
34
|
+
***************************************************************************** */
|
|
35
|
+
|
|
36
|
+
static char *iodine_json_stringify_key(char *dest, VALUE tmp) {
|
|
37
|
+
/* Note: keys MUST be Strings */
|
|
38
|
+
switch (rb_type(tmp)) {
|
|
39
|
+
case RUBY_T_BIGNUM: /* Bignum (Windows: Integers >= 2^30) */
|
|
40
|
+
tmp = rb_big2str(tmp, 10);
|
|
41
|
+
goto string_key;
|
|
42
|
+
case RUBY_T_SYMBOL: tmp = rb_sym2str(tmp); /* fall through */
|
|
43
|
+
case RUBY_T_STRING:
|
|
44
|
+
string_key:
|
|
45
|
+
dest = fio_bstr_write(dest, "\"", 1);
|
|
46
|
+
dest =
|
|
47
|
+
fio_bstr_write_escape(dest, RSTRING_PTR(tmp), (size_t)RSTRING_LEN(tmp));
|
|
48
|
+
dest = fio_bstr_write(dest, "\"", 1);
|
|
49
|
+
break;
|
|
50
|
+
case RUBY_T_FIXNUM:
|
|
51
|
+
dest = fio_bstr_write2(dest,
|
|
52
|
+
FIO_STRING_WRITE_STR2("\"", 1),
|
|
53
|
+
FIO_STRING_WRITE_NUM(NUM2LL(tmp)),
|
|
54
|
+
FIO_STRING_WRITE_STR2("\"", 1));
|
|
55
|
+
break;
|
|
56
|
+
case RUBY_T_TRUE: dest = fio_bstr_write(dest, "\"true\"", 6); break;
|
|
57
|
+
case RUBY_T_FALSE: dest = fio_bstr_write(dest, "\"false\"", 7); break;
|
|
58
|
+
case RUBY_T_NIL: dest = fio_bstr_write(dest, "\"null\"", 6); break;
|
|
59
|
+
default:
|
|
60
|
+
tmp = rb_any_to_s(tmp);
|
|
61
|
+
if (RB_TYPE_P(tmp, RUBY_T_STRING))
|
|
62
|
+
goto string_key;
|
|
63
|
+
dest = fio_bstr_write(dest, "\"error\"", 7);
|
|
64
|
+
}
|
|
65
|
+
return dest;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static char *iodine_json_stringify2bstr(char *dest, VALUE o);
|
|
69
|
+
|
|
70
|
+
static int iodine_json_hash_each_callback(VALUE k, VALUE v, VALUE dest_) {
|
|
71
|
+
char **pdest = (char **)dest_;
|
|
72
|
+
*pdest = iodine_json_stringify_key(*pdest, k);
|
|
73
|
+
*pdest = fio_bstr_write(*pdest, ":", 1);
|
|
74
|
+
*pdest = iodine_json_stringify2bstr(*pdest, v);
|
|
75
|
+
*pdest = fio_bstr_write(*pdest, ",", 1);
|
|
76
|
+
return ST_CONTINUE;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Converts a VALUE to a fio_bstr */
|
|
80
|
+
static char *iodine_json_stringify2bstr(char *dest, VALUE o) {
|
|
81
|
+
size_t tmp;
|
|
82
|
+
switch (rb_type(o)) {
|
|
83
|
+
case RUBY_T_NIL: return (dest = fio_bstr_write(dest, "null", 4));
|
|
84
|
+
case RUBY_T_TRUE: return (dest = fio_bstr_write(dest, "true", 4));
|
|
85
|
+
case RUBY_T_FALSE: return (dest = fio_bstr_write(dest, "false", 5));
|
|
86
|
+
case RUBY_T_ARRAY:
|
|
87
|
+
tmp = rb_array_len(o);
|
|
88
|
+
if (!tmp) {
|
|
89
|
+
dest = fio_bstr_write(dest, "[]", 2);
|
|
90
|
+
return dest;
|
|
91
|
+
}
|
|
92
|
+
dest = fio_bstr_write(dest, "[", 1);
|
|
93
|
+
for (size_t i = 0; i < tmp; ++i) {
|
|
94
|
+
dest = iodine_json_stringify2bstr(dest, RARRAY_PTR(o)[i]);
|
|
95
|
+
dest = fio_bstr_write(dest, ",", 1);
|
|
96
|
+
}
|
|
97
|
+
dest[fio_bstr_len(dest) - 1] = ']';
|
|
98
|
+
return dest;
|
|
99
|
+
case RUBY_T_HASH:
|
|
100
|
+
tmp = rb_hash_size_num(o);
|
|
101
|
+
if (!tmp) {
|
|
102
|
+
dest = fio_bstr_write(dest, "{}", 2);
|
|
103
|
+
return dest;
|
|
104
|
+
}
|
|
105
|
+
dest = fio_bstr_write(dest, "{", 1);
|
|
106
|
+
rb_hash_foreach(o, iodine_json_hash_each_callback, (VALUE)(&dest));
|
|
107
|
+
dest[fio_bstr_len(dest) - 1] = '}';
|
|
108
|
+
return dest;
|
|
109
|
+
case RUBY_T_FIXNUM: return (dest = fio_bstr_write_i(dest, RB_NUM2LL(o)));
|
|
110
|
+
case RUBY_T_BIGNUM: /* Bignum (Windows: Integers >= 2^30) */
|
|
111
|
+
o = rb_big2str(o, 10);
|
|
112
|
+
return (dest =
|
|
113
|
+
fio_bstr_write(dest, RSTRING_PTR(o), (size_t)RSTRING_LEN(o)));
|
|
114
|
+
case RUBY_T_FLOAT: {
|
|
115
|
+
FIO_STR_INFO_TMP_VAR(buf, 232);
|
|
116
|
+
buf.len = fio_ftoa(buf.buf, RFLOAT_VALUE(o), 10);
|
|
117
|
+
dest = fio_bstr_write(dest, buf.buf, buf.len);
|
|
118
|
+
return dest;
|
|
119
|
+
}
|
|
120
|
+
case RUBY_T_SYMBOL: o = rb_sym2str(o); /* fall through */
|
|
121
|
+
case RUBY_T_STRING:
|
|
122
|
+
add_string:
|
|
123
|
+
dest = fio_bstr_write(dest, "\"", 1);
|
|
124
|
+
dest = fio_bstr_write_escape(dest, RSTRING_PTR(o), (size_t)RSTRING_LEN(o));
|
|
125
|
+
dest = fio_bstr_write(dest, "\"", 1);
|
|
126
|
+
return dest;
|
|
127
|
+
default:
|
|
128
|
+
if (1) {
|
|
129
|
+
VALUE tmp = rb_check_funcall(o, IODINE_TO_JSON_ID, 0, NULL);
|
|
130
|
+
if (tmp != RUBY_Qundef && RB_TYPE_P(tmp, RUBY_T_STRING))
|
|
131
|
+
return (dest = fio_bstr_write(dest,
|
|
132
|
+
RSTRING_PTR(tmp),
|
|
133
|
+
(size_t)RSTRING_LEN(tmp)));
|
|
134
|
+
o = rb_funcallv(o, IODINE_TO_S_ID, 0, NULL);
|
|
135
|
+
}
|
|
136
|
+
if (!RB_TYPE_P(o, RUBY_T_STRING)) {
|
|
137
|
+
FIO_LOG_ERROR("Iodine::JSON.stringify called with an object that doesn't "
|
|
138
|
+
"respond to #to_s.");
|
|
139
|
+
return (dest = fio_bstr_write(dest, "null", 4));
|
|
140
|
+
}
|
|
141
|
+
goto add_string;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* *****************************************************************************
|
|
146
|
+
JSON Beautifier - Pretty-Printed JSON Output
|
|
147
|
+
***************************************************************************** */
|
|
148
|
+
|
|
149
|
+
typedef struct {
|
|
150
|
+
char *o;
|
|
151
|
+
size_t t;
|
|
152
|
+
} iodine_json_beautify2bstr_s;
|
|
153
|
+
|
|
154
|
+
static char *iodine_json_beautify2bstr(iodine_json_beautify2bstr_s *dest,
|
|
155
|
+
VALUE o);
|
|
156
|
+
|
|
157
|
+
FIO_IFUNC void iodine_json_beautify2bstr_pad(iodine_json_beautify2bstr_s *d) {
|
|
158
|
+
if (!d->t)
|
|
159
|
+
return;
|
|
160
|
+
size_t len = fio_bstr_len(d->o);
|
|
161
|
+
d->o = fio_bstr_len_set(d->o, len + d->t + 1);
|
|
162
|
+
char *tmp = d->o + len;
|
|
163
|
+
*tmp++ = '\n';
|
|
164
|
+
FIO_MEMSET(tmp, '\t', d->t);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static int iodine_json_hash_each_beautify_callback(VALUE k,
|
|
168
|
+
VALUE v,
|
|
169
|
+
VALUE dest_) {
|
|
170
|
+
iodine_json_beautify2bstr_s *d = (iodine_json_beautify2bstr_s *)dest_;
|
|
171
|
+
iodine_json_beautify2bstr_pad(d);
|
|
172
|
+
d->o = iodine_json_stringify_key(d->o, k);
|
|
173
|
+
d->o = fio_bstr_write(d->o, ": ", 2);
|
|
174
|
+
iodine_json_beautify2bstr(d, v);
|
|
175
|
+
d->o = fio_bstr_write(d->o, ",", 1);
|
|
176
|
+
return ST_CONTINUE;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Converts a VALUE to a fio_bstr */
|
|
180
|
+
static char *iodine_json_beautify2bstr(iodine_json_beautify2bstr_s *d,
|
|
181
|
+
VALUE o) {
|
|
182
|
+
size_t tmp;
|
|
183
|
+
switch (rb_type(o)) {
|
|
184
|
+
case RUBY_T_NIL: return (d->o = fio_bstr_write(d->o, "null", 4));
|
|
185
|
+
case RUBY_T_TRUE: return (d->o = fio_bstr_write(d->o, "true", 4));
|
|
186
|
+
case RUBY_T_FALSE: return (d->o = fio_bstr_write(d->o, "false", 5));
|
|
187
|
+
case RUBY_T_ARRAY:
|
|
188
|
+
tmp = rb_array_len(o);
|
|
189
|
+
if (!tmp) {
|
|
190
|
+
d->o = fio_bstr_write(d->o, "[]", 2);
|
|
191
|
+
return d->o;
|
|
192
|
+
}
|
|
193
|
+
d->o = fio_bstr_write(d->o, "[", 1);
|
|
194
|
+
++d->t;
|
|
195
|
+
for (size_t i = 0; i < tmp; ++i) {
|
|
196
|
+
iodine_json_beautify2bstr_pad(d);
|
|
197
|
+
iodine_json_beautify2bstr(d, RARRAY_PTR(o)[i]);
|
|
198
|
+
d->o = fio_bstr_write(d->o, ",", 1);
|
|
199
|
+
}
|
|
200
|
+
d->o[fio_bstr_len(d->o) - 1] = '\n';
|
|
201
|
+
--d->t;
|
|
202
|
+
iodine_json_beautify2bstr_pad(d);
|
|
203
|
+
d->o = fio_bstr_write(d->o, "]", 1);
|
|
204
|
+
return d->o;
|
|
205
|
+
case RUBY_T_HASH:
|
|
206
|
+
tmp = rb_hash_size_num(o);
|
|
207
|
+
if (!tmp) {
|
|
208
|
+
d->o = fio_bstr_write(d->o, "{}", 2);
|
|
209
|
+
return d->o;
|
|
210
|
+
}
|
|
211
|
+
d->o = fio_bstr_write(d->o, "{", 1);
|
|
212
|
+
++d->t;
|
|
213
|
+
rb_hash_foreach(o, iodine_json_hash_each_beautify_callback, (VALUE)(d));
|
|
214
|
+
d->o[fio_bstr_len(d->o) - 1] = '\n';
|
|
215
|
+
--d->t;
|
|
216
|
+
iodine_json_beautify2bstr_pad(d);
|
|
217
|
+
d->o = fio_bstr_write(d->o, "}", 1);
|
|
218
|
+
return d->o;
|
|
219
|
+
case RUBY_T_FIXNUM: return (d->o = fio_bstr_write_i(d->o, RB_NUM2LL(o)));
|
|
220
|
+
case RUBY_T_BIGNUM: /* Bignum (Windows: Integers >= 2^30) */
|
|
221
|
+
o = rb_big2str(o, 10);
|
|
222
|
+
return (d->o =
|
|
223
|
+
fio_bstr_write(d->o, RSTRING_PTR(o), (size_t)RSTRING_LEN(o)));
|
|
224
|
+
case RUBY_T_FLOAT: {
|
|
225
|
+
FIO_STR_INFO_TMP_VAR(buf, 232);
|
|
226
|
+
buf.len = fio_ftoa(buf.buf, RFLOAT_VALUE(o), 10);
|
|
227
|
+
d->o = fio_bstr_write(d->o, buf.buf, buf.len);
|
|
228
|
+
return d->o;
|
|
229
|
+
}
|
|
230
|
+
case RUBY_T_SYMBOL: o = rb_sym2str(o); /* fall through */
|
|
231
|
+
case RUBY_T_STRING:
|
|
232
|
+
add_string:
|
|
233
|
+
d->o = fio_bstr_write(d->o, "\"", 1);
|
|
234
|
+
d->o = fio_bstr_write_escape(d->o, RSTRING_PTR(o), (size_t)RSTRING_LEN(o));
|
|
235
|
+
d->o = fio_bstr_write(d->o, "\"", 1);
|
|
236
|
+
return d->o;
|
|
237
|
+
default:
|
|
238
|
+
if (1) {
|
|
239
|
+
VALUE tmp = rb_check_funcall(o, IODINE_TO_JSON_ID, 0, NULL);
|
|
240
|
+
if (tmp != RUBY_Qundef && RB_TYPE_P(tmp, RUBY_T_STRING))
|
|
241
|
+
return (d->o = fio_bstr_write(d->o,
|
|
242
|
+
RSTRING_PTR(tmp),
|
|
243
|
+
(size_t)RSTRING_LEN(tmp)));
|
|
244
|
+
o = rb_funcallv(o, IODINE_TO_S_ID, 0, NULL);
|
|
245
|
+
if (RB_TYPE_P(o, RUBY_T_STRING))
|
|
246
|
+
goto add_string;
|
|
247
|
+
}
|
|
248
|
+
FIO_LOG_ERROR("Iodine::JSON.stringify called with an object that doesn't "
|
|
249
|
+
"respond to neither #to_json nor #to_s.");
|
|
250
|
+
return (d->o = fio_bstr_write(d->o, "null", 4));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* *****************************************************************************
|
|
255
|
+
FIOBJ => Ruby Bridge - Convert facil.io Objects to Ruby
|
|
256
|
+
***************************************************************************** */
|
|
257
|
+
|
|
258
|
+
typedef struct iodine_fiobj2ruby_task_s {
|
|
259
|
+
VALUE out;
|
|
260
|
+
} iodine_fiobj2ruby_task_s;
|
|
261
|
+
|
|
262
|
+
static VALUE iodine_fiobj2ruby(FIOBJ o);
|
|
263
|
+
|
|
264
|
+
static int iodine_fiobj2ruby_array_task(fiobj_array_each_s *e) {
|
|
265
|
+
rb_ary_push((VALUE)e->udata, iodine_fiobj2ruby(e->value));
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
static int iodine_fiobj2ruby_hash_task(fiobj_hash_each_s *e) {
|
|
269
|
+
VALUE k = iodine_fiobj2ruby(e->key);
|
|
270
|
+
VALUE v = iodine_fiobj2ruby(e->value);
|
|
271
|
+
rb_hash_aset((VALUE)e->udata, k, v);
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Converts a FIOBJ (facil.io object) to a Ruby VALUE.
|
|
277
|
+
*
|
|
278
|
+
* Recursively converts FIOBJ types to their Ruby equivalents:
|
|
279
|
+
* - FIOBJ_T_TRUE/FALSE -> Qtrue/Qfalse
|
|
280
|
+
* - FIOBJ_T_NUMBER -> Fixnum/Bignum
|
|
281
|
+
* - FIOBJ_T_FLOAT -> Float
|
|
282
|
+
* - FIOBJ_T_STRING -> String
|
|
283
|
+
* - FIOBJ_T_ARRAY -> Array
|
|
284
|
+
* - FIOBJ_T_HASH -> Hash
|
|
285
|
+
* - FIOBJ_T_NULL/INVALID -> Qnil
|
|
286
|
+
*
|
|
287
|
+
* @param o The FIOBJ to convert
|
|
288
|
+
* @return Ruby VALUE equivalent
|
|
289
|
+
*
|
|
290
|
+
* @note Does NOT place VALUE in STORE automatically.
|
|
291
|
+
*/
|
|
292
|
+
static VALUE iodine_fiobj2ruby(FIOBJ o) {
|
|
293
|
+
VALUE r;
|
|
294
|
+
switch (FIOBJ_TYPE(o)) {
|
|
295
|
+
case FIOBJ_T_TRUE: return Qtrue;
|
|
296
|
+
case FIOBJ_T_FALSE: return Qfalse;
|
|
297
|
+
case FIOBJ_T_NUMBER: return RB_LL2NUM(fiobj_num2i(o));
|
|
298
|
+
case FIOBJ_T_FLOAT: return rb_float_new(fiobj_float2f(o));
|
|
299
|
+
case FIOBJ_T_STRING: return rb_str_new(fiobj_str_ptr(o), fiobj_str_len(o));
|
|
300
|
+
case FIOBJ_T_ARRAY:
|
|
301
|
+
STORE.gc_stop();
|
|
302
|
+
r = rb_ary_new_capa(fiobj_array_count(o));
|
|
303
|
+
fiobj_array_each(o, iodine_fiobj2ruby_array_task, (void *)r, 0);
|
|
304
|
+
STORE.gc_start();
|
|
305
|
+
return r;
|
|
306
|
+
case FIOBJ_T_HASH:
|
|
307
|
+
STORE.gc_stop();
|
|
308
|
+
r = rb_hash_new();
|
|
309
|
+
fiobj_hash_each(o, iodine_fiobj2ruby_hash_task, (void *)r, 0);
|
|
310
|
+
STORE.gc_start();
|
|
311
|
+
return r;
|
|
312
|
+
// case FIOBJ_T_NULL: /* fall through */
|
|
313
|
+
// case FIOBJ_T_INVALID: /* fall through */
|
|
314
|
+
default: return Qnil;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* *****************************************************************************
|
|
319
|
+
Ruby => FIOBJ Bridge - Convert Ruby Objects to facil.io Objects
|
|
320
|
+
***************************************************************************** */
|
|
321
|
+
|
|
322
|
+
typedef struct iodine_ruby2fiobj_task_s {
|
|
323
|
+
FIOBJ out;
|
|
324
|
+
} iodine_ruby2fiobj_task_s;
|
|
325
|
+
|
|
326
|
+
static FIOBJ iodine_ruby2fiobj(VALUE o);
|
|
327
|
+
|
|
328
|
+
static int iodine_ruby2fiobj_hash_each_task(VALUE n, VALUE v, VALUE h_) {
|
|
329
|
+
FIOBJ h = (FIOBJ)h_;
|
|
330
|
+
FIOBJ key = iodine_ruby2fiobj(n);
|
|
331
|
+
fiobj_hash_set(h, key, iodine_ruby2fiobj(v), NULL);
|
|
332
|
+
fiobj_free(key); /* by default, keys aren't owned by the hash */
|
|
333
|
+
return ST_CONTINUE;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Converts a Ruby VALUE to a FIOBJ (facil.io object).
|
|
338
|
+
*
|
|
339
|
+
* Recursively converts Ruby types to their FIOBJ equivalents:
|
|
340
|
+
* - Qtrue/Qfalse -> fiobj_true()/fiobj_false()
|
|
341
|
+
* - Fixnum -> fiobj_num_new()
|
|
342
|
+
* - Float -> fiobj_float_new()
|
|
343
|
+
* - Symbol/String -> fiobj_str_new_cstr()
|
|
344
|
+
* - Array -> fiobj_array_new()
|
|
345
|
+
* - Hash -> fiobj_hash_new()
|
|
346
|
+
* - nil/undef -> fiobj_null()
|
|
347
|
+
* - Other -> calls #to_json or #to_s
|
|
348
|
+
*
|
|
349
|
+
* @param o The Ruby VALUE to convert
|
|
350
|
+
* @return FIOBJ equivalent
|
|
351
|
+
*
|
|
352
|
+
* @note Does NOT place VALUE in STORE automatically.
|
|
353
|
+
*/
|
|
354
|
+
static FIOBJ iodine_ruby2fiobj(VALUE o) {
|
|
355
|
+
FIOBJ r;
|
|
356
|
+
VALUE tmp;
|
|
357
|
+
switch (rb_type(o)) {
|
|
358
|
+
case RUBY_T_TRUE: return fiobj_true();
|
|
359
|
+
case RUBY_T_FALSE: return fiobj_false();
|
|
360
|
+
case RUBY_T_FIXNUM: return fiobj_num_new((intptr_t)RB_NUM2LL(o));
|
|
361
|
+
case RUBY_T_BIGNUM: /* Bignum (Windows: Integers >= 2^30) */
|
|
362
|
+
if (1) {
|
|
363
|
+
size_t c = 0;
|
|
364
|
+
o = rb_big2str(o, 10);
|
|
365
|
+
return fiobj_json_parse((fio_str_info_s)IODINE_RSTR_INFO(o), &c);
|
|
366
|
+
}
|
|
367
|
+
case RUBY_T_FLOAT: return fiobj_float_new(rb_float_value(o));
|
|
368
|
+
case RUBY_T_SYMBOL: o = rb_sym_to_s(o); /* fall through */
|
|
369
|
+
case RUBY_T_STRING: return fiobj_str_new_cstr(RSTRING_PTR(o), RSTRING_LEN(o));
|
|
370
|
+
case RUBY_T_ARRAY:
|
|
371
|
+
if (1) {
|
|
372
|
+
const size_t alen = rb_array_len(o);
|
|
373
|
+
r = fiobj_array_new();
|
|
374
|
+
if (alen)
|
|
375
|
+
fiobj_array_reserve(r, (int64_t)alen);
|
|
376
|
+
for (size_t i = 0; i < alen; ++i)
|
|
377
|
+
fiobj_array_push(r, iodine_ruby2fiobj(RARRAY_PTR(o)[i]));
|
|
378
|
+
return r;
|
|
379
|
+
}
|
|
380
|
+
case RUBY_T_HASH:
|
|
381
|
+
r = fiobj_hash_new();
|
|
382
|
+
if (rb_hash_size_num(o))
|
|
383
|
+
fiobj_hash_reserve(r, rb_hash_size_num(o));
|
|
384
|
+
rb_hash_foreach(o, iodine_ruby2fiobj_hash_each_task, (VALUE)r);
|
|
385
|
+
return r;
|
|
386
|
+
case RUBY_T_NIL: /* fall through */
|
|
387
|
+
case RUBY_T_UNDEF: /* fall through */
|
|
388
|
+
case RUBY_T_NONE: return fiobj_null();
|
|
389
|
+
default:
|
|
390
|
+
tmp = rb_check_funcall(o, IODINE_TO_JSON_ID, 0, NULL);
|
|
391
|
+
if (tmp != RUBY_Qundef && RB_TYPE_P(tmp, RUBY_T_STRING)) {
|
|
392
|
+
size_t c = 0;
|
|
393
|
+
r = fiobj_json_parse((fio_str_info_s)IODINE_RSTR_INFO(tmp), &c);
|
|
394
|
+
return r;
|
|
395
|
+
}
|
|
396
|
+
o = rb_any_to_s(o);
|
|
397
|
+
if (RB_TYPE_P(o, RUBY_T_STRING))
|
|
398
|
+
return fiobj_str_new_cstr(RSTRING_PTR(o), RSTRING_LEN(o));
|
|
399
|
+
return fiobj_null();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* *****************************************************************************
|
|
404
|
+
JSON Parser (Indirect) - Parse via FIOBJ intermediate
|
|
405
|
+
***************************************************************************** */
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Parses a JSON string to Ruby objects via FIOBJ intermediate.
|
|
409
|
+
*
|
|
410
|
+
* This is the slower parsing path that:
|
|
411
|
+
* 1. Parses JSON string to FIOBJ tree
|
|
412
|
+
* 2. Converts FIOBJ tree to Ruby objects
|
|
413
|
+
*
|
|
414
|
+
* @param self The Iodine::JSON module
|
|
415
|
+
* @param rstr The JSON string to parse (String)
|
|
416
|
+
* @return Ruby object tree (Hash, Array, String, etc.)
|
|
417
|
+
*
|
|
418
|
+
* Ruby: Iodine::JSON.parse_slow(json_string)
|
|
419
|
+
*/
|
|
420
|
+
static VALUE iodine_json_parse_indirect(VALUE self, VALUE rstr) {
|
|
421
|
+
VALUE r = Qnil;
|
|
422
|
+
rb_check_type(rstr, RUBY_T_STRING);
|
|
423
|
+
size_t consumed = 0;
|
|
424
|
+
FIOBJ tmp =
|
|
425
|
+
fiobj_json_parse((fio_str_info_s)IODINE_RSTR_INFO(rstr), &consumed);
|
|
426
|
+
STORE.gc_stop();
|
|
427
|
+
r = iodine_fiobj2ruby(tmp);
|
|
428
|
+
STORE.gc_start();
|
|
429
|
+
fiobj_free(tmp);
|
|
430
|
+
return r;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* *****************************************************************************
|
|
434
|
+
JSON Parser (Direct) - Parse Directly to Ruby Objects
|
|
435
|
+
|
|
436
|
+
This parser uses facil.io's streaming JSON parser with callbacks that
|
|
437
|
+
create Ruby objects directly, avoiding the FIOBJ intermediate step.
|
|
438
|
+
This is the faster parsing path used by Iodine::JSON.parse().
|
|
439
|
+
***************************************************************************** */
|
|
440
|
+
|
|
441
|
+
/** JSON parser callback implementations for direct Ruby object creation */
|
|
442
|
+
/** NULL object was detected. Returns new object as `void *`. */
|
|
443
|
+
FIO_SFUNC void *iodine___json_on_null(void *udata) {
|
|
444
|
+
(void)udata;
|
|
445
|
+
return (void *)Qnil;
|
|
446
|
+
}
|
|
447
|
+
/** TRUE object was detected. Returns new object as `void *`. */
|
|
448
|
+
FIO_SFUNC void *iodine___json_on_true(void *udata) {
|
|
449
|
+
(void)udata;
|
|
450
|
+
return (void *)Qtrue;
|
|
451
|
+
}
|
|
452
|
+
/** FALSE object was detected. Returns new object as `void *`. */
|
|
453
|
+
FIO_SFUNC void *iodine___json_on_false(void *udata) {
|
|
454
|
+
(void)udata;
|
|
455
|
+
return (void *)Qfalse;
|
|
456
|
+
}
|
|
457
|
+
/** Number was detected (long long). Returns new object as `void *`. */
|
|
458
|
+
FIO_SFUNC void *iodine___json_on_number(void *udata, int64_t i) {
|
|
459
|
+
(void)udata;
|
|
460
|
+
return (void *)(LL2NUM(((long long)i)));
|
|
461
|
+
}
|
|
462
|
+
/** Float was detected (double).Returns new object as `void *`. */
|
|
463
|
+
FIO_SFUNC void *iodine___json_on_float(void *udata, double f) {
|
|
464
|
+
(void)udata;
|
|
465
|
+
return (void *)DBL2NUM(f);
|
|
466
|
+
}
|
|
467
|
+
/** String was detected (int / float). update `pos` to point at ending */
|
|
468
|
+
FIO_SFUNC void *iodine___json_on_string(void *udata,
|
|
469
|
+
const void *start,
|
|
470
|
+
size_t len) {
|
|
471
|
+
(void)udata;
|
|
472
|
+
VALUE str;
|
|
473
|
+
if (len < 4096) {
|
|
474
|
+
FIO_STR_INFO_TMP_VAR(buf, 4096);
|
|
475
|
+
fio_string_write_unescape(&buf, NULL, start, len);
|
|
476
|
+
str = rb_str_new(buf.buf, buf.len);
|
|
477
|
+
} else {
|
|
478
|
+
char *tmp = fio_bstr_write_unescape(NULL, start, len);
|
|
479
|
+
str = rb_str_new(tmp, fio_bstr_len(tmp));
|
|
480
|
+
fio_bstr_free(tmp);
|
|
481
|
+
}
|
|
482
|
+
return (void *)str;
|
|
483
|
+
}
|
|
484
|
+
FIO_SFUNC void *iodine___json_on_string_simple(void *udata,
|
|
485
|
+
const void *start,
|
|
486
|
+
size_t len) {
|
|
487
|
+
(void)udata;
|
|
488
|
+
VALUE str = rb_str_new((const char *)start, len);
|
|
489
|
+
return (void *)str;
|
|
490
|
+
}
|
|
491
|
+
/** Dictionary was detected. Returns ctx to hash map or NULL on error. */
|
|
492
|
+
FIO_SFUNC void *iodine___json_on_map(void *udata, void *ctx, void *at) {
|
|
493
|
+
(void)udata, (void)ctx, (void)at;
|
|
494
|
+
VALUE map = rb_hash_new();
|
|
495
|
+
return (void *)map;
|
|
496
|
+
}
|
|
497
|
+
/** Array was detected. Returns ctx to array or NULL on error. */
|
|
498
|
+
FIO_SFUNC void *iodine___json_on_array(void *udata, void *ctx, void *at) {
|
|
499
|
+
(void)udata, (void)ctx, (void)at;
|
|
500
|
+
VALUE ary = rb_ary_new();
|
|
501
|
+
return (void *)ary;
|
|
502
|
+
}
|
|
503
|
+
/** Map entry detected. Returns non-zero on error. */
|
|
504
|
+
FIO_SFUNC int iodine___json_map_push(void *udata,
|
|
505
|
+
void *ctx,
|
|
506
|
+
void *key,
|
|
507
|
+
void *val) {
|
|
508
|
+
(void)udata;
|
|
509
|
+
rb_hash_aset((VALUE)ctx, (VALUE)key, (VALUE)val);
|
|
510
|
+
return 0;
|
|
511
|
+
}
|
|
512
|
+
/** Array entry detected. Returns non-zero on error. */
|
|
513
|
+
FIO_SFUNC int iodine___json_array_push(void *udata, void *ctx, void *val) {
|
|
514
|
+
(void)udata;
|
|
515
|
+
rb_ary_push((VALUE)ctx, (VALUE)val);
|
|
516
|
+
return 0;
|
|
517
|
+
}
|
|
518
|
+
/** Called for the `key` element in case of error or NULL value. */
|
|
519
|
+
FIO_SFUNC void iodine___json_free_unused_object(void *udata, void *ctx) {
|
|
520
|
+
(void)udata, (void)ctx;
|
|
521
|
+
}
|
|
522
|
+
/** the JSON parsing encountered an error - what to do with ctx? */
|
|
523
|
+
FIO_SFUNC void *iodine___json_on_error(void *udata, void *ctx) {
|
|
524
|
+
(void)udata, (void)ctx;
|
|
525
|
+
return (void *)Qnil;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
static fio_json_parser_callbacks_s IODINE_JSON_PARSER_CALLBACKS = {
|
|
529
|
+
.on_null = iodine___json_on_null,
|
|
530
|
+
.on_true = iodine___json_on_true,
|
|
531
|
+
.on_false = iodine___json_on_false,
|
|
532
|
+
.on_number = iodine___json_on_number,
|
|
533
|
+
.on_float = iodine___json_on_float,
|
|
534
|
+
.on_string = iodine___json_on_string,
|
|
535
|
+
.on_string_simple = iodine___json_on_string_simple,
|
|
536
|
+
.on_map = iodine___json_on_map,
|
|
537
|
+
.on_array = iodine___json_on_array,
|
|
538
|
+
.map_push = iodine___json_map_push,
|
|
539
|
+
.array_push = iodine___json_array_push,
|
|
540
|
+
.free_unused_object = iodine___json_free_unused_object,
|
|
541
|
+
.on_error = iodine___json_on_error,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/* *****************************************************************************
|
|
545
|
+
API - Public Ruby Methods
|
|
546
|
+
***************************************************************************** */
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Parses a JSON string to Ruby objects (fast path).
|
|
550
|
+
*
|
|
551
|
+
* Uses the direct parser that creates Ruby objects without
|
|
552
|
+
* an intermediate FIOBJ representation.
|
|
553
|
+
*
|
|
554
|
+
* @param self The Iodine::JSON module
|
|
555
|
+
* @param rstr The JSON string to parse (String)
|
|
556
|
+
* @return Ruby object tree (Hash, Array, String, etc.)
|
|
557
|
+
*
|
|
558
|
+
* Ruby: Iodine::JSON.parse(json_string)
|
|
559
|
+
*/
|
|
560
|
+
static VALUE iodine_json_parse(VALUE self, VALUE rstr) {
|
|
561
|
+
VALUE r = Qnil;
|
|
562
|
+
rb_check_type(rstr, RUBY_T_STRING);
|
|
563
|
+
STORE.gc_stop();
|
|
564
|
+
fio_json_result_s result = fio_json_parse(&IODINE_JSON_PARSER_CALLBACKS,
|
|
565
|
+
NULL,
|
|
566
|
+
RSTRING_PTR(rstr),
|
|
567
|
+
RSTRING_LEN(rstr));
|
|
568
|
+
STORE.gc_start();
|
|
569
|
+
r = (VALUE)(result.ctx);
|
|
570
|
+
STORE.cache(r);
|
|
571
|
+
rb_str_buf_new(1); /* force GC to run if Ruby needs it */
|
|
572
|
+
return r;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Converts a Ruby object to a JSON string.
|
|
577
|
+
*
|
|
578
|
+
* Handles all standard Ruby types (Hash, Array, String, Numeric,
|
|
579
|
+
* true, false, nil). For other objects, calls #to_json or #to_s.
|
|
580
|
+
*
|
|
581
|
+
* @param self The Iodine::JSON module
|
|
582
|
+
* @param object The Ruby object to stringify
|
|
583
|
+
* @return JSON string representation
|
|
584
|
+
*
|
|
585
|
+
* Ruby: Iodine::JSON.stringify(object)
|
|
586
|
+
* Iodine::JSON.dump(object) # alias
|
|
587
|
+
*/
|
|
588
|
+
static VALUE iodine_json_stringify(VALUE self, VALUE object) {
|
|
589
|
+
VALUE r = Qnil;
|
|
590
|
+
char *str = fio_bstr_reserve(NULL, ((size_t)1 << 12) - 64);
|
|
591
|
+
str = iodine_json_stringify2bstr(str, object);
|
|
592
|
+
r = rb_str_new(str, (long)fio_bstr_len(str));
|
|
593
|
+
fio_bstr_free(str);
|
|
594
|
+
return r;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Converts a Ruby object to a pretty-printed JSON string (slow path).
|
|
599
|
+
*
|
|
600
|
+
* Uses FIOBJ intermediate for formatting. Slower but produces
|
|
601
|
+
* nicely indented output.
|
|
602
|
+
*
|
|
603
|
+
* @param self The Iodine::JSON module
|
|
604
|
+
* @param object The Ruby object to stringify
|
|
605
|
+
* @return Pretty-printed JSON string
|
|
606
|
+
*
|
|
607
|
+
* Ruby: Iodine::JSON.beautify_slow(object)
|
|
608
|
+
*/
|
|
609
|
+
static VALUE iodine_json_pretty(VALUE self, VALUE object) {
|
|
610
|
+
VALUE r = Qnil;
|
|
611
|
+
FIOBJ o = iodine_ruby2fiobj(object);
|
|
612
|
+
FIOBJ out = fiobj2json(FIOBJ_T_INVALID, o, 1);
|
|
613
|
+
fiobj_free(o);
|
|
614
|
+
r = rb_str_new(fiobj_str_ptr(out), (long)fiobj_str_len(out));
|
|
615
|
+
fiobj_free(out);
|
|
616
|
+
return r;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Converts a Ruby object to a pretty-printed JSON string (fast path).
|
|
621
|
+
*
|
|
622
|
+
* Uses direct stringification with indentation. Faster than
|
|
623
|
+
* beautify_slow but produces similar output.
|
|
624
|
+
*
|
|
625
|
+
* @param self The Iodine::JSON module
|
|
626
|
+
* @param object The Ruby object to stringify
|
|
627
|
+
* @return Pretty-printed JSON string with newlines and tabs
|
|
628
|
+
*
|
|
629
|
+
* Ruby: Iodine::JSON.beautify(object)
|
|
630
|
+
*/
|
|
631
|
+
static VALUE iodine_json_beautify(VALUE self, VALUE object) {
|
|
632
|
+
VALUE r = Qnil;
|
|
633
|
+
iodine_json_beautify2bstr_s dest = {
|
|
634
|
+
.o = fio_bstr_reserve(NULL, ((size_t)1 << 12) - 64),
|
|
635
|
+
};
|
|
636
|
+
iodine_json_beautify2bstr(&dest, object);
|
|
637
|
+
r = rb_str_new(dest.o, (long)fio_bstr_len(dest.o));
|
|
638
|
+
fio_bstr_free(dest.o);
|
|
639
|
+
return r;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Initialize Iodine::JSON */ // clang-format off
|
|
643
|
+
/**
|
|
644
|
+
Iodine::JSON exposes the {Iodine::Connection#write} fallback behavior when called with non-String objects.
|
|
645
|
+
|
|
646
|
+
The fallback behavior is similar to (though faster than) calling:
|
|
647
|
+
|
|
648
|
+
client.write(Iodine::JSON.stringify(data))
|
|
649
|
+
|
|
650
|
+
If you want to work with JSON, consider using the `oj` gem.
|
|
651
|
+
|
|
652
|
+
This API is mostly to test for Iodine JSON input/output errors and reflects what the C layer sees.
|
|
653
|
+
|
|
654
|
+
## Performance
|
|
655
|
+
|
|
656
|
+
Performance... could be better.
|
|
657
|
+
|
|
658
|
+
Converting Ruby objects into a JSON String (Strinfigying) should be fast even though the String data is copied twice, once to C and then to Ruby.
|
|
659
|
+
|
|
660
|
+
However, Converting a JSON object into Ruby Objects is currently slow and it is better to use the `oj` gem or even the Ruby builtin parser.
|
|
661
|
+
|
|
662
|
+
The reason is simple - the implementation is designed to create C objects (C Hash Maps, C Arrays, etc'), not Ruby objects. When converting from a String to Ruby Objevts, the data is copied twice, once to C and then to Ruby.
|
|
663
|
+
|
|
664
|
+
This especially effects parsing, where more objects are allocated,
|
|
665
|
+
whereas {Iodine::JSON.stringify} only (re)copies the String data which is a single continuous block of memory.
|
|
666
|
+
|
|
667
|
+
That's why {Iodine::JSON.stringify} is significantly faster than the Ruby `object.to_json` approach,
|
|
668
|
+
yet slower than `JSON.parse(json_string)`.
|
|
669
|
+
|
|
670
|
+
require 'oj' rescue nil
|
|
671
|
+
require 'benchmark/ips'
|
|
672
|
+
require 'json'
|
|
673
|
+
require 'iodine'
|
|
674
|
+
|
|
675
|
+
def benchmark_json
|
|
676
|
+
# make a big data store with nothings
|
|
677
|
+
data_1000 = []
|
|
678
|
+
1000.times do
|
|
679
|
+
tmp = {f: rand() };
|
|
680
|
+
tmp[:i] = (tmp[:f] * 1000000).to_i
|
|
681
|
+
tmp[:str] = tmp[:i].to_s
|
|
682
|
+
tmp[:sym] = tmp[:str].to_sym
|
|
683
|
+
tmp[:ary] = []
|
|
684
|
+
tmp[:ary_empty] = []
|
|
685
|
+
tmp[:hash_empty] = Hash.new
|
|
686
|
+
100.times {|i| tmp[:ary] << i }
|
|
687
|
+
data_1000 << tmp
|
|
688
|
+
end
|
|
689
|
+
3.times do
|
|
690
|
+
json_string = data_1000.to_json
|
|
691
|
+
puts "-----"
|
|
692
|
+
puts "Benchmark #{data_1000.length} item tree, and #{json_string.length} bytes of JSON"
|
|
693
|
+
# benchmark stringification
|
|
694
|
+
Benchmark.ips do |x|
|
|
695
|
+
x.report(" Ruby obj.to_json") do |times|
|
|
696
|
+
data_1000.to_json
|
|
697
|
+
end
|
|
698
|
+
x.report("Iodine::JSON.stringify") do |times|
|
|
699
|
+
Iodine::JSON.stringify(data_1000)
|
|
700
|
+
end
|
|
701
|
+
if(defined?(Oj))
|
|
702
|
+
x.report(" Oj.dump") do |times|
|
|
703
|
+
Oj.dump(data_1000)
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
x.compare!
|
|
707
|
+
end ; nil
|
|
708
|
+
# benchmark parsing
|
|
709
|
+
Benchmark.ips do |x|
|
|
710
|
+
x.report(" Ruby JSON.parse") do |times|
|
|
711
|
+
JSON.parse(json_string)
|
|
712
|
+
end
|
|
713
|
+
x.report("Iodine::JSON.parse") do |times|
|
|
714
|
+
Iodine::JSON.parse(json_string)
|
|
715
|
+
end
|
|
716
|
+
if(defined?(Oj))
|
|
717
|
+
x.report(" Oj.load") do |times|
|
|
718
|
+
Oj.load(json_string)
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
x.compare!
|
|
722
|
+
end
|
|
723
|
+
data_1000 = data_1000.slice(0, (data_1000.length / 10))
|
|
724
|
+
nil
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
benchmark_json
|
|
729
|
+
|
|
730
|
+
*/
|
|
731
|
+
static void Init_Iodine_JSON(void) {
|
|
732
|
+
VALUE m = rb_define_module_under(iodine_rb_IODINE, "JSON"); // clang-format on
|
|
733
|
+
rb_define_singleton_method(m, "parse", iodine_json_parse, 1);
|
|
734
|
+
rb_define_singleton_method(m, "parse_slow", iodine_json_parse_indirect, 1);
|
|
735
|
+
rb_define_singleton_method(m, "stringify", iodine_json_stringify, 1);
|
|
736
|
+
rb_define_singleton_method(m, "beautify_slow", iodine_json_pretty, 1);
|
|
737
|
+
rb_define_singleton_method(m, "beautify", iodine_json_beautify, 1);
|
|
738
|
+
rb_define_singleton_method(m, "dump", iodine_json_stringify, 1);
|
|
739
|
+
}
|
|
740
|
+
#endif /* H___IODINE_JSON___H */
|