json 2.7.3 → 2.7.6

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: 1500b32611dd205778d21f8175a6e587c85d8090e9861c6ab5c2bbdc8cc7b107
4
- data.tar.gz: 7bccf2b0c350f282b152f0e85addedb62d596c83a5347afa4205bb025f1dd24f
3
+ metadata.gz: 065f3d9d7ea71adff1e29821c3cac1ebcab3ab3fa8ffc5254d4977c61b998fd4
4
+ data.tar.gz: d0ec44d829e429395f42191ac8bf91e6be4498e3c4bba37eb7fdf3ad1cfce80f
5
5
  SHA512:
6
- metadata.gz: cf52a3ae9060dcebbc0febfde70aba86d718dd0545eb55da4f25e7fffa3bacbaff82e56656c189f6d7ffd25093c9a5967085864ce1db921dcb0e9cc92c9b299f
7
- data.tar.gz: 55d5e7da5ff1dee20b3a48e08d7a8e389fe8cd4a1f6569ca84d0a59f0e84085fb5af3e2a6721f76ea7dfd860bdbdc8844e7464572191be0c816bf0522036edd0
6
+ metadata.gz: 59f4e6204591c6fa2204e30ae4e663f5d5f6d89cfc44a26174da1a17cd720cd6594ad047b98933295a33f793b89e97c7952095bedda1d25f378b7a99ead84b04
7
+ data.tar.gz: 7438175fc69f92a7aa2a13b7599b417111562d9edfd94987dec4f94c37e5f2b152a6025d2f6ed2d49d246f0b0e3fe53a71038c9c59e7f826e8d3c2e09e40994a
data/CHANGES.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changes
2
2
 
3
+ ### 2024-11-04 (2.7.6)
4
+
5
+ * Fix a regression in JSON.generate when dealing with Hash keys that are string subclasses, call `to_json` on them.
6
+
7
+ ### 2024-10-25 (2.7.5)
8
+
9
+ * Fix a memory leak when `#to_json` methods raise an exception.
10
+ * Gracefully handle formatting configs being set to `nil` instead of `""`.
11
+ * Workaround another issue caused by conflicting versions of both `json_pure` and `json` being loaded.
12
+
13
+ ### 2024-10-25 (2.7.4)
14
+
15
+ * Workaround a bug in 3.4.8 and older https://github.com/rubygems/rubygems/pull/6490.
16
+ This bug would cause some gems with native extension to fail during compilation.
17
+ * Workaround different versions of `json` and `json_pure` being loaded (not officially supported).
18
+ * Make `json_pure` Ractor compatible.
19
+
3
20
  ### 2024-10-24 (2.7.3)
4
21
 
5
22
  * Numerous performance optimizations in `JSON.generate` and `JSON.dump` (up to 2 times faster).
@@ -36,6 +36,10 @@ static VALUE fbuffer_to_s(FBuffer *fb);
36
36
  #define RB_UNLIKELY(expr) expr
37
37
  #endif
38
38
 
39
+ #ifndef RB_LIKELY
40
+ #define RB_LIKELY(expr) expr
41
+ #endif
42
+
39
43
  static FBuffer *fbuffer_alloc(unsigned long initial_length)
40
44
  {
41
45
  FBuffer *fb;
@@ -403,7 +403,9 @@ static char *fstrndup(const char *ptr, unsigned long len) {
403
403
  */
404
404
  static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
405
405
  {
406
- GENERATE_JSON(object);
406
+ rb_check_arity(argc, 0, 1);
407
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
408
+ return cState_partial_generate(Vstate, self, generate_json_object);
407
409
  }
408
410
 
409
411
  /*
@@ -415,7 +417,9 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
415
417
  * produced JSON string output further.
416
418
  */
417
419
  static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
418
- GENERATE_JSON(array);
420
+ rb_check_arity(argc, 0, 1);
421
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
422
+ return cState_partial_generate(Vstate, self, generate_json_array);
419
423
  }
420
424
 
421
425
  #ifdef RUBY_INTEGER_UNIFICATION
@@ -426,7 +430,9 @@ static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
426
430
  */
427
431
  static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
428
432
  {
429
- GENERATE_JSON(integer);
433
+ rb_check_arity(argc, 0, 1);
434
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
435
+ return cState_partial_generate(Vstate, self, generate_json_integer);
430
436
  }
431
437
 
432
438
  #else
@@ -437,7 +443,9 @@ static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
437
443
  */
438
444
  static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self)
