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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 722ff2bcb2ebfc8ab861d5066e53e938933bd03d2e7f4bca7662233fa0a8a314
4
- data.tar.gz: 59d25e68af12d81bc3b5517a88de320a76a72c6b5d7cc9d26758084bd604b9fe
3
+ metadata.gz: f6e0fb853f22254885b3a5e40c0b615992bbe77c7cde00d6cf5c3f9bdd8fa3c7
4
+ data.tar.gz: 0e94b7913c864277937c7b1024fd2e6d99a3730598112eba16e86ff5fc286b6d
5
5
  SHA512:
6
- metadata.gz: 4eea208e695986e0e312c3794ba44852057f86a8159c4d862518482d263f2d13ef94fff481c38e2d7a79f881a13cef08e65eff4d49ded5c85378891bc79dab85
7
- data.tar.gz: cafbbb2e33c4adecdfac50d1fc0bb34e3c1b019828af29714b0005a8a24955c3914c79e7a9690161589597b3df12928d6adddad8887014fd48fb36b58525f25d
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
- No monkey patches, ActiveSupport integration, `json` gem emulation.
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 (compred using Rails 7.0)
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 provice a monkey patch to entirely replace the stdlib JSON gem.
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 an fstrings (dedup'd and frozen strings) as the key
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 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).
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).
@@ -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
- return encoder.encode(obj);
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
- return encoder.encode(obj);
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
- Reader reader;
35
- char *cstring = StringValueCStr(string); // fixme?
36
- StringStream ss(cstring);
37
- ParseResult ok = reader.Parse(ss, handler);
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
- rb_raise(rb_eParseError, "JSON parse error: %s (%lu)",
41
- GetParseError_En(ok.Code()), ok.Offset());
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, "load", load, 1);
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
  }
@@ -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
  };
@@ -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
- rb_raise(rb_eParseError, "JSON parse error: input too deep");
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
- RubyObjectHandler(): depth(0) {
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 = value.as_json(@options.dup)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RapidJSON
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/rapidjson.rb CHANGED
@@ -17,6 +17,10 @@ module RapidJSON
17
17
  def dump(object)
18
18
  _dump(object, @pretty, @to_json_proc, @allow_nan)
19
19
  end
20
+
21
+ def load(string)
22
+ _load(string, @allow_nan)
23
+ end
20
24
  end
21
25
  end
22
26
 
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.2.2
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: 2023-06-27 00:00:00.000000000 Z
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.4.10
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