oj 3.17.5 → 3.17.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/ext/oj/oj.c +47 -15
- data/ext/oj/oj.h +1 -0
- data/ext/oj/rails.c +20 -8
- data/lib/oj/version.rb +1 -1
- data/pages/Rails.md +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af3a1f2b343eefacdaeb1f82d4b48fe67bece49e4aae63ad46aa878f726ae3de
|
|
4
|
+
data.tar.gz: 8cd6e1ac300706f39d434cbbba52ea780a96b761ba2090d3d3624185bc021819
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e081fafa7bd5af792fef91dc58612d6ea85bdae7b3d709bbd945773a5ad9d1feec4c88e3844aa403098c3fa267814ef61dcbba5fb4d9ee86a239eecf5b3926fa
|
|
7
|
+
data.tar.gz: 339ba6bedb36fb30a382f8cb5037053a4197d4e9364bb11f50b79ec6ec3002cdd687868e140fec1dacabdc9cfa972a45cfcd6cb8a85e4c4148cd484f56278004
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 3.17.6 - 2026-08-10
|
|
4
|
+
|
|
5
|
+
- Fixed issue #1092, where the encoder ActiveSupport 8.1 caches with `escape: false` froze the options as they stood before `set_encoder` wrote `time_precision` into them, so `to_json(escape: false)` emitted 9 fractional digits. An options hash that names no option Oj knows no longer detaches an encoder from the defaults. (#1093)
|
|
6
|
+
|
|
3
7
|
## 3.17.5 - 2026-07-31
|
|
4
8
|
|
|
5
9
|
Most of this release comes out of a security review of the C extension. There are no API changes.
|
data/ext/oj/oj.c
CHANGED
|
@@ -836,12 +836,14 @@ static const char *make_only_value(VALUE v) {
|
|
|
836
836
|
return NULL;
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
// Apply one key and value to copts. Returns true if the key named an option Oj
|
|
840
|
+
// knows and false if it did not. An unrecognized key is still ignored rather
|
|
841
|
+
// than an error; the return only lets a caller tell the two cases apart.
|
|
842
|
+
static bool parse_one_option(VALUE k, VALUE v, Options copts) {
|
|
843
|
+
size_t len;
|
|
842
844
|
|
|
843
845
|
if (set_yesno_options(k, v, copts)) {
|
|
844
|
-
return
|
|
846
|
+
return true;
|
|
845
847
|
}
|
|
846
848
|
if (oj_indent_sym == k) {
|
|
847
849
|
switch (rb_type(v)) {
|
|
@@ -967,7 +969,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
967
969
|
}
|
|
968
970
|
} else if (bigdecimal_load_sym == k) {
|
|
969
971
|
if (Qnil == v) {
|
|
970
|
-
return
|
|
972
|
+
return true;
|
|
971
973
|
}
|
|
972
974
|
|
|
973
975
|
if (bigdecimal_sym == v || Qtrue == v) {
|
|
@@ -983,7 +985,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
983
985
|
}
|
|
984
986
|
} else if (compat_bigdecimal_sym == k) {
|
|
985
987
|
if (Qnil == v) {
|
|
986
|
-
return
|
|
988
|
+
return true;
|
|
987
989
|
}
|
|
988
990
|
copts->compat_bigdec = (Qtrue == v);
|
|
989
991
|
} else if (oj_decimal_class_sym == k) {
|
|
@@ -1078,7 +1080,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1078
1080
|
}
|
|
1079
1081
|
} else if (nan_sym == k) {
|
|
1080
1082
|
if (Qnil == v) {
|
|
1081
|
-
return
|
|
1083
|
+
return true;
|
|
1082
1084
|
}
|
|
1083
1085
|
if (null_sym == v) {
|
|
1084
1086
|
copts->dump_opts.nan_dump = NullNan;
|
|
@@ -1095,7 +1097,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1095
1097
|
}
|
|
1096
1098
|
} else if (omit_nil_sym == k) {
|
|
1097
1099
|
if (Qnil == v) {
|
|
1098
|
-
return
|
|
1100
|
+
return true;
|
|
1099
1101
|
}
|
|
1100
1102
|
if (Qtrue == v) {
|
|
1101
1103
|
copts->dump_opts.omit_nil = true;
|
|
@@ -1106,7 +1108,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1106
1108
|
}
|
|
1107
1109
|
} else if (omit_null_byte_sym == k) {
|
|
1108
1110
|
if (Qnil == v) {
|
|
1109
|
-
return
|
|
1111
|
+
return true;
|
|
1110
1112
|
}
|
|
1111
1113
|
if (Qtrue == v) {
|
|
1112
1114
|
copts->dump_opts.omit_null_byte = true;
|
|
@@ -1167,7 +1169,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1167
1169
|
}
|
|
1168
1170
|
} else if (integer_range_sym == k) {
|
|
1169
1171
|
if (Qnil == v) {
|
|
1170
|
-
return
|
|
1172
|
+
return true;
|
|
1171
1173
|
}
|
|
1172
1174
|
if (rb_obj_class(v) == rb_cRange) {
|
|
1173
1175
|
VALUE min = rb_funcall(v, oj_begin_id, 0);
|
|
@@ -1198,7 +1200,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1198
1200
|
}
|
|
1199
1201
|
} else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) {
|
|
1200
1202
|
if (Qnil == v) {
|
|
1201
|
-
return
|
|
1203
|
+
return true;
|
|
1202
1204
|
}
|
|
1203
1205
|
copts->sym_key = (Qtrue == v) ? Yes : No;
|
|
1204
1206
|
|
|
@@ -1233,21 +1235,51 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1233
1235
|
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1234
1236
|
}
|
|
1235
1237
|
copts->dump_opts.except = make_only_value(v);
|
|
1238
|
+
} else {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Collects the return of parse_one_option() over every key in a hash.
|
|
1245
|
+
struct _optsParse {
|
|
1246
|
+
Options copts;
|
|
1247
|
+
bool consumed;
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
1251
|
+
struct _optsParse *op = (struct _optsParse *)opts;
|
|
1252
|
+
|
|
1253
|
+
if (parse_one_option(k, v, op->copts)) {
|
|
1254
|
+
op->consumed = true;
|
|
1236
1255
|
}
|
|
1237
1256
|
return ST_CONTINUE;
|
|
1238
1257
|
}
|
|
1239
1258
|
|
|
1240
|
-
|
|
1259
|
+
// Apply the options in ropts to copts and report whether any key was
|
|
1260
|
+
// recognized. A hash of nothing but unknown keys leaves copts untouched and
|
|
1261
|
+
// returns false.
|
|
1262
|
+
bool oj_parse_options_consumed(VALUE ropts, Options copts) {
|
|
1263
|
+
struct _optsParse op = {copts, false};
|
|
1264
|
+
|
|
1241
1265
|
if (T_HASH != rb_type(ropts)) {
|
|
1242
|
-
return;
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
rb_hash_foreach(ropts, parse_options_cb, (VALUE)&op);
|
|
1269
|
+
if (Qnil != rb_hash_lookup(ropts, match_string_sym)) {
|
|
1270
|
+
op.consumed = true;
|
|
1243
1271
|
}
|
|
1244
|
-
rb_hash_foreach(ropts, parse_options_cb, (VALUE)copts);
|
|
1245
1272
|
oj_parse_opt_match_string(&copts->str_rx, ropts);
|
|
1246
1273
|
|
|
1247
1274
|
copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size ||
|
|
1248
1275
|
0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size ||
|
|
1249
1276
|
0 < copts->dump_opts.array_size);
|
|
1250
|
-
|
|
1277
|
+
|
|
1278
|
+
return op.consumed;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
void oj_parse_options(VALUE ropts, Options copts) {
|
|
1282
|
+
oj_parse_options_consumed(ropts, copts);
|
|
1251
1283
|
}
|
|
1252
1284
|
|
|
1253
1285
|
// Free the option buffers that oj_parse_options() allocated for a single call.
|
data/ext/oj/oj.h
CHANGED
|
@@ -257,6 +257,7 @@ extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len)
|
|
|
257
257
|
|
|
258
258
|
extern bool oj_hash_has_key(VALUE hash, VALUE key);
|
|
259
259
|
extern void oj_parse_options(VALUE ropts, Options copts);
|
|
260
|
+
extern bool oj_parse_options_consumed(VALUE ropts, Options copts);
|
|
260
261
|
extern void oj_free_call_options(Options copts);
|
|
261
262
|
extern void oj_options_take_ownership(Options copts);
|
|
262
263
|
extern void oj_options_release(Options copts);
|
data/ext/oj/rails.c
CHANGED
|
@@ -765,11 +765,15 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
|
|
|
765
765
|
e->arg = rb_hash_new();
|
|
766
766
|
}
|
|
767
767
|
// An encoder given no options of its own reads oj_default_options at encode
|
|
768
|
-
// time rather than copying it here. ActiveSupport keeps
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
768
|
+
// time rather than copying it here. ActiveSupport keeps such an encoder for
|
|
769
|
+
// the life of the process, so a copy taken now would freeze every later
|
|
770
|
+
// Oj.default_options and ActiveSupport::JSON::Encoding change out of it -
|
|
771
|
+
// including the time_precision that set_encoder itself writes after
|
|
772
|
+
// ActiveSupport has already built and cached that encoder.
|
|
773
|
+
//
|
|
774
|
+
// A hash counts as options only if at least one key names an option Oj
|
|
775
|
+
// knows. Non-empty is not enough: ActiveSupport 8.1 caches a second encoder
|
|
776
|
+
// built with {escape: false}, a key Oj has never had.
|
|
773
777
|
e->own_opts = T_HASH == rb_type(e->arg) && 0 < RHASH_SIZE(e->arg);
|
|
774
778
|
if (e->own_opts) {
|
|
775
779
|
e->opts = oj_default_options;
|
|
@@ -778,9 +782,13 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
|
|
|
778
782
|
// its own that is freed with the encoder.
|
|
779
783
|
e->opts.str_rx.head = NULL;
|
|
780
784
|
e->opts.str_rx.tail = NULL;
|
|
781
|
-
|
|
785
|
+
e->own_opts = oj_parse_options_consumed(e->arg, &e->opts);
|
|
786
|
+
}
|
|
787
|
+
if (e->own_opts) {
|
|
782
788
|
oj_options_take_ownership(&e->opts);
|
|
783
789
|
} else {
|
|
790
|
+
// Nothing to free: every option that allocates is reached through a
|
|
791
|
+
// recognized key, and ownership is not taken until after that check.
|
|
784
792
|
memset(&e->opts, 0, sizeof(e->opts));
|
|
785
793
|
}
|
|
786
794
|
|
|
@@ -1176,8 +1184,6 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1176
1184
|
} else {
|
|
1177
1185
|
rb_raise(rb_eStandardError, "ActiveSupport not loaded.");
|
|
1178
1186
|
}
|
|
1179
|
-
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1180
|
-
|
|
1181
1187
|
json = rb_const_get_at(active, rb_intern("JSON"));
|
|
1182
1188
|
encoding = rb_const_get_at(json, rb_intern("Encoding"));
|
|
1183
1189
|
|
|
@@ -1204,6 +1210,12 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1204
1210
|
rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
|
|
1205
1211
|
rb_gv_set("$VERBOSE", verbose);
|
|
1206
1212
|
|
|
1213
|
+
// Last, because ActiveSupport builds and caches encoders inside
|
|
1214
|
+
// json_encoder= and they should see the settings above already in the
|
|
1215
|
+
// defaults. escape_html and xml_time are read ahead of it for the same
|
|
1216
|
+
// reason.
|
|
1217
|
+
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1218
|
+
|
|
1207
1219
|
return Qnil;
|
|
1208
1220
|
}
|
|
1209
1221
|
|
data/lib/oj/version.rb
CHANGED
data/pages/Rails.md
CHANGED
|
@@ -91,6 +91,10 @@ The classes that can be put in optimized mode and are optimized when
|
|
|
91
91
|
|
|
92
92
|
Both `Oj::Rails` and each `Oj::Rails::Encoder` have `optimize()`, `deoptimize()`, and `optimized?()` methods. The module level methods change the setting for the whole process. An encoder follows those process wide settings until `optimize()` or `deoptimize()` is called on the encoder itself. From then on the encoder keeps a set of its own and later module level changes do not reach it.
|
|
93
93
|
|
|
94
|
+
Options work the same way. An encoder reads `Oj.default_options` when it encodes, so later changes to the defaults reach it, unless it was given options of its own, in which case it keeps the copy it took when it was created. An encoder counts as having options of its own only if the hash it was built with names at least one option Oj knows. A hash of nothing but keys Oj does not have, such as the `escape: false` ActiveSupport 8.1 builds one of its cached encoders with, leaves the encoder on the defaults.
|
|
95
|
+
|
|
96
|
+
Once a class is optimized its `as_json()` method is no longer called, so an application that defines its own `as_json()` on an optimized class, `BigDecimal` for example, will not see it used. Call `Oj::Rails.deoptimize(BigDecimal)` after `Oj.optimize_rails` to put that one class back on the `as_json()` path and leave the rest optimized.
|
|
97
|
+
|
|
94
98
|
The ActiveSupport decoder is the `JSON.parse()` method. Calling the
|
|
95
99
|
`Oj::Rails.set_decoder()` method replaces that method with the Oj equivalent.
|
|
96
100
|
|