json 2.8.1 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +13 -0
- data/ext/json/ext/fbuffer/fbuffer.h +46 -16
- data/ext/json/ext/generator/generator.c +68 -40
- data/ext/json/ext/parser/extconf.rb +0 -1
- data/ext/json/ext/parser/parser.c +198 -267
- data/ext/json/ext/parser/parser.rl +26 -40
- data/lib/json/common.rb +44 -23
- data/lib/json/ext/generator/state.rb +11 -0
- data/lib/json/truffle_ruby/generator.rb +25 -17
- data/lib/json/version.rb +1 -1
- metadata +2 -2
@@ -26,19 +26,6 @@ static const char deprecated_create_additions_warning[] =
|
|
26
26
|
"and will be removed in 3.0, use JSON.unsafe_load or explicitly "
|
27
27
|
"pass `create_additions: true`";
|
28
28
|
|
29
|
-
#ifndef HAVE_RB_GC_MARK_LOCATIONS
|
30
|
-
// For TruffleRuby
|
31
|
-
void rb_gc_mark_locations(const VALUE *start, const VALUE *end)
|
32
|
-
{
|
33
|
-
VALUE *value = start;
|
34
|
-
|
35
|
-
while (value < end) {
|
36
|
-
rb_gc_mark(*value);
|
37
|
-
value++;
|
38
|
-
}
|
39
|
-
}
|
40
|
-
#endif
|
41
|
-
|
42
29
|
#ifndef HAVE_RB_HASH_BULK_INSERT
|
43
30
|
// For TruffleRuby
|
44
31
|
void rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash)
|
@@ -264,7 +251,10 @@ static inline void rvalue_stack_pop(rvalue_stack *stack, long count)
|
|
264
251
|
static void rvalue_stack_mark(void *ptr)
|
265
252
|
{
|
266
253
|
rvalue_stack *stack = (rvalue_stack *)ptr;
|
267
|
-
|
254
|
+
long index;
|
255
|
+
for (index = 0; index < stack->head; index++) {
|
256
|
+
rb_gc_mark(stack->ptr[index]);
|
257
|
+
}
|
268
258
|
}
|
269
259
|
|
270
260
|
static void rvalue_stack_free(void *ptr)
|
@@ -392,6 +382,7 @@ typedef struct JSON_ParserStruct {
|
|
392
382
|
VALUE decimal_class;
|
393
383
|
VALUE match_string;
|
394
384
|
FBuffer fbuffer;
|
385
|
+
int in_array;
|
395
386
|
int max_nesting;
|
396
387
|
bool allow_nan;
|
397
388
|
bool allow_trailing_comma;
|
@@ -420,8 +411,7 @@ static const rb_data_type_t JSON_Parser_type;
|
|
420
411
|
static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
421
412
|
static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
422
413
|
static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
423
|
-
static char *
|
424
|
-
static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
414
|
+
static char *JSON_parse_number(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
425
415
|
static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
426
416
|
|
427
417
|
|
@@ -627,11 +617,7 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|
627
617
|
raise_parse_error("unexpected token at '%s'", p);
|
628
618
|
}
|
629
619
|
}
|
630
|
-
np =
|
631
|
-
if (np != NULL) {
|
632
|
-
fexec np;
|
633
|
-
}
|
634
|
-
np = JSON_parse_integer(json, fpc, pe, result);
|
620
|
+
np = JSON_parse_number(json, fpc, pe, result);
|
635
621
|
if (np != NULL) {
|
636
622
|
fexec np;
|
637
623
|
}
|
@@ -640,7 +626,9 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|
640
626
|
|
641
627
|
action parse_array {
|
642
628
|
char *np;
|
629
|
+
json->in_array++;
|
643
630
|
np = JSON_parse_array(json, fpc, pe, result, current_nesting + 1);
|
631
|
+
json->in_array--;
|
644
632
|
if (np == NULL) { fhold; fbreak; } else fexec np;
|
645
633
|
}
|
646
634
|
|
@@ -716,15 +704,8 @@ static inline VALUE fast_parse_integer(char *p, char *pe)
|
|
716
704
|
return LL2NUM(memo);
|
717
705
|
}
|
718
706
|
|
719
|
-
static char *
|
707
|
+
static char *JSON_decode_integer(JSON_Parser *json, char *p, VALUE *result)
|
720
708
|
{
|
721
|
-
int cs = EVIL;
|
722
|
-
|
723
|
-
%% write init;
|
724
|
-
json->memo = p;
|
725
|
-
%% write exec;
|
726
|
-
|
727
|
-
if (cs >= JSON_integer_first_final) {
|
728
709
|
long len = p - json->memo;
|
729
710
|
if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) {
|
730
711
|
*result = fast_parse_integer(json->memo, p);
|
@@ -735,9 +716,6 @@ static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *res
|
|
735
716
|
*result = rb_cstr2inum(FBUFFER_PTR(&json->fbuffer), 10);
|
736
717
|
}
|
737
718
|
return p + 1;
|
738
|
-
} else {
|
739
|
-
return NULL;
|
740
|
-
}
|
741
719
|
}
|
742
720
|
|
743
721
|
%%{
|
@@ -747,22 +725,28 @@ static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *res
|
|
747
725
|
write data;
|
748
726
|
|
749
727
|
action exit { fhold; fbreak; }
|
728
|
+
action isFloat { is_float = true; }
|
750
729
|
|
751
730
|
main := '-'? (
|
752
|
-
(('0' | [1-9][0-9]*)
|
753
|
-
|
754
|
-
|
731
|
+
(('0' | [1-9][0-9]*)
|
732
|
+
((('.' [0-9]+ ([Ee] [+\-]?[0-9]+)?) |
|
733
|
+
([Ee] [+\-]?[0-9]+)) > isFloat)?
|
734
|
+
) (^[0-9Ee.\-]? @exit ));
|
755
735
|
}%%
|
756
736
|
|
757
|
-
static char *
|
737
|
+
static char *JSON_parse_number(JSON_Parser *json, char *p, char *pe, VALUE *result)
|
758
738
|
{
|
759
739
|
int cs = EVIL;
|
740
|
+
bool is_float = false;
|
760
741
|
|
761
742
|
%% write init;
|
762
743
|
json->memo = p;
|
763
744
|
%% write exec;
|
764
745
|
|
765
746
|
if (cs >= JSON_float_first_final) {
|
747
|
+
if (!is_float) {
|
748
|
+
return JSON_decode_integer(json, p, result);
|
749
|
+
}
|
766
750
|
VALUE mod = Qnil;
|
767
751
|
ID method_id = 0;
|
768
752
|
if (json->decimal_class) {
|
@@ -906,7 +890,7 @@ static VALUE json_string_fastpath(JSON_Parser *json, char *string, char *stringE
|
|
906
890
|
{
|
907
891
|
size_t bufferSize = stringEnd - string;
|
908
892
|
|
909
|
-
if (is_name) {
|
893
|
+
if (is_name && json->in_array) {
|
910
894
|
VALUE cached_key;
|
911
895
|
if (RB_UNLIKELY(symbolize)) {
|
912
896
|
cached_key = rsymbol_cache_fetch(&json->name_cache, string, bufferSize);
|
@@ -929,7 +913,7 @@ static VALUE json_string_unescape(JSON_Parser *json, char *string, char *stringE
|
|
929
913
|
int unescape_len;
|
930
914
|
char buf[4];
|
931
915
|
|
932
|
-
if (is_name) {
|
916
|
+
if (is_name && json->in_array) {
|
933
917
|
VALUE cached_key;
|
934
918
|
if (RB_UNLIKELY(symbolize)) {
|
935
919
|
cached_key = rsymbol_cache_fetch(&json->name_cache, string, bufferSize);
|
@@ -1355,8 +1339,10 @@ static void JSON_mark(void *ptr)
|
|
1355
1339
|
rb_gc_mark(json->match_string);
|
1356
1340
|
rb_gc_mark(json->stack_handle);
|
1357
1341
|
|
1358
|
-
|
1359
|
-
|
1342
|
+
long index;
|
1343
|
+
for (index = 0; index < json->name_cache.length; index++) {
|
1344
|
+
rb_gc_mark(json->name_cache.entries[index]);
|
1345
|
+
}
|
1360
1346
|
}
|
1361
1347
|
|
1362
1348
|
static void JSON_free(void *ptr)
|
data/lib/json/common.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#frozen_string_literal: true
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json/version'
|
3
4
|
|
4
5
|
module JSON
|
@@ -25,7 +26,7 @@ module JSON
|
|
25
26
|
elsif object.respond_to?(:to_str)
|
26
27
|
str = object.to_str
|
27
28
|
if str.is_a?(String)
|
28
|
-
return JSON.parse(
|
29
|
+
return JSON.parse(str, opts)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -142,7 +143,23 @@ module JSON
|
|
142
143
|
# :startdoc:
|
143
144
|
|
144
145
|
# This exception is raised if a generator or unparser error occurs.
|
145
|
-
class GeneratorError < JSONError
|
146
|
+
class GeneratorError < JSONError
|
147
|
+
attr_reader :invalid_object
|
148
|
+
|
149
|
+
def initialize(message, invalid_object = nil)
|
150
|
+
super(message)
|
151
|
+
@invalid_object = invalid_object
|
152
|
+
end
|
153
|
+
|
154
|
+
def detailed_message(...)
|
155
|
+
if @invalid_object.nil?
|
156
|
+
super
|
157
|
+
else
|
158
|
+
"#{super}\nInvalid object: #{@invalid_object.inspect}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
146
163
|
# For backwards compatibility
|
147
164
|
UnparserError = GeneratorError # :nodoc:
|
148
165
|
|
@@ -230,8 +247,8 @@ module JSON
|
|
230
247
|
# parse(File.read(path), opts)
|
231
248
|
#
|
232
249
|
# See method #parse.
|
233
|
-
def load_file(filespec, opts =
|
234
|
-
parse(File.read(filespec), opts)
|
250
|
+
def load_file(filespec, opts = nil)
|
251
|
+
parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
|
235
252
|
end
|
236
253
|
|
237
254
|
# :call-seq:
|
@@ -242,7 +259,7 @@ module JSON
|
|
242
259
|
#
|
243
260
|
# See method #parse!
|
244
261
|
def load_file!(filespec, opts = {})
|
245
|
-
parse!(File.read(filespec), opts)
|
262
|
+
parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
|
246
263
|
end
|
247
264
|
|
248
265
|
# :call-seq:
|
@@ -285,7 +302,7 @@ module JSON
|
|
285
302
|
if State === opts
|
286
303
|
opts.generate(obj)
|
287
304
|
else
|
288
|
-
State.generate(obj, opts)
|
305
|
+
State.generate(obj, opts, nil)
|
289
306
|
end
|
290
307
|
end
|
291
308
|
|
@@ -410,6 +427,10 @@ module JSON
|
|
410
427
|
#
|
411
428
|
# Returns the Ruby objects created by parsing the given +source+.
|
412
429
|
#
|
430
|
+
# BEWARE: This method is meant to serialise data from trusted user input,
|
431
|
+
# like from your own database server or clients under your control, it could
|
432
|
+
# be dangerous to allow untrusted users to pass JSON sources into it.
|
433
|
+
#
|
413
434
|
# - Argument +source+ must be, or be convertible to, a \String:
|
414
435
|
# - If +source+ responds to instance method +to_str+,
|
415
436
|
# <tt>source.to_str</tt> becomes the source.
|
@@ -424,9 +445,6 @@ module JSON
|
|
424
445
|
# - Argument +proc+, if given, must be a \Proc that accepts one argument.
|
425
446
|
# It will be called recursively with each result (depth-first order).
|
426
447
|
# See details below.
|
427
|
-
# BEWARE: This method is meant to serialise data from trusted user input,
|
428
|
-
# like from your own database server or clients under your control, it could
|
429
|
-
# be dangerous to allow untrusted users to pass JSON sources into it.
|
430
448
|
# - Argument +opts+, if given, contains a \Hash of options for the parsing.
|
431
449
|
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
432
450
|
# The default options can be changed via method JSON.unsafe_load_default_options=.
|
@@ -563,6 +581,16 @@ module JSON
|
|
563
581
|
#
|
564
582
|
# Returns the Ruby objects created by parsing the given +source+.
|
565
583
|
#
|
584
|
+
# BEWARE: This method is meant to serialise data from trusted user input,
|
585
|
+
# like from your own database server or clients under your control, it could
|
586
|
+
# be dangerous to allow untrusted users to pass JSON sources into it.
|
587
|
+
# If you must use it, use JSON.unsafe_load instead to make it clear.
|
588
|
+
#
|
589
|
+
# Since JSON version 2.8.0, `load` emits a deprecation warning when a
|
590
|
+
# non native type is deserialized, without `create_additions` being explicitly
|
591
|
+
# enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
|
592
|
+
# by default.
|
593
|
+
#
|
566
594
|
# - Argument +source+ must be, or be convertible to, a \String:
|
567
595
|
# - If +source+ responds to instance method +to_str+,
|
568
596
|
# <tt>source.to_str</tt> becomes the source.
|
@@ -577,10 +605,6 @@ module JSON
|
|
577
605
|
# - Argument +proc+, if given, must be a \Proc that accepts one argument.
|
578
606
|
# It will be called recursively with each result (depth-first order).
|
579
607
|
# See details below.
|
580
|
-
# BEWARE: This method is meant to serialise data from trusted user input,
|
581
|
-
# like from your own database server or clients under your control, it could
|
582
|
-
# be dangerous to allow untrusted users to pass JSON sources into it.
|
583
|
-
# If you must use it, use JSON.unsafe_load instead to make it clear.
|
584
608
|
# - Argument +opts+, if given, contains a \Hash of options for the parsing.
|
585
609
|
# See {Parsing Options}[#module-JSON-label-Parsing+Options].
|
586
610
|
# The default options can be changed via method JSON.load_default_options=.
|
@@ -793,18 +817,15 @@ module JSON
|
|
793
817
|
opts = opts.merge(:max_nesting => limit) if limit
|
794
818
|
opts = merge_dump_options(opts, **kwargs) if kwargs
|
795
819
|
|
796
|
-
|
797
|
-
|
820
|
+
begin
|
821
|
+
if State === opts
|
822
|
+
opts.generate(obj, anIO)
|
823
|
+
else
|
824
|
+
State.generate(obj, opts, anIO)
|
825
|
+
end
|
798
826
|
rescue JSON::NestingError
|
799
827
|
raise ArgumentError, "exceed depth limit"
|
800
828
|
end
|
801
|
-
|
802
|
-
if anIO.nil?
|
803
|
-
result
|
804
|
-
else
|
805
|
-
anIO.write result
|
806
|
-
anIO
|
807
|
-
end
|
808
829
|
end
|
809
830
|
|
810
831
|
# Encodes string using String.encode.
|
@@ -47,6 +47,17 @@ module JSON
|
|
47
47
|
|
48
48
|
alias_method :merge, :configure
|
49
49
|
|
50
|
+
# call-seq:
|
51
|
+
# generate(obj) -> String
|
52
|
+
# generate(obj, anIO) -> anIO
|
53
|
+
#
|
54
|
+
# Generates a valid JSON document from object +obj+ and returns the
|
55
|
+
# result. If no valid JSON document can be created this method raises a
|
56
|
+
# GeneratorError exception.
|
57
|
+
def generate(obj, io = nil)
|
58
|
+
_generate(obj, io)
|
59
|
+
end
|
60
|
+
|
50
61
|
# call-seq: to_h
|
51
62
|
#
|
52
63
|
# Returns the configuration instance variables as a hash, that can be
|
@@ -62,8 +62,8 @@ module JSON
|
|
62
62
|
string
|
63
63
|
end
|
64
64
|
|
65
|
-
def utf8_to_json_ascii(
|
66
|
-
string =
|
65
|
+
def utf8_to_json_ascii(original_string, script_safe = false) # :nodoc:
|
66
|
+
string = original_string.b
|
67
67
|
map = script_safe ? SCRIPT_SAFE_MAP : MAP
|
68
68
|
string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& }
|
69
69
|
string.gsub!(/(
|
@@ -74,7 +74,7 @@ module JSON
|
|
74
74
|
)+ |
|
75
75
|
[\x80-\xc1\xf5-\xff] # invalid
|
76
76
|
)/nx) { |c|
|
77
|
-
c.size == 1 and raise GeneratorError
|
77
|
+
c.size == 1 and raise GeneratorError.new("invalid utf8 byte: '#{c}'", original_string)
|
78
78
|
s = c.encode(::Encoding::UTF_16BE, ::Encoding::UTF_8).unpack('H*')[0]
|
79
79
|
s.force_encoding(::Encoding::BINARY)
|
80
80
|
s.gsub!(/.{4}/n, '\\\\u\&')
|
@@ -83,7 +83,7 @@ module JSON
|
|
83
83
|
string.force_encoding(::Encoding::UTF_8)
|
84
84
|
string
|
85
85
|
rescue => e
|
86
|
-
raise GeneratorError.
|
86
|
+
raise GeneratorError.new(e.message, original_string)
|
87
87
|
end
|
88
88
|
|
89
89
|
def valid_utf8?(string)
|
@@ -96,8 +96,14 @@ module JSON
|
|
96
96
|
# This class is used to create State instances, that are use to hold data
|
97
97
|
# while generating a JSON text from a Ruby data structure.
|
98
98
|
class State
|
99
|
-
def self.generate(obj, opts = nil)
|
100
|
-
new(opts).generate(obj)
|
99
|
+
def self.generate(obj, opts = nil, io = nil)
|
100
|
+
string = new(opts).generate(obj)
|
101
|
+
if io
|
102
|
+
io.write(string)
|
103
|
+
io
|
104
|
+
else
|
105
|
+
string
|
106
|
+
end
|
101
107
|
end
|
102
108
|
|
103
109
|
# Creates a State object from _opts_, which ought to be Hash to create
|
@@ -300,8 +306,10 @@ module JSON
|
|
300
306
|
else
|
301
307
|
result = obj.to_json(self)
|
302
308
|
end
|
303
|
-
JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError
|
304
|
-
"source sequence #{result.inspect} is illegal/malformed utf-8"
|
309
|
+
JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new(
|
310
|
+
"source sequence #{result.inspect} is illegal/malformed utf-8",
|
311
|
+
obj
|
312
|
+
)
|
305
313
|
result
|
306
314
|
end
|
307
315
|
|
@@ -358,10 +366,10 @@ module JSON
|
|
358
366
|
begin
|
359
367
|
string = string.encode(::Encoding::UTF_8)
|
360
368
|
rescue Encoding::UndefinedConversionError => error
|
361
|
-
raise GeneratorError
|
369
|
+
raise GeneratorError.new(error.message, string)
|
362
370
|
end
|
363
371
|
end
|
364
|
-
raise GeneratorError
|
372
|
+
raise GeneratorError.new("source sequence is illegal/malformed utf-8", string) unless string.valid_encoding?
|
365
373
|
|
366
374
|
if /["\\\x0-\x1f]/n.match?(string)
|
367
375
|
buf << string.gsub(/["\\\x0-\x1f]/n, MAP)
|
@@ -397,7 +405,7 @@ module JSON
|
|
397
405
|
# special method #to_json was defined for some object.
|
398
406
|
def to_json(state = nil, *)
|
399
407
|
if state && State.from_state(state).strict?
|
400
|
-
raise GeneratorError
|
408
|
+
raise GeneratorError.new("#{self.class} not allowed in JSON", self)
|
401
409
|
else
|
402
410
|
to_s.to_json
|
403
411
|
end
|
@@ -448,7 +456,7 @@ module JSON
|
|
448
456
|
|
449
457
|
result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
|
450
458
|
if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value)
|
451
|
-
raise GeneratorError
|
459
|
+
raise GeneratorError.new("#{value.class} not allowed in JSON", value)
|
452
460
|
elsif value.respond_to?(:to_json)
|
453
461
|
result << value.to_json(state)
|
454
462
|
else
|
@@ -501,7 +509,7 @@ module JSON
|
|
501
509
|
result << delim unless first
|
502
510
|
result << state.indent * depth if indent
|
503
511
|
if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value)
|
504
|
-
raise GeneratorError
|
512
|
+
raise GeneratorError.new("#{value.class} not allowed in JSON", value)
|
505
513
|
elsif value.respond_to?(:to_json)
|
506
514
|
result << value.to_json(state)
|
507
515
|
else
|
@@ -530,13 +538,13 @@ module JSON
|
|
530
538
|
if state.allow_nan?
|
531
539
|
to_s
|
532
540
|
else
|
533
|
-
raise GeneratorError
|
541
|
+
raise GeneratorError.new("#{self} not allowed in JSON", self)
|
534
542
|
end
|
535
543
|
when nan?
|
536
544
|
if state.allow_nan?
|
537
545
|
to_s
|
538
546
|
else
|
539
|
-
raise GeneratorError
|
547
|
+
raise GeneratorError.new("#{self} not allowed in JSON", self)
|
540
548
|
end
|
541
549
|
else
|
542
550
|
to_s
|
@@ -552,7 +560,7 @@ module JSON
|
|
552
560
|
state = State.from_state(state)
|
553
561
|
if encoding == ::Encoding::UTF_8
|
554
562
|
unless valid_encoding?
|
555
|
-
raise GeneratorError
|
563
|
+
raise GeneratorError.new("source sequence is illegal/malformed utf-8", self)
|
556
564
|
end
|
557
565
|
string = self
|
558
566
|
else
|
@@ -564,7 +572,7 @@ module JSON
|
|
564
572
|
%("#{JSON::TruffleRuby::Generator.utf8_to_json(string, state.script_safe)}")
|
565
573
|
end
|
566
574
|
rescue Encoding::UndefinedConversionError => error
|
567
|
-
raise ::JSON::GeneratorError
|
575
|
+
raise ::JSON::GeneratorError.new(error.message, self)
|
568
576
|
end
|
569
577
|
|
570
578
|
# Module that holds the extending methods if, the String module is
|
data/lib/json/version.rb
CHANGED
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.
|
4
|
+
version: 2.9.0
|
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-
|
11
|
+
date: 2024-12-03 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
|