439
445
  {
440
- GENERATE_JSON(fixnum);
446
+ rb_check_arity(argc, 0, 1);
447
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
448
+ return cState_partial_generate(Vstate, self, generate_json_fixnum);
441
449
  }
442
450
 
443
451
  /*
@@ -447,7 +455,9 @@ static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self)
447
455
  */
448
456
  static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self)
449
457
  {
450
- GENERATE_JSON(bignum);
458
+ rb_check_arity(argc, 0, 1);
459
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
460
+ return cState_partial_generate(Vstate, self, generate_json_bignum);
451
461
  }
452
462
  #endif
453
463
 
@@ -458,7 +468,9 @@ static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self)
458
468
  */
459
469
  static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
460
470
  {
461
- GENERATE_JSON(float);
471
+ rb_check_arity(argc, 0, 1);
472
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
473
+ return cState_partial_generate(Vstate, self, generate_json_float);
462
474
  }
463
475
 
464
476
  /*
@@ -481,7 +493,9 @@ static VALUE mString_included_s(VALUE self, VALUE modul) {
481
493
  */
482
494
  static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
483
495
  {
484
- GENERATE_JSON(string);
496
+ rb_check_arity(argc, 0, 1);
497
+ VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
498
+ return cState_partial_generate(Vstate, self, generate_json_string);
485
499
  }
486
500
 
487
501
  /*
@@ -536,7 +550,8 @@ static VALUE mString_Extend_json_create(VALUE self, VALUE o)
536
550
  */
537
551
  static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
538
552
  {
539
- GENERATE_JSON(true);
553
+ rb_check_arity(argc, 0, 1);
554
+ return rb_utf8_str_new("true", 4);
540
555
  }
541
556
 
542
557
  /*
@@ -546,7 +561,8 @@ static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
546
561
  */
547
562
  static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
548
563
  {
549
- GENERATE_JSON(false);
564
+ rb_check_arity(argc, 0, 1);
565
+ return rb_utf8_str_new("false", 5);
550
566
  }
551
567
 
552
568
  /*
@@ -556,7 +572,8 @@ static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
556
572
  */
557
573
  static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self)
558
574
  {
559
- GENERATE_JSON(null);
575
+ rb_check_arity(argc, 0, 1);
576
+ return rb_utf8_str_new("null", 4);
560
577
  }
561
578
 
