@@ -11092,6 +11092,22 @@ parse_number_nonfinite(
11092
11092
return ms_post_decode_float(val, type, path, strict, true);
11093
11093
}
11094
11094
11095
+ static MS_NOINLINE PyObject *
11096
+ json_float_hook(
11097
+ const char *buf, Py_ssize_t size, PathNode *path, PyObject *float_hook
11098
+ ) {
11099
+ PyObject *str = PyUnicode_New(size, 127);
11100
+ if (str == NULL) return NULL;
11101
+ memcpy(ascii_get_buffer(str), buf, size);
11102
+ PyObject *out = CALL_ONE_ARG(float_hook, str);
11103
+ Py_DECREF(str);
11104
+ if (out == NULL) {
11105
+ ms_maybe_wrap_validation_error(path);
11106
+ return NULL;
11107
+ }
11108
+ return out;
11109
+ }
11110
+
11095
11111
static MS_INLINE PyObject *
11096
11112
parse_number_inline(
11097
11113
const unsigned char *p,
@@ -11101,6 +11117,7 @@ parse_number_inline(
11101
11117
TypeNode *type,
11102
11118
PathNode *path,
11103
11119
bool strict,
11120
+ PyObject *float_hook,
11104
11121
bool from_str
11105
11122
) {
11106
11123
uint64_t mantissa = 0;
@@ -11286,6 +11303,9 @@ parse_number_inline(
11286
11303
(char *)start, p - start, true, path, NULL
11287
11304
);
11288
11305
}
11306
+ else if (MS_UNLIKELY(float_hook != NULL && type->types & MS_TYPE_ANY)) {
11307
+ return json_float_hook((char *)start, p - start, path, float_hook);
11308
+ }
11289
11309
else {
11290
11310
if (MS_UNLIKELY(exponent > 288 || exponent < -307)) {
11291
11311
/* Exponent is out of bounds */
@@ -11363,6 +11383,7 @@ maybe_parse_number(
11363
11383
type,
11364
11384
path,
11365
11385
strict,
11386
+ NULL,
11366
11387
true
11367
11388
);
11368
11389
return (*out != NULL || errmsg == NULL);
@@ -15403,6 +15424,7 @@ typedef struct JSONDecoderState {
15403
15424
/* Configuration */
15404
15425
TypeNode *type;
15405
15426
PyObject *dec_hook;
15427
+ PyObject *float_hook;
15406
15428
bool strict;
15407
15429
15408
15430
/* Temporary scratch space */
@@ -15425,10 +15447,11 @@ typedef struct JSONDecoder {
15425
15447
TypeNode *type;
15426
15448
char strict;
15427
15449
PyObject *dec_hook;
15450
+ PyObject *float_hook;
15428
15451
} JSONDecoder;
15429
15452
15430
15453
PyDoc_STRVAR(JSONDecoder__doc__,
15431
- "Decoder(type='Any', *, strict=True, dec_hook=None)\n"
15454
+ "Decoder(type='Any', *, strict=True, dec_hook=None, float_hook=None )\n"
15432
15455
"--\n"
15433
15456
"\n"
15434
15457
"A JSON decoder.\n"
@@ -15449,19 +15472,28 @@ PyDoc_STRVAR(JSONDecoder__doc__,
15449
15472
" signature ``dec_hook(type: Type, obj: Any) -> Any``, where ``type`` is the\n"
15450
15473
" expected message type, and ``obj`` is the decoded representation composed\n"
15451
15474
" of only basic JSON types. This hook should transform ``obj`` into type\n"
15452
- " ``type``, or raise a ``NotImplementedError`` if unsupported."
15475
+ " ``type``, or raise a ``NotImplementedError`` if unsupported.\n"
15476
+ "float_hook : callable, optional\n"
15477
+ " An optional callback for handling decoding untyped float literals. Should\n"
15478
+ " have the signature ``float_hook(val: str) -> Any``, where ``val`` is the\n"
15479
+ " raw string value of the JSON float. This hook is called to decode any\n"
15480
+ " \"untyped\" float value (e.g. ``typing.Any`` typed). The default is\n"
15481
+ " equivalent to ``float_hook=float``, where all untyped JSON floats are\n"
15482
+ " decoded as python floats. Specifying ``float_hook=decimal.Decimal``\n"
15483
+ " will decode all untyped JSON floats as decimals instead."
15453
15484
);
15454
15485
static int
15455
15486
JSONDecoder_init(JSONDecoder *self, PyObject *args, PyObject *kwds)
15456
15487
{
15457
- char *kwlist[] = {"type", "strict", "dec_hook", NULL};
15488
+ char *kwlist[] = {"type", "strict", "dec_hook", "float_hook", NULL};
15458
15489
MsgspecState *st = msgspec_get_global_state();
15459
15490
PyObject *type = st->typing_any;
15460
15491
PyObject *dec_hook = NULL;
15492
+ PyObject *float_hook = NULL;
15461
15493
int strict = 1;
15462
15494
15463
15495
if (!PyArg_ParseTupleAndKeywords(
15464
- args, kwds, "|O$pO ", kwlist, &type, &strict, &dec_hook)
15496
+ args, kwds, "|O$pOO ", kwlist, &type, &strict, &dec_hook, &float_hook )
15465
15497
) {
15466
15498
return -1;
15467
15499
}
@@ -15479,6 +15511,19 @@ JSONDecoder_init(JSONDecoder *self, PyObject *args, PyObject *kwds)
15479
15511
}
15480
15512
self->dec_hook = dec_hook;
15481
15513
15514
+ /* Handle float_hook */
15515
+ if (float_hook == Py_None) {
15516
+ float_hook = NULL;
15517
+ }
15518
+ if (float_hook != NULL) {
15519
+ if (!PyCallable_Check(float_hook)) {
15520
+ PyErr_SetString(PyExc_TypeError, "float_hook must be callable");
15521
+ return -1;
15522
+ }
15523
+ Py_INCREF(float_hook);
15524
+ }
15525
+ self->float_hook = float_hook;
15526
+
15482
15527
/* Handle strict */
15483
15528
self->strict = strict;
15484
15529
@@ -15498,6 +15543,7 @@ JSONDecoder_traverse(JSONDecoder *self, visitproc visit, void *arg)
15498
15543
if (out != 0) return out;
15499
15544
Py_VISIT(self->orig_type);
15500
15545
Py_VISIT(self->dec_hook);
15546
+ Py_VISIT(self->float_hook);
15501
15547
return 0;
15502
15548
}
15503
15549
@@ -15508,6 +15554,7 @@ JSONDecoder_dealloc(JSONDecoder *self)
15508
15554
TypeNode_Free(self->type);
15509
15555
Py_XDECREF(self->orig_type);
15510
15556
Py_XDECREF(self->dec_hook);
15557
+ Py_XDECREF(self->float_hook);
15511
15558
Py_TYPE(self)->tp_free((PyObject *)self);
15512
15559
}
15513
15560
@@ -17551,7 +17598,7 @@ json_maybe_decode_number(JSONDecoderState *self, TypeNode *type, PathNode *path)
17551
17598
PyObject *out = parse_number_inline(
17552
17599
self->input_pos, self->input_end,
17553
17600
&pout, &errmsg,
17554
- type, path, self->strict, false
17601
+ type, path, self->strict, self->float_hook, false
17555
17602
);
17556
17603
self->input_pos = (unsigned char *)pout;
17557
17604
@@ -18014,6 +18061,7 @@ msgspec_json_format(PyObject *self, PyObject *args, PyObject *kwargs)
18014
18061
18015
18062
/* Init decoder */
18016
18063
dec.dec_hook = NULL;
18064
+ dec.float_hook = NULL;
18017
18065
dec.type = NULL;
18018
18066
dec.scratch = NULL;
18019
18067
dec.scratch_capacity = 0;
@@ -18095,6 +18143,7 @@ JSONDecoder_decode(JSONDecoder *self, PyObject *const *args, Py_ssize_t nargs)
18095
18143
.type = self->type,
18096
18144
.strict = self->strict,
18097
18145
.dec_hook = self->dec_hook,
18146
+ .float_hook = self->float_hook,
18098
18147
.scratch = NULL,
18099
18148
.scratch_capacity = 0,
18100
18149
.scratch_len = 0
@@ -18161,6 +18210,7 @@ JSONDecoder_decode_lines(JSONDecoder *self, PyObject *const *args, Py_ssize_t na
18161
18210
.type = self->type,
18162
18211
.strict = self->strict,
18163
18212
.dec_hook = self->dec_hook,
18213
+ .float_hook = self->float_hook,
18164
18214
.scratch = NULL,
18165
18215
.scratch_capacity = 0,
18166
18216
.scratch_len = 0
@@ -18237,6 +18287,7 @@ static PyMemberDef JSONDecoder_members[] = {
18237
18287
{"type", T_OBJECT_EX, offsetof(JSONDecoder, orig_type), READONLY, "The Decoder type"},
18238
18288
{"strict", T_BOOL, offsetof(JSONDecoder, strict), READONLY, "The Decoder strict setting"},
18239
18289
{"dec_hook", T_OBJECT, offsetof(JSONDecoder, dec_hook), READONLY, "The Decoder dec_hook"},
18290
+ {"float_hook", T_OBJECT, offsetof(JSONDecoder, float_hook), READONLY, "The Decoder float_hook"},
18240
18291
{NULL},
18241
18292
};
18242
18293
@@ -18334,6 +18385,7 @@ msgspec_json_decode(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyO
18334
18385
JSONDecoderState state = {
18335
18386
.strict = strict,
18336
18387
.dec_hook = dec_hook,
18388
+ .float_hook = NULL,
18337
18389
.scratch = NULL,
18338
18390
.scratch_capacity = 0,
18339
18391
.scratch_len = 0
0 commit comments