json 2.15.1 → 2.16.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/CHANGES.md +15 -0
- data/LEGAL +12 -0
- data/README.md +17 -1
- data/ext/json/ext/fbuffer/fbuffer.h +3 -52
- data/ext/json/ext/generator/generator.c +188 -135
- data/ext/json/ext/json.h +92 -0
- data/ext/json/ext/parser/extconf.rb +2 -0
- data/ext/json/ext/parser/parser.c +400 -316
- data/ext/json/ext/simd/simd.h +15 -12
- data/ext/json/ext/vendor/ryu.h +819 -0
- data/lib/json/common.rb +7 -12
- data/lib/json/ext/generator/state.rb +4 -0
- data/lib/json/truffle_ruby/generator.rb +36 -8
- data/lib/json/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8bd00bbc63367659eb90ee7e3f4f1394fecdf713fc724f7908c6a2c7b79bbf7
|
|
4
|
+
data.tar.gz: efa1d81f687277247649de777f25be01e56ad8dfac1bdd9ce61111e10231185e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb8b505c08b88115a87aa0b6ab8b7f34d6e0611af7d37277cef243131990db6cb77ca2508045104588607d54bd58b0c2e67ce0728bd72c4c32d2d77a56b9762e
|
|
7
|
+
data.tar.gz: 8dec0d5ba99f33e68049328f060a600da2fe0545caf47c44c2e812a8316cbe64a99152f365be8654cb4af45493de3bed97baa61a0b2a71ae71a7466d0ee11e8c
|
data/CHANGES.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2025-11-07 (2.16.0)
|
|
6
|
+
|
|
7
|
+
* Deprecate `JSON::State#[]` and `JSON::State#[]=`. Consider using `JSON::Coder` instead.
|
|
8
|
+
* `JSON::Coder` now also yields to the block when encountering strings with invalid encoding.
|
|
9
|
+
* Fix GeneratorError messages to be UTF-8 encoded.
|
|
10
|
+
* Fix memory leak when `Exception` is raised, or `throw` is used during JSON generation.
|
|
11
|
+
* Optimized floating point number parsing by integrating the ryu algorithm (thanks to Josef Šimánek).
|
|
12
|
+
* Optimized numbers parsing using SWAR (thanks to Scott Myron).
|
|
13
|
+
* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
|
|
14
|
+
|
|
15
|
+
### 2025-10-25 (2.15.2)
|
|
16
|
+
|
|
17
|
+
* Fix `JSON::Coder` to have one dedicated depth counter per invocation.
|
|
18
|
+
After encountering a circular reference in `JSON::Coder#dump`, any further `#dump` call would raise `JSON::NestingError`.
|
|
19
|
+
|
|
5
20
|
### 2025-10-07 (2.15.1)
|
|
6
21
|
|
|
7
22
|
* Fix incorrect escaping in the JRuby extension when encoding shared strings.
|
data/LEGAL
CHANGED
|
@@ -6,3 +6,15 @@
|
|
|
6
6
|
All the files in this distribution are covered under either the Ruby's
|
|
7
7
|
license (see the file COPYING) or public-domain except some files
|
|
8
8
|
mentioned below.
|
|
9
|
+
|
|
10
|
+
ext/json/ext/vendor/fpconv.h::
|
|
11
|
+
This file is adapted from https://github.com/night-shift/fpconv
|
|
12
|
+
It is licensed under Boost Software License 1.0.
|
|
13
|
+
|
|
14
|
+
ext/json/ext/vendor/jeaiii-ltoa.h::
|
|
15
|
+
This file is adapted from https://github.com/jeaiii/itoa
|
|
16
|
+
It is licensed under the MIT License
|
|
17
|
+
|
|
18
|
+
ext/json/ext/vendor/ryu.h::
|
|
19
|
+
This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu.
|
|
20
|
+
It is dual-licensed under Apache License 2.0 OR Boost Software License 1.0.
|
data/README.md
CHANGED
|
@@ -113,7 +113,23 @@ puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
|
|
|
113
113
|
The provided block is called for all objects that don't have a native JSON equivalent, and
|
|
114
114
|
must return a Ruby object that has a native JSON equivalent.
|
|
115
115
|
|
|
116
|
-
It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}
|
|
116
|
+
It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`,
|
|
117
|
+
as well as for strings that aren't valid UTF-8:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
coder = JSON::Combining.new do |object, is_object_key|
|
|
121
|
+
case object
|
|
122
|
+
when String
|
|
123
|
+
if !string.valid_encoding? || string.encoding != Encoding::UTF_8
|
|
124
|
+
Base64.encode64(string)
|
|
125
|
+
else
|
|
126
|
+
string
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
object
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
117
133
|
|
|
118
134
|
## Combining JSON fragments
|
|
119
135
|
|
|
@@ -1,55 +1,9 @@
|
|
|
1
1
|
#ifndef _FBUFFER_H_
|
|
2
2
|
#define _FBUFFER_H_
|
|
3
3
|
|
|
4
|
-
#include "
|
|
5
|
-
#include "ruby/encoding.h"
|
|
4
|
+
#include "../json.h"
|
|
6
5
|
#include "../vendor/jeaiii-ltoa.h"
|
|
7
6
|
|
|
8
|
-
/* shims */
|
|
9
|
-
/* This is the fallback definition from Ruby 3.4 */
|
|
10
|
-
|
|
11
|
-
#ifndef RBIMPL_STDBOOL_H
|
|
12
|
-
#if defined(__cplusplus)
|
|
13
|
-
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
14
|
-
# include <cstdbool>
|
|
15
|
-
# endif
|
|
16
|
-
#elif defined(HAVE_STDBOOL_H)
|
|
17
|
-
# include <stdbool.h>
|
|
18
|
-
#elif !defined(HAVE__BOOL)
|
|
19
|
-
typedef unsigned char _Bool;
|
|
20
|
-
# define bool _Bool
|
|
21
|
-
# define true ((_Bool)+1)
|
|
22
|
-
# define false ((_Bool)+0)
|
|
23
|
-
# define __bool_true_false_are_defined
|
|
24
|
-
#endif
|
|
25
|
-
#endif
|
|
26
|
-
|
|
27
|
-
#ifndef NOINLINE
|
|
28
|
-
#if defined(__has_attribute) && __has_attribute(noinline)
|
|
29
|
-
#define NOINLINE() __attribute__((noinline))
|
|
30
|
-
#else
|
|
31
|
-
#define NOINLINE()
|
|
32
|
-
#endif
|
|
33
|
-
#endif
|
|
34
|
-
|
|
35
|
-
#ifndef RB_UNLIKELY
|
|
36
|
-
#define RB_UNLIKELY(expr) expr
|
|
37
|
-
#endif
|
|
38
|
-
|
|
39
|
-
#ifndef RB_LIKELY
|
|
40
|
-
#define RB_LIKELY(expr) expr
|
|
41
|
-
#endif
|
|
42
|
-
|
|
43
|
-
#ifndef MAYBE_UNUSED
|
|
44
|
-
# define MAYBE_UNUSED(x) x
|
|
45
|
-
#endif
|
|
46
|
-
|
|
47
|
-
#ifdef RUBY_DEBUG
|
|
48
|
-
#ifndef JSON_DEBUG
|
|
49
|
-
#define JSON_DEBUG RUBY_DEBUG
|
|
50
|
-
#endif
|
|
51
|
-
#endif
|
|
52
|
-
|
|
53
7
|
enum fbuffer_type {
|
|
54
8
|
FBUFFER_HEAP_ALLOCATED = 0,
|
|
55
9
|
FBUFFER_STACK_ALLOCATED = 1,
|
|
@@ -283,14 +237,11 @@ static VALUE fbuffer_finalize(FBuffer *fb)
|
|
|
283
237
|
{
|
|
284
238
|
if (fb->io) {
|
|
285
239
|
fbuffer_flush(fb);
|
|
286
|
-
fbuffer_free(fb);
|
|
287
240
|
rb_io_flush(fb->io);
|
|
288
241
|
return fb->io;
|
|
289
242
|
} else {
|
|
290
|
-
|
|
291
|
-
fbuffer_free(fb);
|
|
292
|
-
return result;
|
|
243
|
+
return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
|
|
293
244
|
}
|
|
294
245
|
}
|
|
295
246
|
|
|
296
|
-
#endif
|
|
247
|
+
#endif // _FBUFFER_H_
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "../json.h"
|
|
2
2
|
#include "../fbuffer/fbuffer.h"
|
|
3
3
|
#include "../vendor/fpconv.c"
|
|
4
4
|
|
|
@@ -36,10 +36,6 @@ typedef struct JSON_Generator_StateStruct {
|
|
|
36
36
|
bool strict;
|
|
37
37
|
} JSON_Generator_State;
|
|
38
38
|
|
|
39
|
-
#ifndef RB_UNLIKELY
|
|
40
|
-
#define RB_UNLIKELY(cond) (cond)
|
|
41
|
-
#endif
|
|
42
|
-
|
|
43
39
|
static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
|
|
44
40
|
|
|
45
41
|
static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode;
|
|
@@ -85,23 +81,18 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
|
|
|
85
81
|
|
|
86
82
|
static int usascii_encindex, utf8_encindex, binary_encindex;
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
RBIMPL_ATTR_NORETURN()
|
|
90
|
-
#endif
|
|
91
|
-
static void raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
84
|
+
NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
92
85
|
{
|
|
86
|
+
rb_enc_associate_index(str, utf8_encindex);
|
|
93
87
|
VALUE exc = rb_exc_new_str(eGeneratorError, str);
|
|
94
88
|
rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object);
|
|
95
89
|
rb_exc_raise(exc);
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
#ifdef RBIMPL_ATTR_NORETURN
|
|
99
|
-
RBIMPL_ATTR_NORETURN()
|
|
100
|
-
#endif
|
|
101
92
|
#ifdef RBIMPL_ATTR_FORMAT
|
|
102
93
|
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3)
|
|
103
94
|
#endif
|
|
104
|
-
static void raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
95
|
+
NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
105
96
|
{
|
|
106
97
|
va_list args;
|
|
107
98
|
va_start(args, fmt);
|
|
@@ -136,13 +127,7 @@ typedef struct _search_state {
|
|
|
136
127
|
#endif /* HAVE_SIMD */
|
|
137
128
|
} search_state;
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
#define FORCE_INLINE __attribute__((always_inline))
|
|
141
|
-
#else
|
|
142
|
-
#define FORCE_INLINE
|
|
143
|
-
#endif
|
|
144
|
-
|
|
145
|
-
static inline FORCE_INLINE void search_flush(search_state *search)
|
|
130
|
+
static ALWAYS_INLINE() void search_flush(search_state *search)
|
|
146
131
|
{
|
|
147
132
|
// Do not remove this conditional without profiling, specifically escape-heavy text.
|
|
148
133
|
// escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
|
|
@@ -185,7 +170,7 @@ static inline unsigned char search_escape_basic(search_state *search)
|
|
|
185
170
|
return 0;
|
|
186
171
|
}
|
|
187
172
|
|
|
188
|
-
static
|
|
173
|
+
static ALWAYS_INLINE() void escape_UTF8_char_basic(search_state *search)
|
|
189
174
|
{
|
|
190
175
|
const unsigned char ch = (unsigned char)*search->ptr;
|
|
191
176
|
switch (ch) {
|
|
@@ -272,7 +257,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
|
272
257
|
|
|
273
258
|
#ifdef HAVE_SIMD
|
|
274
259
|
|
|
275
|
-
static
|
|
260
|
+
static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
|
|
276
261
|
{
|
|
277
262
|
// Flush the buffer so everything up until the last 'len' characters are unflushed.
|
|
278
263
|
search_flush(search);
|
|
@@ -295,7 +280,7 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi
|
|
|
295
280
|
|
|
296
281
|
#ifdef HAVE_SIMD_NEON
|
|
297
282
|
|
|
298
|
-
static
|
|
283
|
+
static ALWAYS_INLINE() unsigned char neon_next_match(search_state *search)
|
|
299
284
|
{
|
|
300
285
|
uint64_t mask = search->matches_mask;
|
|
301
286
|
uint32_t index = trailing_zeros64(mask) >> 2;
|
|
@@ -409,7 +394,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
|
409
394
|
|
|
410
395
|
#ifdef HAVE_SIMD_SSE2
|
|
411
396
|
|
|
412
|
-
static
|
|
397
|
+
static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search)
|
|
413
398
|
{
|
|
414
399
|
int mask = search->matches_mask;
|
|
415
400
|
int index = trailing_zeros(mask);
|
|
@@ -433,7 +418,7 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
|
|
|
433
418
|
#define TARGET_SSE2
|
|
434
419
|
#endif
|
|
435
420
|
|
|
436
|
-
static
|
|
421
|
+
static TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search)
|
|
437
422
|
{
|
|
438
423
|
if (RB_UNLIKELY(search->has_matches)) {
|
|
439
424
|
// There are more matches if search->matches_mask > 0.
|
|
@@ -995,13 +980,12 @@ static inline VALUE vstate_get(struct generate_json_data *data)
|
|
|
995
980
|
return data->vstate;
|
|
996
981
|
}
|
|
997
982
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
};
|
|
983
|
+
static VALUE
|
|
984
|
+
json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
|
|
985
|
+
{
|
|
986
|
+
VALUE proc_args[2] = {object, is_key};
|
|
987
|
+
return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
|
|
988
|
+
}
|
|
1005
989
|
|
|
1006
990
|
static VALUE
|
|
1007
991
|
convert_string_subclass(VALUE key)
|
|
@@ -1018,6 +1002,129 @@ convert_string_subclass(VALUE key)
|
|
|
1018
1002
|
return key_to_s;
|
|
1019
1003
|
}
|
|
1020
1004
|
|
|
1005
|
+
static bool enc_utf8_compatible_p(int enc_idx)
|
|
1006
|
+
{
|
|
1007
|
+
if (enc_idx == usascii_encindex) return true;
|
|
1008
|
+
if (enc_idx == utf8_encindex) return true;
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
static VALUE encode_json_string_try(VALUE str)
|
|
1013
|
+
{
|
|
1014
|
+
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
1018
|
+
{
|
|
1019
|
+
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
1020
|
+
return Qundef;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
static inline bool valid_json_string_p(VALUE str)
|
|
1024
|
+
{
|
|
1025
|
+
int coderange = rb_enc_str_coderange(str);
|
|
1026
|
+
|
|
1027
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) {
|
|
1032
|
+
return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
1039
|
+
{
|
|
1040
|
+
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
1041
|
+
return str;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
|
|
1045
|
+
VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
|
|
1046
|
+
if (coerced_str != str) {
|
|
1047
|
+
if (RB_TYPE_P(coerced_str, T_STRING)) {
|
|
1048
|
+
if (!valid_json_string_p(coerced_str)) {
|
|
1049
|
+
raise_generator_error(str, "source sequence is illegal/malformed utf-8");
|
|
1050
|
+
}
|
|
1051
|
+
} else {
|
|
1052
|
+
// as_json could return another type than T_STRING
|
|
1053
|
+
if (is_key) {
|
|
1054
|
+
raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return coerced_str;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (RB_ENCODING_GET_INLINED(str) == binary_encindex) {
|
|
1063
|
+
VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
1064
|
+
switch (rb_enc_str_coderange(utf8_string)) {
|
|
1065
|
+
case ENC_CODERANGE_7BIT:
|
|
1066
|
+
return utf8_string;
|
|
1067
|
+
case ENC_CODERANGE_VALID:
|
|
1068
|
+
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
1069
|
+
// TODO: Raise in 3.0.0
|
|
1070
|
+
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
1071
|
+
return utf8_string;
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1080
|
+
{
|
|
1081
|
+
fbuffer_append_char(buffer, '"');
|
|
1082
|
+
|
|
1083
|
+
long len;
|
|
1084
|
+
search_state search;
|
|
1085
|
+
search.buffer = buffer;
|
|
1086
|
+
RSTRING_GETMEM(obj, search.ptr, len);
|
|
1087
|
+
search.cursor = search.ptr;
|
|
1088
|
+
search.end = search.ptr + len;
|
|
1089
|
+
|
|
1090
|
+
#ifdef HAVE_SIMD
|
|
1091
|
+
search.matches_mask = 0;
|
|
1092
|
+
search.has_matches = false;
|
|
1093
|
+
search.chunk_base = NULL;
|
|
1094
|
+
#endif /* HAVE_SIMD */
|
|
1095
|
+
|
|
1096
|
+
switch (rb_enc_str_coderange(obj)) {
|
|
1097
|
+
case ENC_CODERANGE_7BIT:
|
|
1098
|
+
case ENC_CODERANGE_VALID:
|
|
1099
|
+
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
1100
|
+
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
1101
|
+
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
1102
|
+
convert_UTF8_to_script_safe_JSON(&search);
|
|
1103
|
+
} else {
|
|
1104
|
+
convert_UTF8_to_JSON(&search);
|
|
1105
|
+
}
|
|
1106
|
+
break;
|
|
1107
|
+
default:
|
|
1108
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
fbuffer_append_char(buffer, '"');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1115
|
+
{
|
|
1116
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
1117
|
+
raw_generate_json_string(buffer, data, obj);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
struct hash_foreach_arg {
|
|
1121
|
+
VALUE hash;
|
|
1122
|
+
struct generate_json_data *data;
|
|
1123
|
+
int first_key_type;
|
|
1124
|
+
bool first;
|
|
1125
|
+
bool mixed_keys_encountered;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1021
1128
|
NOINLINE()
|
|
1022
1129
|
static void
|
|
1023
1130
|
json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
@@ -1034,13 +1141,6 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
|
1034
1141
|
}
|
|
1035
1142
|
}
|
|
1036
1143
|
|
|
1037
|
-
static VALUE
|
|
1038
|
-
json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
|
|
1039
|
-
{
|
|
1040
|
-
VALUE proc_args[2] = {object, is_key};
|
|
1041
|
-
return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
1144
|
static int
|
|
1045
1145
|
json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
1046
1146
|
{
|
|
@@ -1106,8 +1206,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1106
1206
|
break;
|
|
1107
1207
|
}
|
|
1108
1208
|
|
|
1209
|
+
key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true);
|
|
1210
|
+
|
|
1109
1211
|
if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
|
|
1110
|
-
|
|
1212
|
+
raw_generate_json_string(buffer, data, key_to_s);
|
|
1111
1213
|
} else {
|
|
1112
1214
|
generate_json(buffer, data, key_to_s);
|
|
1113
1215
|
}
|
|
@@ -1124,7 +1226,7 @@ static inline long increase_depth(struct generate_json_data *data)
|
|
|
1124
1226
|
JSON_Generator_State *state = data->state;
|
|
1125
1227
|
long depth = ++state->depth;
|
|
1126
1228
|
if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) {
|
|
1127
|
-
rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
|
|
1229
|
+
rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth);
|
|
1128
1230
|
}
|
|
1129
1231
|
return depth;
|
|
1130
1232
|
}
|
|
@@ -1190,85 +1292,6 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
|
|
|
1190
1292
|
fbuffer_append_char(buffer, ']');
|
|
1191
1293
|
}
|
|
1192
1294
|
|
|
1193
|
-
static inline int enc_utf8_compatible_p(int enc_idx)
|
|
1194
|
-
{
|
|
1195
|
-
if (enc_idx == usascii_encindex) return 1;
|
|
1196
|
-
if (enc_idx == utf8_encindex) return 1;
|
|
1197
|
-
return 0;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
static VALUE encode_json_string_try(VALUE str)
|
|
1201
|
-
{
|
|
1202
|
-
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
1206
|
-
{
|
|
1207
|
-
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
1208
|
-
return Qundef;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
static inline VALUE ensure_valid_encoding(VALUE str)
|
|
1212
|
-
{
|
|
1213
|
-
int encindex = RB_ENCODING_GET(str);
|
|
1214
|
-
VALUE utf8_string;
|
|
1215
|
-
if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) {
|
|
1216
|
-
if (encindex == binary_encindex) {
|
|
1217
|
-
utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
1218
|
-
switch (rb_enc_str_coderange(utf8_string)) {
|
|
1219
|
-
case ENC_CODERANGE_7BIT:
|
|
1220
|
-
return utf8_string;
|
|
1221
|
-
case ENC_CODERANGE_VALID:
|
|
1222
|
-
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
1223
|
-
// TODO: Raise in 3.0.0
|
|
1224
|
-
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
1225
|
-
return utf8_string;
|
|
1226
|
-
break;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
1231
|
-
}
|
|
1232
|
-
return str;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1236
|
-
{
|
|
1237
|
-
obj = ensure_valid_encoding(obj);
|
|
1238
|
-
|
|
1239
|
-
fbuffer_append_char(buffer, '"');
|
|
1240
|
-
|
|
1241
|
-
long len;
|
|
1242
|
-
search_state search;
|
|
1243
|
-
search.buffer = buffer;
|
|
1244
|
-
RSTRING_GETMEM(obj, search.ptr, len);
|
|
1245
|
-
search.cursor = search.ptr;
|
|
1246
|
-
search.end = search.ptr + len;
|
|
1247
|
-
|
|
1248
|
-
#ifdef HAVE_SIMD
|
|
1249
|
-
search.matches_mask = 0;
|
|
1250
|
-
search.has_matches = false;
|
|
1251
|
-
search.chunk_base = NULL;
|
|
1252
|
-
#endif /* HAVE_SIMD */
|
|
1253
|
-
|
|
1254
|
-
switch (rb_enc_str_coderange(obj)) {
|
|
1255
|
-
case ENC_CODERANGE_7BIT:
|
|
1256
|
-
case ENC_CODERANGE_VALID:
|
|
1257
|
-
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
1258
|
-
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
1259
|
-
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
1260
|
-
convert_UTF8_to_script_safe_JSON(&search);
|
|
1261
|
-
} else {
|
|
1262
|
-
convert_UTF8_to_JSON(&search);
|
|
1263
|
-
}
|
|
1264
|
-
break;
|
|
1265
|
-
default:
|
|
1266
|
-
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1267
|
-
break;
|
|
1268
|
-
}
|
|
1269
|
-
fbuffer_append_char(buffer, '"');
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
1295
|
static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1273
1296
|
{
|
|
1274
1297
|
VALUE tmp;
|
|
@@ -1407,7 +1430,16 @@ start:
|
|
|
1407
1430
|
break;
|
|
1408
1431
|
case T_STRING:
|
|
1409
1432
|
if (klass != rb_cString) goto general;
|
|
1410
|
-
|
|
1433
|
+
|
|
1434
|
+
if (RB_LIKELY(valid_json_string_p(obj))) {
|
|
1435
|
+
raw_generate_json_string(buffer, data, obj);
|
|
1436
|
+
} else if (as_json_called) {
|
|
1437
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1438
|
+
} else {
|
|
1439
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
1440
|
+
as_json_called = true;
|
|
1441
|
+
goto start;
|
|
1442
|
+
}
|
|
1411
1443
|
break;
|
|
1412
1444
|
case T_SYMBOL:
|
|
1413
1445
|
generate_json_symbol(buffer, data, obj);
|
|
@@ -1443,16 +1475,14 @@ static VALUE generate_json_try(VALUE d)
|
|
|
1443
1475
|
|
|
1444
1476
|
data->func(data->buffer, data, data->obj);
|
|
1445
1477
|
|
|
1446
|
-
return
|
|
1478
|
+
return fbuffer_finalize(data->buffer);
|
|
1447
1479
|
}
|
|
1448
1480
|
|
|
1449
|
-
static VALUE
|
|
1481
|
+
static VALUE generate_json_ensure(VALUE d)
|
|
1450
1482
|
{
|
|
1451
1483
|
struct generate_json_data *data = (struct generate_json_data *)d;
|
|
1452
1484
|
fbuffer_free(data->buffer);
|
|
1453
1485
|
|
|
1454
|
-
rb_exc_raise(exc);
|
|
1455
|
-
|
|
1456
1486
|
return Qundef;
|
|
1457
1487
|
}
|
|
1458
1488
|
|
|
@@ -1473,9 +1503,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func,
|
|
|
1473
1503
|
.obj = obj,
|
|
1474
1504
|
.func = func
|
|
1475
1505
|
};
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
return fbuffer_finalize(&buffer);
|
|
1506
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1479
1507
|
}
|
|
1480
1508
|
|
|
1481
1509
|
/* call-seq:
|
|
@@ -1491,10 +1519,37 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
|
|
|
1491
1519
|
rb_check_arity(argc, 1, 2);
|
|
1492
1520
|
VALUE obj = argv[0];
|
|
1493
1521
|
VALUE io = argc > 1 ? argv[1] : Qnil;
|
|
1494
|
-
|
|
1522
|
+
return cState_partial_generate(self, obj, generate_json, io);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
|
|
1526
|
+
{
|
|
1527
|
+
rb_check_arity(argc, 1, 2);
|
|
1528
|
+
VALUE obj = argv[0];
|
|
1529
|
+
VALUE io = argc > 1 ? argv[1] : Qnil;
|
|
1530
|
+
|
|
1495
1531
|
GET_STATE(self);
|
|
1496
|
-
|
|
1497
|
-
|
|
1532
|
+
|
|
1533
|
+
JSON_Generator_State new_state;
|
|
1534
|
+
MEMCPY(&new_state, state, JSON_Generator_State, 1);
|
|
1535
|
+
|
|
1536
|
+
// FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently.
|
|
1537
|
+
new_state.depth = 0;
|
|
1538
|
+
|
|
1539
|
+
char stack_buffer[FBUFFER_STACK_SIZE];
|
|
1540
|
+
FBuffer buffer = {
|
|
1541
|
+
.io = RTEST(io) ? io : Qfalse,
|
|
1542
|
+
};
|
|
1543
|
+
fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1544
|
+
|
|
1545
|
+
struct generate_json_data data = {
|
|
1546
|
+
.buffer = &buffer,
|
|
1547
|
+
.vstate = Qfalse,
|
|
1548
|
+
.state = &new_state,
|
|
1549
|
+
.obj = obj,
|
|
1550
|
+
.func = generate_json
|
|
1551
|
+
};
|
|
1552
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1498
1553
|
}
|
|
1499
1554
|
|
|
1500
1555
|
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
|
|
@@ -2000,9 +2055,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
|
|
|
2000
2055
|
.obj = obj,
|
|
2001
2056
|
.func = generate_json,
|
|
2002
2057
|
};
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
return fbuffer_finalize(&buffer);
|
|
2058
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
2006
2059
|
}
|
|
2007
2060
|
|
|
2008
2061
|
/*
|
|
@@ -2072,7 +2125,7 @@ void Init_generator(void)
|
|
|
2072
2125
|
rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
|
|
2073
2126
|
rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
|
|
2074
2127
|
rb_define_method(cState, "generate", cState_generate, -1);
|
|
2075
|
-
|
|
2128
|
+
rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc:
|
|
2076
2129
|
|
|
2077
2130
|
rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
|
|
2078
2131
|
|