562
579
  /*
@@ -573,7 +590,7 @@ static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
573
590
  rb_scan_args(argc, argv, "01", &state);
574
591
  Check_Type(string, T_STRING);
575
592
  state = cState_from_state_s(cState, state);
576
- return cState_partial_generate(state, string);
593
+ return cState_partial_generate(state, string, generate_json_string);
577
594
  }
578
595
 
579
596
  static void State_free(void *ptr)
@@ -651,7 +668,11 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
651
668
  VALUE key_to_s;
652
669
  switch(rb_type(key)) {
653
670
  case T_STRING:
654
- key_to_s = key;
671
+ if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) {
672
+ key_to_s = key;
673
+ } else {
674
+ key_to_s = rb_funcall(key, i_to_s, 0);
675
+ }
655
676
  break;
656
677
  case T_SYMBOL:
657
678
  key_to_s = rb_sym2str(key);
@@ -661,7 +682,11 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
661
682
  break;
662
683
  }
663
684
 
664
- generate_json_string(buffer, Vstate, state, key_to_s);
685
+ if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
686
+ generate_json_string(buffer, Vstate, state, key_to_s);
687
+ } else {
688
+ generate_json(buffer, Vstate, state, key_to_s);
689
+ }
665
690
  if (RB_UNLIKELY(state->space_before)) fbuffer_append(buffer, state->space_before, state->space_before_len);
666
691
  fbuffer_append_char(buffer, ':');
667
692
  if (RB_UNLIKELY(state->space)) fbuffer_append(buffer, state->space, state->space_len);
@@ -826,6 +851,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
826
851
  generate_json_bignum(buffer, Vstate, state, obj);
827
852
  }
828
853
  #endif
854
+
829
855
  static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
830
856
  {
831
857
  double value = RFLOAT_VALUE(obj);
@@ -911,13 +937,14 @@ struct generate_json_data {
911
937
  VALUE vstate;
912
938
  JSON_Generator_State *state;
913
939
  VALUE obj;
940
+ void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
914
941
  };
915
942
 
916
943
  static VALUE generate_json_try(VALUE d)
917
944
  {
918
945
  struct generate_json_data *data = (struct generate_json_data *)d;
919
946
 
920
- generate_json(data->buffer, data->vstate, data->state, data->obj);
947
+ data->func(data->buffer, data->vstate, data->state, data->obj);
921
948
 
922
949
  return Qnil;
923
950
  }
@@ -932,7 +959,7 @@ static VALUE generate_json_rescue(VALUE d, VALUE exc)
932
959
  return Qundef;
933
960
  }
934
961
 
935
- static VALUE cState_partial_generate(VALUE self, VALUE obj)
962
+ static VALUE cState_partial_generate(VALUE self, VALUE obj, void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj))
936
963
  {
937
964
  FBuffer *buffer = cState_prepare_buffer(self);
938
965
  GET_STATE(self);
@@ -941,7 +968,8 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj)
941
968
  .buffer = buffer,
942
969
  .vstate = self,
943
970
  .state = state,
944
- .obj = obj
971
+ .obj = obj,
972
+ .func = func
945
973
  };
946
974
  rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
947
975
 
@@ -957,12 +985,18 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj)
957
985
  */
958
986
  static VALUE cState_generate(VALUE self, VALUE obj)
959
987
  {
960
- VALUE result = cState_partial_generate(self, obj);
988
+ VALUE result = cState_partial_generate(self, obj, generate_json);
961
989
  GET_STATE(self);
962
990
  (void)state;
963
991
  return result;
964
992
  }
965
993
 
