rapidjson 0.2.2 → 0.3.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/README.md +9 -9
- data/ext/rapidjson/cext.cc +81 -11
- data/ext/rapidjson/encoder.hh +16 -1
- data/ext/rapidjson/parser.hh +13 -3
- data/lib/rapidjson/active_support_encoder.rb +3 -1
- data/lib/rapidjson/version.rb +1 -1
- data/lib/rapidjson.rb +4 -0
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6e0fb853f22254885b3a5e40c0b615992bbe77c7cde00d6cf5c3f9bdd8fa3c7
|
4
|
+
data.tar.gz: 0e94b7913c864277937c7b1024fd2e6d99a3730598112eba16e86ff5fc286b6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 524f57ff6a71f07de04c173f84ed7465d8b5f97450afda4a5e0f6c21ba29e44d41a8aa0958d675b38b6daa2bca7fd0017c99eabda4c77c72cd63ebbc21309e38
|
7
|
+
data.tar.gz: f9d5f408ffaeb533e2e8c0d3eb156bd2493f2928dc2aa4dee07e9e1ca0053ec4f6df5a5c7f57ed2b335fa9fc2f14f24efc2265d4700cc76614c53bf66917c555
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
(Maybe) Ruby's fastest JSON library! Built using the [RapidJSON C++ library](https://rapidjson.org/)
|
4
4
|
|
5
|
-
|
5
|
+
ActiveSupport integration, `json` gem emulation, and no monkey patches.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -44,24 +44,24 @@ RapidJSON.pretty_encode(json_string)
|
|
44
44
|
# }
|
45
45
|
```
|
46
46
|
|
47
|
-
By default the encoder is "strict" and will raise an exception
|
47
|
+
By default the encoder is "strict" and will raise an exception.
|
48
48
|
|
49
49
|
## ActiveSupport
|
50
50
|
|
51
51
|
RapidJSON provides a drop-in replacement ActiveSupport encoder, with very good compatibility.
|
52
52
|
Add the following to an initializer to opt-in.
|
53
53
|
|
54
|
-
```
|
54
|
+
```ruby
|
55
55
|
# config/initializers/rapidjson.rb
|
56
56
|
|
57
57
|
ActiveSupport::JSON::Encoding.json_encoder = RapidJSON::ActiveSupportEncoder
|
58
58
|
```
|
59
59
|
|
60
|
-
This makes `model.to_json` ~15x faster, and `nested_hash.to_json` ~27x faster (
|
60
|
+
This makes `model.to_json` ~15x faster, and `nested_hash.to_json` ~27x faster (compared using Rails 7.0)
|
61
61
|
|
62
62
|
## JSON gem compatibility
|
63
63
|
|
64
|
-
Contrary to some other JSON libraries, `RapidJSON` doesn't
|
64
|
+
Contrary to some other JSON libraries, `RapidJSON` doesn't provide a monkey patch to entirely replace the stdlib JSON gem.
|
65
65
|
|
66
66
|
However it does provide a module that behave like the stdlib JSON gem and that can be used to monkey patch existing code.
|
67
67
|
|
@@ -86,7 +86,7 @@ By default RapidJSON will only encode "JSON-ready" types: `Hash`, `Array`, `Inte
|
|
86
86
|
|
87
87
|
RapidJSON::Coder can be initialized with a block which allows the behaviour to be customized. This is how the ActiveSupport encoder and JSON compatibility above are implemented! Just using Ruby :heart:.
|
88
88
|
|
89
|
-
```
|
89
|
+
```ruby
|
90
90
|
RapidJSON::Coder.new do |object, is_key|
|
91
91
|
object.to_s # Convert any unknown object to string
|
92
92
|
end
|
@@ -105,11 +105,11 @@ Unless there's good reason, it's probably best sticking with the standard `json`
|
|
105
105
|
However this library has a few performance advantages:
|
106
106
|
|
107
107
|
* JSON parsing
|
108
|
-
* Performance is achieved mostly through using RapidJSON one of the fastest open source JSON parsing libraries. It supports SIMD (SSE2, SSE4.2, NEON), avoids allocated memory, and has been honed to be if not the fastest library (that honour likely going to simdjson) the library to beat for JSON performance.
|
108
|
+
* Performance is achieved mostly through using RapidJSON one of the fastest open source JSON parsing libraries. It supports SIMD (SSE2, SSE4.2, NEON), avoids allocated memory, and has been honed to be if not the fastest library (that honour likely going to simdjson), the library to beat for JSON performance.
|
109
109
|
* Object allocation
|
110
110
|
* Wherever possible we avoid allocating objects. When generating JSON, RapidJSON will write the emitted JSON directly into the buffer of a Ruby string. (This is an optimization most Ruby JSON libraries will have)
|
111
111
|
* When parsing JSON we parse directly form the source string with a single copy
|
112
|
-
* When building a Hash for a JSON object, we use
|
112
|
+
* When building a Hash for a JSON object, we use `fstrings` (dedup'd and frozen strings) as the key
|
113
113
|
* Whenever possible we build Ruby objects from C types (int, char \*, double) rather than constructing intermediate Ruby string objects.
|
114
114
|
|
115
115
|
Many of these optimization can be found in all popular Ruby JSON libraries
|
@@ -158,4 +158,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
158
158
|
|
159
159
|
## Code of Conduct
|
160
160
|
|
161
|
-
Everyone interacting in the
|
161
|
+
Everyone interacting in the RapidJSON project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/jhawthorn/rapidjson/blob/main/CODE_OF_CONDUCT.md).
|
data/ext/rapidjson/cext.cc
CHANGED
@@ -19,26 +19,44 @@ typedef RubyStringBuffer DefaultBuffer;
|
|
19
19
|
static VALUE
|
20
20
|
dump(VALUE _self, VALUE obj, VALUE pretty, VALUE as_json, VALUE allow_nan) {
|
21
21
|
// NB: as_json here is not marked by the extension, but is always on the stack
|
22
|
+
VALUE result;
|
23
|
+
int state;
|
24
|
+
|
22
25
|
if (RTEST(pretty)) {
|
23
26
|
RubyObjectEncoder<DefaultBuffer, PrettyWriter<DefaultBuffer> > encoder(as_json, RTEST(allow_nan));
|
24
|
-
|
27
|
+
encoder.writer.SetIndent(' ', 2);
|
28
|
+
result = encoder.encode_protected(obj, &state);
|
25
29
|
} else {
|
26
30
|
RubyObjectEncoder<DefaultBuffer, Writer<DefaultBuffer> > encoder(as_json, RTEST(allow_nan));
|
27
|
-
|
31
|
+
result = encoder.encode_protected(obj, &state);
|
32
|
+
}
|
33
|
+
|
34
|
+
if (state) {
|
35
|
+
rb_jump_tag(state);
|
28
36
|
}
|
37
|
+
return result;
|
29
38
|
}
|
30
39
|
|
31
40
|
static VALUE
|
32
|
-
load(VALUE _self, VALUE string) {
|
33
|
-
RubyObjectHandler handler;
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
41
|
+
load(VALUE _self, VALUE string, VALUE allow_nan) {
|
42
|
+
RubyObjectHandler handler(RTEST(allow_nan));
|
43
|
+
ParseResult ok;
|
44
|
+
|
45
|
+
{
|
46
|
+
char *cstring = StringValueCStr(string); // fixme?
|
47
|
+
StringStream ss(cstring);
|
48
|
+
Reader reader;
|
49
|
+
ok = reader.Parse<kParseNanAndInfFlag>(ss, handler);
|
50
|
+
}
|
38
51
|
|
39
52
|
if (!ok) {
|
40
|
-
|
41
|
-
|
53
|
+
VALUE err = handler.GetErr();
|
54
|
+
if (RTEST(err)) {
|
55
|
+
rb_exc_raise(err);
|
56
|
+
} else {
|
57
|
+
rb_raise(rb_eParseError, "JSON parse error: %s (%lu)",
|
58
|
+
GetParseError_En(ok.Code()), ok.Offset());
|
59
|
+
}
|
42
60
|
}
|
43
61
|
|
44
62
|
return handler.GetRoot();
|
@@ -59,6 +77,57 @@ valid_json_p(VALUE _self, VALUE string) {
|
|
59
77
|
return Qtrue;
|
60
78
|
}
|
61
79
|
|
80
|
+
static bool is_json_ready(VALUE obj);
|
81
|
+
|
82
|
+
static int is_json_ready_hash_i(VALUE key, VALUE val, VALUE arg) {
|
83
|
+
bool *result = (bool *)arg;
|
84
|
+
|
85
|
+
if (!RB_TYPE_P(key, T_STRING) && !RB_TYPE_P(key, T_SYMBOL)) {
|
86
|
+
*result = false;
|
87
|
+
return ST_STOP;
|
88
|
+
}
|
89
|
+
if (!is_json_ready(val)) {
|
90
|
+
*result = false;
|
91
|
+
return ST_STOP;
|
92
|
+
}
|
93
|
+
return ST_CONTINUE;
|
94
|
+
}
|
95
|
+
|
96
|
+
static bool
|
97
|
+
is_json_ready(VALUE obj) {
|
98
|
+
switch(rb_type(obj)) {
|
99
|
+
case T_NIL:
|
100
|
+
case T_FALSE:
|
101
|
+
case T_TRUE:
|
102
|
+
case T_FIXNUM:
|
103
|
+
case T_BIGNUM:
|
104
|
+
case T_FLOAT:
|
105
|
+
case T_SYMBOL:
|
106
|
+
case T_STRING:
|
107
|
+
return true;
|
108
|
+
case T_HASH:
|
109
|
+
{
|
110
|
+
bool result = true;
|
111
|
+
rb_hash_foreach(obj, is_json_ready_hash_i, (VALUE)&result);
|
112
|
+
return result;
|
113
|
+
}
|
114
|
+
case T_ARRAY:
|
115
|
+
for (int i = 0; i < RARRAY_LEN(obj); i++) {
|
116
|
+
if (!is_json_ready(RARRAY_AREF(obj, i))) {
|
117
|
+
return false;
|
118
|
+
}
|
119
|
+
}
|
120
|
+
return true;
|
121
|
+
default:
|
122
|
+
return false;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
static VALUE
|
127
|
+
json_ready_p(VALUE _self, VALUE obj) {
|
128
|
+
return is_json_ready(obj) ? Qtrue : Qfalse;
|
129
|
+
}
|
130
|
+
|
62
131
|
extern "C" void
|
63
132
|
Init_rapidjson(void)
|
64
133
|
{
|
@@ -68,7 +137,7 @@ Init_rapidjson(void)
|
|
68
137
|
rb_global_variable(&rb_cRapidJSONFragment);
|
69
138
|
|
70
139
|
rb_define_private_method(rb_cCoder, "_dump", dump, 4);
|
71
|
-
rb_define_method(rb_cCoder, "
|
140
|
+
rb_define_method(rb_cCoder, "_load", load, 2);
|
72
141
|
rb_define_method(rb_cCoder, "valid_json?", valid_json_p, 1);
|
73
142
|
|
74
143
|
VALUE rb_eRapidJSONError = rb_const_get(rb_mRapidJSON, rb_intern("Error"));
|
@@ -76,4 +145,5 @@ Init_rapidjson(void)
|
|
76
145
|
rb_eEncodeError = rb_define_class_under(rb_mRapidJSON, "EncodeError", rb_eRapidJSONError);
|
77
146
|
|
78
147
|
rb_define_singleton_method(rb_mRapidJSON, "json_escape", escape_json, 1);
|
148
|
+
rb_define_singleton_method(rb_mRapidJSON, "json_ready?", json_ready_p, 1);
|
79
149
|
}
|
data/ext/rapidjson/encoder.hh
CHANGED
@@ -8,7 +8,6 @@ using namespace rapidjson;
|
|
8
8
|
template <typename B = RubyStringBuffer, typename W=Writer<B> >
|
9
9
|
class RubyObjectEncoder {
|
10
10
|
B buf;
|
11
|
-
W writer;
|
12
11
|
VALUE as_json;
|
13
12
|
bool allow_nan;
|
14
13
|
|
@@ -200,10 +199,26 @@ class RubyObjectEncoder {
|
|
200
199
|
allow_nan = allow_nan_;
|
201
200
|
};
|
202
201
|
|
202
|
+
W writer;
|
203
203
|
int depth;
|
204
204
|
|
205
205
|
VALUE encode(VALUE obj) {
|
206
206
|
encode_any(obj, true);
|
207
207
|
return buf.GetRubyString();
|
208
208
|
}
|
209
|
+
|
210
|
+
struct protected_args {
|
211
|
+
RubyObjectEncoder *encoder;
|
212
|
+
VALUE obj;
|
213
|
+
};
|
214
|
+
|
215
|
+
static VALUE encode_protected_cb(VALUE data) {
|
216
|
+
struct protected_args *args = (struct protected_args *)data;
|
217
|
+
return args->encoder->encode(args->obj);
|
218
|
+
}
|
219
|
+
|
220
|
+
VALUE encode_protected(VALUE obj, int *state) {
|
221
|
+
struct protected_args args = { this, obj };
|
222
|
+
return rb_protect(encode_protected_cb, (VALUE)&args, state);
|
223
|
+
}
|
209
224
|
};
|
data/ext/rapidjson/parser.hh
CHANGED
@@ -50,6 +50,10 @@ struct RubyObjectHandler : public BaseReaderHandler<UTF8<>, RubyObjectHandler> {
|
|
50
50
|
}
|
51
51
|
|
52
52
|
bool Double(double d) {
|
53
|
+
if (!isfinite(d) && !allow_nan) {
|
54
|
+
err = rb_exc_new_cstr(rb_eParseError, "JSON parse error: Invalid float value");
|
55
|
+
return false;
|
56
|
+
}
|
53
57
|
return PutValue(rb_float_new(d));
|
54
58
|
}
|
55
59
|
|
@@ -92,7 +96,7 @@ struct RubyObjectHandler : public BaseReaderHandler<UTF8<>, RubyObjectHandler> {
|
|
92
96
|
depth++;
|
93
97
|
return true;
|
94
98
|
} else {
|
95
|
-
|
99
|
+
err = rb_exc_new_cstr(rb_eParseError, "JSON parse error: input too deep");
|
96
100
|
return false;
|
97
101
|
}
|
98
102
|
}
|
@@ -140,12 +144,18 @@ struct RubyObjectHandler : public BaseReaderHandler<UTF8<>, RubyObjectHandler> {
|
|
140
144
|
return stack[0];
|
141
145
|
}
|
142
146
|
|
143
|
-
|
147
|
+
VALUE GetErr() {
|
148
|
+
return err;
|
149
|
+
}
|
150
|
+
|
151
|
+
RubyObjectHandler(bool allow_nan): err(Qfalse), depth(0), allow_nan(allow_nan) {
|
144
152
|
stack[0] = Qundef;
|
145
153
|
}
|
146
154
|
|
147
155
|
static const int MAX_DEPTH = 256;
|
148
|
-
int depth;
|
149
156
|
VALUE stack[MAX_DEPTH];
|
150
157
|
VALUE last_key[MAX_DEPTH];
|
158
|
+
VALUE err;
|
159
|
+
int depth;
|
160
|
+
bool allow_nan;
|
151
161
|
};
|
@@ -14,7 +14,9 @@ module RapidJSON
|
|
14
14
|
# Encode the given object into a JSON string
|
15
15
|
def encode(value)
|
16
16
|
if @options && !@options.empty?
|
17
|
-
value
|
17
|
+
if !RapidJSON.json_ready?(value) || @options.key?(:only) || @options.key?(:except)
|
18
|
+
value = value.as_json(@options.dup)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
json = @coder.dump(value)
|
20
22
|
if ActiveSupport::JSON::Encoding.escape_html_entities_in_json
|
data/lib/rapidjson/version.rb
CHANGED
data/lib/rapidjson.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rapidjson
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fast JSON encoder/decoder based using RapidJSON
|
14
14
|
email:
|
@@ -71,13 +71,13 @@ files:
|
|
71
71
|
- lib/rapidjson/active_support_encoder.rb
|
72
72
|
- lib/rapidjson/json_gem.rb
|
73
73
|
- lib/rapidjson/version.rb
|
74
|
-
homepage: https://github.com/jhawthorn/rapidjson
|
74
|
+
homepage: https://github.com/jhawthorn/rapidjson-ruby
|
75
75
|
licenses:
|
76
76
|
- MIT
|
77
77
|
metadata:
|
78
|
-
homepage_uri: https://github.com/jhawthorn/rapidjson
|
79
|
-
source_code_uri: https://github.com/jhawthorn/rapidjson
|
80
|
-
changelog_uri: https://github.com/jhawthorn/rapidjson
|
78
|
+
homepage_uri: https://github.com/jhawthorn/rapidjson-ruby
|
79
|
+
source_code_uri: https://github.com/jhawthorn/rapidjson-ruby
|
80
|
+
changelog_uri: https://github.com/jhawthorn/rapidjson-ruby
|
81
81
|
post_install_message:
|
82
82
|
rdoc_options: []
|
83
83
|
require_paths:
|
@@ -93,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
93
|
- !ruby/object:Gem::Version
|
94
94
|
version: '0'
|
95
95
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
96
|
+
rubygems_version: 3.5.11
|
97
97
|
signing_key:
|
98
98
|
specification_version: 4
|
99
99
|
summary: Fast JSON encoder/decoder based using RapidJSON
|