Skip to content

Commit 203fe2d

Browse files
committed
Improve JSON.stringify
- changed error messages - clarify `toJSON` method usage - simplify boxed objects handling - for ECMA conformity, BigInt objects need a toJSON method in the prototype chain including boxed objects
1 parent ce6b6dc commit 203fe2d

File tree

1 file changed

+43
-39
lines changed

1 file changed

+43
-39
lines changed

quickjs.c

+43-39
Original file line numberDiff line numberDiff line change
@@ -45088,7 +45088,7 @@ static JSValue json_parse_value(JSParseState *s)
4508845088
default:
4508945089
def_token:
4509045090
if (s->token.val == TOK_EOF) {
45091-
js_parse_error(s, "unexpected end of input");
45091+
js_parse_error(s, "Unexpected end of JSON input");
4509245092
} else {
4509345093
js_parse_error(s, "unexpected token: '%.*s'",
4509445094
(int)(s->buf_ptr - s->token.ptr), s->token.ptr);
@@ -45255,22 +45255,27 @@ static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc,
4525545255
JSValue v;
4525645256
JSValueConst args[2];
4525745257

45258-
if (JS_IsObject(val) ||
45259-
JS_IsBigInt(ctx, val) /* XXX: probably useless */
45258+
/* check for object.toJSON method */
45259+
/* ECMA specifies this is done only for Object and BigInt */
45260+
/* we do it for BigFloat and BigDecimal as an extension */
45261+
if (JS_IsObject(val) || JS_IsBigInt(ctx, val)
45262+
#ifdef CONFIG_BIGNUM
45263+
|| JS_IsBigFloat(val) || JS_IsBigDecimal(val)
45264+
#endif
4526045265
) {
45261-
JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON);
45262-
if (JS_IsException(f))
45266+
JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON);
45267+
if (JS_IsException(f))
45268+
goto exception;
45269+
if (JS_IsFunction(ctx, f)) {
45270+
v = JS_CallFree(ctx, f, val, 1, &key);
45271+
JS_FreeValue(ctx, val);
45272+
val = v;
45273+
if (JS_IsException(val))
4526345274
goto exception;
45264-
if (JS_IsFunction(ctx, f)) {
45265-
v = JS_CallFree(ctx, f, val, 1, &key);
45266-
JS_FreeValue(ctx, val);
45267-
val = v;
45268-
if (JS_IsException(val))
45269-
goto exception;
45270-
} else {
45271-
JS_FreeValue(ctx, f);
45272-
}
45275+
} else {
45276+
JS_FreeValue(ctx, f);
4527345277
}
45278+
}
4527445279

4527545280
if (!JS_IsUndefined(jsc->replacer_func)) {
4527645281
args[0] = key;
@@ -45289,12 +45294,13 @@ static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc,
4528945294
case JS_TAG_STRING:
4529045295
case JS_TAG_INT:
4529145296
case JS_TAG_FLOAT64:
45292-
#ifdef CONFIG_BIGNUM
45293-
case JS_TAG_BIG_FLOAT:
45294-
#endif
4529545297
case JS_TAG_BOOL:
4529645298
case JS_TAG_NULL:
4529745299
case JS_TAG_BIG_INT:
45300+
#ifdef CONFIG_BIGNUM
45301+
case JS_TAG_BIG_FLOAT:
45302+
case JS_TAG_BIG_DECIMAL:
45303+
#endif
4529845304
case JS_TAG_EXCEPTION:
4529945305
return val;
4530045306
default:
@@ -45324,36 +45330,29 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
4532445330
tab = JS_UNDEFINED;
4532545331
prop = JS_UNDEFINED;
4532645332

45327-
switch (JS_VALUE_GET_NORM_TAG(val)) {
45328-
case JS_TAG_OBJECT:
45333+
if (JS_IsObject(val)) {
4532945334
p = JS_VALUE_GET_OBJ(val);
4533045335
cl = p->class_id;
4533145336
if (cl == JS_CLASS_STRING) {
4533245337
val = JS_ToStringFree(ctx, val);
4533345338
if (JS_IsException(val))
4533445339
goto exception;
45335-
val = JS_ToQuotedStringFree(ctx, val);
45336-
if (JS_IsException(val))
45337-
goto exception;
45338-
return string_buffer_concat_value_free(jsc->b, val);
45340+
goto concat_primitive;
4533945341
} else if (cl == JS_CLASS_NUMBER) {
4534045342
val = JS_ToNumberFree(ctx, val);
4534145343
if (JS_IsException(val))
4534245344
goto exception;
45343-
return string_buffer_concat_value_free(jsc->b, val);
45344-
} else if (cl == JS_CLASS_BOOLEAN) {
45345-
ret = string_buffer_concat_value(jsc->b, p->u.object_data);
45346-
JS_FreeValue(ctx, val);
45347-
return ret;
45348-
} else
45345+
goto concat_primitive;
45346+
} else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT
4534945347
#ifdef CONFIG_BIGNUM
45350-
if (cl == JS_CLASS_BIG_FLOAT) {
45351-
return string_buffer_concat_value_free(jsc->b, val);
45352-
} else
45348+
|| cl == JS_CLASS_BIG_FLOAT
45349+
|| cl == JS_CLASS_BIG_DECIMAL
4535345350
#endif
45354-
if (cl == JS_CLASS_BIG_INT) {
45355-
JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify");
45356-
goto exception;
45351+
)
45352+
{
45353+
/* This will thow the same error as for the primitive object */
45354+
set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data));
45355+
goto concat_primitive;
4535745356
}
4535845357
v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val);
4535945358
if (JS_IsException(v))
@@ -45466,6 +45465,9 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
4546645465
JS_FreeValue(ctx, indent1);
4546745466
JS_FreeValue(ctx, prop);
4546845467
return 0;
45468+
}
45469+
concat_primitive:
45470+
switch (JS_VALUE_GET_NORM_TAG(val)) {
4546945471
case JS_TAG_STRING:
4547045472
val = JS_ToQuotedStringFree(ctx, val);
4547145473
if (JS_IsException(val))
@@ -45477,15 +45479,17 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
4547745479
}
4547845480
goto concat_value;
4547945481
case JS_TAG_INT:
45480-
#ifdef CONFIG_BIGNUM
45481-
case JS_TAG_BIG_FLOAT:
45482-
#endif
4548345482
case JS_TAG_BOOL:
4548445483
case JS_TAG_NULL:
4548545484
concat_value:
4548645485
return string_buffer_concat_value_free(jsc->b, val);
4548745486
case JS_TAG_BIG_INT:
45488-
JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify");
45487+
#ifdef CONFIG_BIGNUM
45488+
case JS_TAG_BIG_FLOAT:
45489+
case JS_TAG_BIG_DECIMAL:
45490+
#endif
45491+
/* reject big numbers: use toJSON method to override */
45492+
JS_ThrowTypeError(ctx, "Do not know how to serialize a BigInt");
4548945493
goto exception;
4549045494
default:
4549145495
JS_FreeValue(ctx, val);

0 commit comments

Comments
 (0)