994
+ static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
995
+ {
996
+ rb_warn("The json gem extension was loaded with the stdlib ruby code. You should upgrade rubygems with `gem update --system`");
997
+ return self;
998
+ }
999
+
966
1000
  /*
967
1001
  * call-seq: initialize_copy(orig)
968
1002
  *
@@ -1408,6 +1442,9 @@ void Init_generator(void)
1408
1442
  cState = rb_define_class_under(mGenerator, "State", rb_cObject);
1409
1443
  rb_define_alloc_func(cState, cState_s_allocate);
1410
1444
  rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
1445
+ rb_define_method(cState, "initialize", cState_initialize, -1);
1446
+ rb_define_alias(cState, "initialize", "initialize"); // avoid method redefinition warnings
1447
+
1411
1448
  rb_define_method(cState, "initialize_copy", cState_init_copy, 1);
1412
1449
  rb_define_method(cState, "indent", cState_indent, 0);
1413
1450
  rb_define_method(cState, "indent=", cState_indent_set, 1);
@@ -1498,4 +1535,6 @@ void Init_generator(void)
1498
1535
  usascii_encindex = rb_usascii_encindex();
1499
1536
  utf8_encindex = rb_utf8_encindex();
1500
1537
  binary_encindex = rb_ascii8bit_encindex();
1538
+
1539
+ rb_require("json/ext/generator/state");
1501
1540
  }
@@ -54,17 +54,6 @@ typedef struct JSON_Generator_StateStruct {
54
54
  JSON_Generator_State *state; \
55
55
  GET_STATE_TO(self, state)
56
56
 
57
- #define GENERATE_JSON(type) \
58
- FBuffer *buffer; \
59
- VALUE Vstate; \
60
- JSON_Generator_State *state; \
61
- \
62
- rb_scan_args(argc, argv, "01", &Vstate); \
63
- Vstate = cState_from_state_s(cState, Vstate); \
64
- TypedData_Get_Struct(Vstate, JSON_Generator_State, &JSON_Generator_State_type, state); \
65
- buffer = cState_prepare_buffer(Vstate); \
66
- generate_json_##type(buffer, Vstate, state, self); \
67
- return fbuffer_to_s(buffer)
68
57
 
69
58
  static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self);
70
59
  static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self);
@@ -99,7 +88,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
99
88
  static void generate_json_fixnum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
100
89
  static void generate_json_bignum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
101
90
  static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
102
- static VALUE cState_partial_generate(VALUE self, VALUE obj);
91
+ static VALUE cState_partial_generate(VALUE self, VALUE obj, void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj));
103
92
  static VALUE cState_generate(VALUE self, VALUE obj);
104
93
  static VALUE cState_from_state_s(VALUE self, VALUE opts);
105
94
  static VALUE cState_indent(VALUE self);
data/lib/json/common.rb CHANGED
@@ -219,7 +219,12 @@ module JSON
219
219
  if opts.nil?
220
220
  Parser.new(source).parse
221
221
  else
222
- Parser.new(source, opts).parse
222
+ # NB: The ** shouldn't be required, but we have to deal with
223
+ # different versions of the `json` and `json_pure` gems being
224
+ # loaded concurrently.
225
+ # Prior to 2.7.3, `JSON::Ext::Parser` would only take kwargs.
226
+ # Ref: https://github.com/ruby/json/issues/650
227
+ Parser.new(source, **opts).parse
223
228
  end
224
229
  end
225
230
 
@@ -46,15 +46,15 @@ module JSON
46
46
  opts.each do |key, value|
47
47
  case key
48
48
  when :indent
49
- self.indent = value
49
+ self.indent = value || ''
50
50
  when :space
51
- self.space = value
51
+ self.space = value || ''
52
52
  when :space_before
53
- self.space_before = value
53
+ self.space_before = value || ''
54
54
  when :array_nl
55
- self.array_nl = value
55
+ self.array_nl = value || ''
56
56
  when :object_nl
57
- self.object_nl = value
57
+ self.object_nl = value || ''
58
58
  when :max_nesting
59
59
  self.max_nesting = value || 0
60
60
  when :depth
data/lib/json/ext.rb CHANGED
@@ -15,9 +15,6 @@ module JSON
15
15
  else
16
16
  require 'json/ext/parser'
17
17
  require 'json/ext/generator'
18
- unless RUBY_ENGINE == 'jruby'
19
- require 'json/ext/generator/state'
20
- end
21
18
  $DEBUG and warn "Using Ext extension for JSON."
22
19
  JSON.parser = Parser
23
20
  JSON.generator = Generator
@@ -239,13 +239,13 @@ module JSON
239
239
  end
240
240
 
241
241
  # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
242
- @indent = opts[:indent] if opts.key?(:indent)
243
- @space = opts[:space] if opts.key?(:space)
244
- @space_before = opts[:space_before] if opts.key?(:space_before)
245
- @object_nl = opts[:object_nl] if opts.key?(:object_nl)
246
- @array_nl = opts[:array_nl] if opts.key?(:array_nl)
247
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
248
- @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
242
+ @indent = opts[:indent] || '' if opts.key?(:indent)
243
+ @space = opts[:space] || '' if opts.key?(:space)
244
+ @space_before = opts[:space_before] || '' if opts.key?(:space_before)
245
+ @object_nl = opts[:object_nl] || '' if opts.key?(:object_nl)
246
+ @array_nl = opts[:array_nl] || '' if opts.key?(:array_nl)
247
+ @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
248
+ @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
249
249
  @depth = opts[:depth] || 0
250
250
  @buffer_initial_length ||= opts[:buffer_initial_length]
251
251
 
@@ -301,19 +301,30 @@ module JSON
301
301
 
302
302
  # Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
303
303
  private def generate_json(obj, buf)
304
- case obj
305
- when Hash
304
+ klass = obj.class
305
+ if klass == Hash
306
306
  buf << '{'
307
307
  first = true
308
308
  obj.each_pair do |k,v|
309
309
  buf << ',' unless first
310
- fast_serialize_string(k.to_s, buf)
310
+
311
+ key_str = k.to_s
312
+ if key_str.is_a?(::String)
313
+ if key_str.class == ::String
314
+ fast_serialize_string(key_str, buf)
315
+ else
316
+ generate_json(key_str, buf)
317
+ end
318
+ else
319
+ raise TypeError, "#{k.class}#to_s returns an instance of #{key_str.class}, expected a String"
320
+ end
321
+
311
322
  buf << ':'
312
323
  generate_json(v, buf)
313
324
  first = false
314
325
  end
315
326
  buf << '}'
316
- when Array
327
+ elsif klass == Array
317
328
  buf << '['
318
329
  first = true
319
330
  obj.each do |e|
@@ -322,9 +333,9 @@ module JSON
322
333
  first = false
323
334
  end
324
335
  buf << ']'
325
- when String
336
+ elsif klass == String
326
337
  fast_serialize_string(obj, buf)
327
- when Integer
338
+ elsif klass == Integer
328
339
  buf << obj.to_s
329
340
  else
330
341
  # Note: Float is handled this way since Float#to_s is slow anyway
@@ -414,7 +425,15 @@ module JSON
414
425
  each { |key, value|
415
426
  result << delim unless first
416
427
  result << state.indent * depth if indent
417
- result = +"#{result}#{key.to_s.to_json(state)}#{state.space_before}:#{state.space}"
428
+
429
+ key_str = key.to_s
430
+ key_json = if key_str.is_a?(::String)
431
+ key_str = key_str.to_json(state)
432
+ else
433
+ raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String"
434
+ end
435
+
436
+ result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
418
437
  if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value)
419
438
  raise GeneratorError, "#{value.class} not allowed in JSON"
420
439
  elsif value.respond_to?(:to_json)
@@ -148,25 +148,25 @@ module JSON
148
148
  end
149
149
 
150
150
  # Unescape characters in strings.
151
- UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
152
- UNESCAPE_MAP.update({
153
- ?" => '"',
154
- ?\\ => '\\',
155
- ?/ => '/',
156
- ?b => "\b",
157
- ?f => "\f",
158
- ?n => "\n",
159
- ?r => "\r",
160
- ?t => "\t",
161
- ?u => nil,
162
- })
151
+ UNESCAPE_MAP = {
152
+ '"' => '"',
153
+ '\\' => '\\',
154
+ '/' => '/',
155
+ 'b' => "\b",
156
+ 'f' => "\f",
157
+ 'n' => "\n",
158
+ 'r' => "\r",
159
+ 't' => "\t",
160
+ 'u' => nil,
161
+ }.freeze
163
162
 
164
163
  STR_UMINUS = ''.respond_to?(:-@)
165
164
  def parse_string
166
165
  if scan(STRING)
167
166
  return '' if self[1].empty?
168
- string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
169
- if u = UNESCAPE_MAP[$&[1]]
167
+ string = self[1].gsub(%r{(?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff])}n) do |c|
168
+ k = $&[1]
169
+ if u = UNESCAPE_MAP.fetch(k) { k.chr }
170
170
  u
171
171
  else # \uXXXX
172
172
  bytes = ''.b
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.7.3'
4
+ VERSION = '2.7.6'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.3
4
+ version: 2.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-24 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This is a JSON implementation as a Ruby extension in C.
14
14
  email: flori@ping.de