2020-06-10 15:01:00 -03:00
/*
2021-04-22 20:53:07 -03:00
* Copyright ( c ) 2020 , Matthew Olsson < mattco @ serenityos . org >
2020-06-10 15:01:00 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-06-10 15:01:00 -03:00
*/
2020-09-28 04:17:33 -03:00
# include <AK/Function.h>
2026-01-11 17:09:41 -03:00
# include <AK/GenericLexer.h>
2020-06-10 15:01:00 -03:00
# include <AK/StringBuilder.h>
2026-01-11 17:09:41 -03:00
# include <AK/StringConversions.h>
2022-11-23 09:41:50 -03:00
# include <AK/TypeCasts.h>
2022-02-06 18:22:06 -03:00
# include <AK/Utf16View.h>
2021-06-30 21:24:04 -03:00
# include <AK/Utf8View.h>
2021-06-19 17:45:00 -03:00
# include <LibJS/Runtime/AbstractOperations.h>
2020-06-10 15:01:00 -03:00
# include <LibJS/Runtime/Array.h>
2021-01-01 13:46:39 -03:00
# include <LibJS/Runtime/BigIntObject.h>
# include <LibJS/Runtime/BooleanObject.h>
2020-06-10 15:01:00 -03:00
# include <LibJS/Runtime/Error.h>
2022-01-23 06:12:26 -03:00
# include <LibJS/Runtime/FunctionObject.h>
2020-06-10 15:01:00 -03:00
# include <LibJS/Runtime/GlobalObject.h>
# include <LibJS/Runtime/JSONObject.h>
2021-01-01 13:46:39 -03:00
# include <LibJS/Runtime/NumberObject.h>
2020-06-10 15:01:00 -03:00
# include <LibJS/Runtime/Object.h>
2025-04-23 14:43:00 -03:00
# include <LibJS/Runtime/RawJSONObject.h>
2021-01-01 13:46:39 -03:00
# include <LibJS/Runtime/StringObject.h>
2023-10-06 12:54:21 -03:00
# include <LibJS/Runtime/ValueInlines.h>
2020-06-10 15:01:00 -03:00
2026-01-11 17:09:41 -03:00
# include <simdjson.h>
2020-06-10 15:01:00 -03:00
namespace JS {
2024-11-14 12:01:23 -03:00
GC_DEFINE_ALLOCATOR ( JSONObject ) ;
2023-11-19 05:45:05 -03:00
2022-08-15 20:20:49 -03:00
JSONObject : : JSONObject ( Realm & realm )
2023-04-12 19:47:15 -03:00
: Object ( ConstructWithPrototypeTag : : Tag , realm . intrinsics ( ) . object_prototype ( ) )
2020-06-20 12:11:11 -03:00
{
}
2023-08-07 03:41:28 -03:00
void JSONObject : : initialize ( Realm & realm )
2020-06-10 15:01:00 -03:00
{
2020-10-13 19:03:58 -03:00
auto & vm = this - > vm ( ) ;
2023-08-07 03:41:28 -03:00
Base : : initialize ( realm ) ;
2020-06-10 15:01:00 -03:00
u8 attr = Attribute : : Writable | Attribute : : Configurable ;
2022-08-22 17:47:35 -03:00
define_native_function ( realm , vm . names . stringify , stringify , 3 , attr ) ;
define_native_function ( realm , vm . names . parse , parse , 2 , attr ) ;
2025-04-23 14:43:00 -03:00
define_native_function ( realm , vm . names . rawJSON , raw_json , 1 , attr ) ;
define_native_function ( realm , vm . names . isRawJSON , is_raw_json , 1 , attr ) ;
2021-06-12 20:22:35 -03:00
// 25.5.3 JSON [ @@toStringTag ], https://tc39.es/ecma262/#sec-json-@@tostringtag
2023-08-08 13:25:57 -03:00
define_direct_property ( vm . well_known_symbol_to_string_tag ( ) , PrimitiveString : : create ( vm , " JSON " _string ) , Attribute : : Configurable ) ;
2020-06-10 15:01:00 -03:00
}
2021-06-30 21:24:04 -03:00
// 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify
2025-03-16 22:45:02 -03:00
ThrowCompletionOr < Optional < String > > JSONObject : : stringify_impl ( VM & vm , Value value , Value replacer , Value space )
2020-08-25 14:48:32 -03:00
{
2022-08-21 13:41:49 -03:00
auto & realm = * vm . current_realm ( ) ;
2022-08-15 20:20:49 -03:00
2020-06-10 15:01:00 -03:00
StringifyState state ;
if ( replacer . is_object ( ) ) {
if ( replacer . as_object ( ) . is_function ( ) ) {
state . replacer_function = & replacer . as_function ( ) ;
2021-06-30 21:24:04 -03:00
} else {
2022-08-21 10:00:56 -03:00
auto is_array = TRY ( replacer . is_array ( vm ) ) ;
2021-06-30 21:24:04 -03:00
if ( is_array ) {
auto & replacer_object = replacer . as_object ( ) ;
2022-08-21 15:24:32 -03:00
auto replacer_length = TRY ( length_of_array_like ( vm , replacer_object ) ) ;
2025-08-02 20:27:29 -03:00
Vector < Utf16String > list ;
2021-06-30 21:24:04 -03:00
for ( size_t i = 0 ; i < replacer_length ; + + i ) {
2021-10-28 18:27:25 -03:00
auto replacer_value = TRY ( replacer_object . get ( i ) ) ;
2025-08-02 20:27:29 -03:00
Optional < Utf16String > item ;
2021-06-30 21:24:04 -03:00
if ( replacer_value . is_string ( ) ) {
2025-08-02 20:27:29 -03:00
item = replacer_value . as_string ( ) . utf16_string ( ) ;
2021-06-30 21:24:04 -03:00
} else if ( replacer_value . is_number ( ) ) {
2025-08-02 20:27:29 -03:00
item = MUST ( replacer_value . to_utf16_string ( vm ) ) ;
2021-06-30 21:24:04 -03:00
} else if ( replacer_value . is_object ( ) ) {
auto & value_object = replacer_value . as_object ( ) ;
2021-10-12 13:49:01 -03:00
if ( is < StringObject > ( value_object ) | | is < NumberObject > ( value_object ) )
2025-08-02 20:27:29 -03:00
item = TRY ( replacer_value . to_utf16_string ( vm ) ) ;
2021-06-30 21:24:04 -03:00
}
2023-10-10 08:30:58 -03:00
if ( item . has_value ( ) & & ! list . contains_slow ( * item ) ) {
list . append ( * item ) ;
2020-06-10 15:01:00 -03:00
}
}
2025-08-02 20:27:29 -03:00
state . property_list = move ( list ) ;
2020-06-10 15:01:00 -03:00
}
}
}
if ( space . is_object ( ) ) {
2021-06-30 21:24:04 -03:00
auto & space_object = space . as_object ( ) ;
2021-10-17 17:20:05 -03:00
if ( is < NumberObject > ( space_object ) )
2022-08-21 10:00:56 -03:00
space = TRY ( space . to_number ( vm ) ) ;
2021-10-17 17:20:05 -03:00
else if ( is < StringObject > ( space_object ) )
2022-08-21 10:00:56 -03:00
space = TRY ( space . to_primitive_string ( vm ) ) ;
2020-06-10 15:01:00 -03:00
}
if ( space . is_number ( ) ) {
2022-08-21 10:00:56 -03:00
auto space_mv = MUST ( space . to_integer_or_infinity ( vm ) ) ;
2021-06-30 21:24:04 -03:00
space_mv = min ( 10 , space_mv ) ;
2025-03-16 22:45:02 -03:00
state . gap = space_mv < 1 ? String { } : MUST ( String : : repeated ( ' ' , space_mv ) ) ;
2020-06-10 15:01:00 -03:00
} else if ( space . is_string ( ) ) {
2025-03-16 22:45:02 -03:00
auto string = space . as_string ( ) . utf8_string ( ) ;
if ( string . bytes ( ) . size ( ) < = 10 )
2020-06-10 15:01:00 -03:00
state . gap = string ;
2021-06-30 21:24:04 -03:00
else
2025-03-16 22:45:02 -03:00
state . gap = MUST ( string . substring_from_byte_offset ( 0 , 10 ) ) ;
2020-06-10 15:01:00 -03:00
} else {
2025-03-16 22:45:02 -03:00
state . gap = String { } ;
2020-06-10 15:01:00 -03:00
}
2022-12-13 17:49:50 -03:00
auto wrapper = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
2025-08-02 20:27:29 -03:00
MUST ( wrapper - > create_data_property_or_throw ( Utf16String { } , value ) ) ;
2026-01-11 20:24:48 -03:00
bool wrote_value = TRY ( serialize_json_property ( vm , state , Utf16String { } , wrapper ) ) ;
if ( ! wrote_value )
return Optional < String > { } ;
return state . builder . to_string_without_validation ( ) ;
2020-07-04 15:37:50 -03:00
}
2021-06-12 20:22:35 -03:00
// 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify
2021-10-28 18:27:25 -03:00
JS_DEFINE_NATIVE_FUNCTION ( JSONObject : : stringify )
2020-07-04 15:37:50 -03:00
{
2020-09-27 13:36:49 -03:00
if ( ! vm . argument_count ( ) )
2020-07-04 15:37:50 -03:00
return js_undefined ( ) ;
2020-09-27 13:36:49 -03:00
auto value = vm . argument ( 0 ) ;
auto replacer = vm . argument ( 1 ) ;
auto space = vm . argument ( 2 ) ;
2020-07-04 15:37:50 -03:00
2023-10-10 08:30:58 -03:00
auto maybe_string = TRY ( stringify_impl ( vm , value , replacer , space ) ) ;
if ( ! maybe_string . has_value ( ) )
2020-06-10 15:01:00 -03:00
return js_undefined ( ) ;
2020-07-04 15:37:50 -03:00
2023-10-10 08:30:58 -03:00
return PrimitiveString : : create ( vm , maybe_string . release_value ( ) ) ;
2020-06-10 15:01:00 -03:00
}
2021-06-30 21:24:04 -03:00
// 25.5.2.1 SerializeJSONProperty ( state, key, holder ), https://tc39.es/ecma262/#sec-serializejsonproperty
2025-04-23 14:43:00 -03:00
// 1.4.1 SerializeJSONProperty ( state, key, holder ), https://tc39.es/proposal-json-parse-with-source/#sec-serializejsonproperty
2026-01-11 20:24:48 -03:00
// Returns true if a value was serialized, false if the value was undefined (should be omitted).
ThrowCompletionOr < bool > JSONObject : : serialize_json_property ( VM & vm , StringifyState & state , PropertyKey const & key , Object * holder )
2020-06-10 15:01:00 -03:00
{
2026-01-11 20:24:48 -03:00
auto & builder = state . builder ;
2022-02-07 00:02:17 -03:00
// 1. Let value be ? Get(holder, key).
2021-10-28 18:27:25 -03:00
auto value = TRY ( holder - > get ( key ) ) ;
2022-02-07 00:02:17 -03:00
// 2. If Type(value) is Object or BigInt, then
2021-06-30 21:24:04 -03:00
if ( value . is_object ( ) | | value . is_bigint ( ) ) {
2022-02-07 00:02:17 -03:00
// a. Let toJSON be ? GetV(value, "toJSON").
2022-08-21 10:00:56 -03:00
auto to_json = TRY ( value . get ( vm , vm . names . toJSON ) ) ;
2022-02-07 00:02:17 -03:00
// b. If IsCallable(toJSON) is true, then
if ( to_json . is_function ( ) ) {
// i. Set value to ? Call(toJSON, value, « key »).
2022-12-06 19:17:27 -03:00
value = TRY ( call ( vm , to_json . as_function ( ) , value , PrimitiveString : : create ( vm , key . to_string ( ) ) ) ) ;
2022-02-07 00:02:17 -03:00
}
2020-06-10 15:01:00 -03:00
}
2022-02-07 00:02:17 -03:00
// 3. If state.[[ReplacerFunction]] is not undefined, then
if ( state . replacer_function ) {
// a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »).
2022-12-06 19:17:27 -03:00
value = TRY ( call ( vm , * state . replacer_function , holder , PrimitiveString : : create ( vm , key . to_string ( ) ) , value ) ) ;
2022-02-07 00:02:17 -03:00
}
2020-06-10 15:01:00 -03:00
2022-02-07 00:02:17 -03:00
// 4. If Type(value) is Object, then
2020-06-10 15:01:00 -03:00
if ( value . is_object ( ) ) {
auto & value_object = value . as_object ( ) ;
2022-02-07 00:02:17 -03:00
2025-04-23 14:43:00 -03:00
// a. If value has an [[IsRawJSON]] internal slot, then
if ( is < RawJSONObject > ( value_object ) ) {
// i. Return ! Get(value, "rawJSON").
2026-01-11 20:24:48 -03:00
builder . append ( MUST ( value_object . get ( vm . names . rawJSON ) ) . as_string ( ) . utf8_string ( ) ) ;
return true ;
2025-04-23 14:43:00 -03:00
}
// b. If value has a [[NumberData]] internal slot, then
2022-02-07 00:02:17 -03:00
if ( is < NumberObject > ( value_object ) ) {
// i. Set value to ? ToNumber(value).
2022-08-21 10:00:56 -03:00
value = TRY ( value . to_number ( vm ) ) ;
2022-02-07 00:02:17 -03:00
}
2025-04-23 14:43:00 -03:00
// c. Else if value has a [[StringData]] internal slot, then
2022-02-07 00:02:17 -03:00
else if ( is < StringObject > ( value_object ) ) {
// i. Set value to ? ToString(value).
2022-08-21 10:00:56 -03:00
value = TRY ( value . to_primitive_string ( vm ) ) ;
2022-02-07 00:02:17 -03:00
}
2025-04-23 14:43:00 -03:00
// d. Else if value has a [[BooleanData]] internal slot, then
2026-02-27 12:05:55 -03:00
else if ( auto const * boolean = as_if < BooleanObject > ( value_object ) ) {
2022-02-07 00:02:17 -03:00
// i. Set value to value.[[BooleanData]].
2026-02-27 12:05:55 -03:00
value = Value { boolean - > boolean ( ) } ;
2022-02-07 00:02:17 -03:00
}
2025-04-23 14:43:00 -03:00
// e. Else if value has a [[BigIntData]] internal slot, then
2026-02-27 12:05:55 -03:00
else if ( auto const * bigint = as_if < BigIntObject > ( value_object ) ) {
2022-02-07 00:02:17 -03:00
// i. Set value to value.[[BigIntData]].
2026-02-27 12:05:55 -03:00
value = Value { & bigint - > bigint ( ) } ;
2022-02-07 00:02:17 -03:00
}
2020-06-10 15:01:00 -03:00
}
2022-02-07 00:02:17 -03:00
// 5. If value is null, return "null".
2026-01-11 20:24:48 -03:00
if ( value . is_null ( ) ) {
builder . append ( " null " sv ) ;
return true ;
}
2022-02-07 00:02:17 -03:00
// 6. If value is true, return "true".
// 7. If value is false, return "false".
2026-01-11 20:24:48 -03:00
if ( value . is_boolean ( ) ) {
builder . append ( value . as_bool ( ) ? " true " sv : " false " sv ) ;
return true ;
}
2022-02-07 00:02:17 -03:00
// 8. If Type(value) is String, return QuoteJSONString(value).
2026-01-11 20:24:48 -03:00
if ( value . is_string ( ) ) {
quote_json_string ( builder , value . as_string ( ) . utf16_string_view ( ) ) ;
return true ;
}
2022-02-07 00:02:17 -03:00
// 9. If Type(value) is Number, then
2020-06-10 15:01:00 -03:00
if ( value . is_number ( ) ) {
2022-02-07 00:02:17 -03:00
// a. If value is finite, return ! ToString(value).
2026-01-11 20:24:48 -03:00
if ( value . is_finite_number ( ) ) {
number_to_string ( builder , value . as_double ( ) ) ;
return true ;
}
2022-02-07 00:02:17 -03:00
// b. Return "null".
2026-01-11 20:24:48 -03:00
builder . append ( " null " sv ) ;
return true ;
2020-06-10 15:01:00 -03:00
}
2022-02-07 00:02:17 -03:00
// 10. If Type(value) is BigInt, throw a TypeError exception.
2021-10-28 18:27:25 -03:00
if ( value . is_bigint ( ) )
2022-08-16 16:33:17 -03:00
return vm . throw_completion < TypeError > ( ErrorType : : JsonBigInt ) ;
2022-02-07 00:02:17 -03:00
// 11. If Type(value) is Object and IsCallable(value) is false, then
2020-06-10 15:01:00 -03:00
if ( value . is_object ( ) & & ! value . is_function ( ) ) {
2022-02-07 00:02:17 -03:00
// a. Let isArray be ? IsArray(value).
2022-08-21 10:00:56 -03:00
auto is_array = TRY ( value . is_array ( vm ) ) ;
2022-02-07 00:02:17 -03:00
// b. If isArray is true, return ? SerializeJSONArray(state, value).
2026-01-11 20:24:48 -03:00
if ( is_array ) {
TRY ( serialize_json_array ( vm , state , value . as_object ( ) ) ) ;
return true ;
}
2022-02-07 00:02:17 -03:00
// c. Return ? SerializeJSONObject(state, value).
2026-01-11 20:24:48 -03:00
TRY ( serialize_json_object ( vm , state , value . as_object ( ) ) ) ;
return true ;
2020-06-10 15:01:00 -03:00
}
2022-02-07 00:02:17 -03:00
// 12. Return undefined.
2026-01-11 20:24:48 -03:00
return false ;
}
static void write_indent ( StringBuilder & builder , StringView gap , size_t depth )
{
for ( size_t i = 0 ; i < depth ; + + i )
builder . append ( gap ) ;
2020-06-10 15:01:00 -03:00
}
2021-06-30 21:24:04 -03:00
// 25.5.2.4 SerializeJSONObject ( state, value ), https://tc39.es/ecma262/#sec-serializejsonobject
2026-01-11 20:24:48 -03:00
ThrowCompletionOr < void > JSONObject : : serialize_json_object ( VM & vm , StringifyState & state , Object & object )
2020-06-10 15:01:00 -03:00
{
2026-05-31 13:25:09 -03:00
if ( vm . did_reach_stack_space_limit ( ) )
return vm . throw_completion < InternalError > ( ErrorType : : CallStackSizeExceeded ) ;
2021-10-28 18:27:25 -03:00
if ( state . seen_objects . contains ( & object ) )
2022-08-16 16:33:17 -03:00
return vm . throw_completion < TypeError > ( ErrorType : : JsonCircular ) ;
2020-06-10 15:01:00 -03:00
state . seen_objects . set ( & object ) ;
2026-01-11 20:24:48 -03:00
+ + state . indent_depth ;
auto & builder = state . builder ;
builder . append ( ' { ' ) ;
size_t position_after_open_brace = builder . length ( ) ;
bool first = true ;
2020-06-10 15:01:00 -03:00
2022-04-01 14:58:27 -03:00
auto process_property = [ & ] ( PropertyKey const & key ) - > ThrowCompletionOr < void > {
2021-04-16 07:02:51 -03:00
if ( key . is_symbol ( ) )
2021-10-28 18:27:25 -03:00
return { } ;
2026-01-11 20:24:48 -03:00
// Mark position before writing anything for this property
size_t mark = builder . length ( ) ;
// Write separator (comma and possibly newline/indent)
if ( ! first ) {
builder . append ( ' , ' ) ;
if ( ! state . gap . is_empty ( ) ) {
builder . append ( ' \n ' ) ;
write_indent ( builder , state . gap , state . indent_depth ) ;
}
} else if ( ! state . gap . is_empty ( ) ) {
builder . append ( ' \n ' ) ;
write_indent ( builder , state . gap , state . indent_depth ) ;
}
// Write key and colon
quote_json_string ( builder , key . to_string ( ) ) ;
builder . append ( ' : ' ) ;
if ( ! state . gap . is_empty ( ) )
builder . append ( ' ' ) ;
// Serialize value
bool wrote_value = TRY ( serialize_json_property ( vm , state , key , & object ) ) ;
if ( wrote_value ) {
first = false ;
} else {
// Rollback - value was undefined, remove everything we wrote for this property
builder . trim ( builder . length ( ) - mark ) ;
2020-06-10 15:01:00 -03:00
}
2021-10-28 18:27:25 -03:00
return { } ;
2020-06-10 15:01:00 -03:00
} ;
if ( state . property_list . has_value ( ) ) {
auto property_list = state . property_list . value ( ) ;
2021-10-28 18:27:25 -03:00
for ( auto & property : property_list )
TRY ( process_property ( property ) ) ;
2020-06-10 15:01:00 -03:00
} else {
2021-10-28 18:27:25 -03:00
auto property_list = TRY ( object . enumerable_own_property_names ( PropertyKind : : Key ) ) ;
for ( auto & property : property_list )
2025-08-02 20:27:29 -03:00
TRY ( process_property ( property . as_string ( ) . utf16_string ( ) ) ) ;
2020-06-10 15:01:00 -03:00
}
2026-01-11 20:24:48 -03:00
// Close the object
- - state . indent_depth ;
if ( builder . length ( ) > position_after_open_brace & & ! state . gap . is_empty ( ) ) {
builder . append ( ' \n ' ) ;
write_indent ( builder , state . gap , state . indent_depth ) ;
2020-06-10 15:01:00 -03:00
}
2026-01-11 20:24:48 -03:00
builder . append ( ' } ' ) ;
2020-06-10 15:01:00 -03:00
state . seen_objects . remove ( & object ) ;
2026-01-11 20:24:48 -03:00
return { } ;
2020-06-10 15:01:00 -03:00
}
2021-06-30 21:24:04 -03:00
// 25.5.2.5 SerializeJSONArray ( state, value ), https://tc39.es/ecma262/#sec-serializejsonarray
2026-01-11 20:24:48 -03:00
ThrowCompletionOr < void > JSONObject : : serialize_json_array ( VM & vm , StringifyState & state , Object & object )
2020-06-10 15:01:00 -03:00
{
2026-05-31 13:25:09 -03:00
if ( vm . did_reach_stack_space_limit ( ) )
return vm . throw_completion < InternalError > ( ErrorType : : CallStackSizeExceeded ) ;
2021-10-28 18:27:25 -03:00
if ( state . seen_objects . contains ( & object ) )
2022-08-16 16:33:17 -03:00
return vm . throw_completion < TypeError > ( ErrorType : : JsonCircular ) ;
2020-06-10 15:01:00 -03:00
state . seen_objects . set ( & object ) ;
2026-01-11 20:24:48 -03:00
+ + state . indent_depth ;
2020-06-10 15:01:00 -03:00
2026-01-11 20:24:48 -03:00
auto & builder = state . builder ;
2022-08-21 15:24:32 -03:00
auto length = TRY ( length_of_array_like ( vm , object ) ) ;
2021-06-30 21:24:04 -03:00
2026-01-11 20:24:48 -03:00
builder . append ( ' [ ' ) ;
2021-06-30 21:24:04 -03:00
2020-06-10 15:01:00 -03:00
for ( size_t i = 0 ; i < length ; + + i ) {
2026-01-11 20:24:48 -03:00
// Write separator
if ( i > 0 ) {
builder . append ( ' , ' ) ;
if ( ! state . gap . is_empty ( ) ) {
builder . append ( ' \n ' ) ;
write_indent ( builder , state . gap , state . indent_depth ) ;
2020-06-10 15:01:00 -03:00
}
2026-01-11 20:24:48 -03:00
} else if ( ! state . gap . is_empty ( ) ) {
2020-06-10 15:01:00 -03:00
builder . append ( ' \n ' ) ;
2026-01-11 20:24:48 -03:00
write_indent ( builder , state . gap , state . indent_depth ) ;
2020-06-10 15:01:00 -03:00
}
2026-01-11 20:24:48 -03:00
// Serialize value (undefined becomes null for arrays)
bool wrote_value = TRY ( serialize_json_property ( vm , state , i , & object ) ) ;
if ( ! wrote_value )
builder . append ( " null " sv ) ;
2020-06-10 15:01:00 -03:00
}
2026-01-11 20:24:48 -03:00
// Close the array
- - state . indent_depth ;
if ( length > 0 & & ! state . gap . is_empty ( ) ) {
builder . append ( ' \n ' ) ;
write_indent ( builder , state . gap , state . indent_depth ) ;
}
builder . append ( ' ] ' ) ;
2020-06-10 15:01:00 -03:00
state . seen_objects . remove ( & object ) ;
2026-01-11 20:24:48 -03:00
return { } ;
2020-06-10 15:01:00 -03:00
}
2021-06-30 21:24:04 -03:00
// 25.5.2.2 QuoteJSONString ( value ), https://tc39.es/ecma262/#sec-quotejsonstring
2026-01-11 20:24:48 -03:00
void JSONObject : : quote_json_string ( StringBuilder & builder , Utf16View const & string )
2020-06-10 15:01:00 -03:00
{
2023-01-22 16:03:01 -03:00
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
2020-06-10 15:01:00 -03:00
builder . append ( ' " ' ) ;
2023-01-22 16:03:01 -03:00
// 2. For each code point C of StringToCodePoints(value), do
2025-08-02 20:27:29 -03:00
for ( auto code_point : string ) {
2026-01-11 20:24:48 -03:00
// a. If C is listed in the "Code Point" column of Table 70, then
// i. Set product to the string-concatenation of product and the escape sequence for C as specified in the "Escape Sequence" column of the corresponding row.
2021-06-30 21:24:04 -03:00
switch ( code_point ) {
2020-06-10 15:01:00 -03:00
case ' \b ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ b " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' \t ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ t " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' \n ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ n " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' \f ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ f " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' \r ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ r " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' " ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ \" " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
case ' \\ ' :
2022-07-11 14:32:29 -03:00
builder . append ( " \\ \\ " sv ) ;
2020-06-10 15:01:00 -03:00
break ;
default :
2023-01-22 16:03:01 -03:00
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a leading surrogate or trailing surrogate, then
2023-01-22 16:04:28 -03:00
if ( code_point < 0x20 | | is_unicode_surrogate ( code_point ) ) {
2023-01-22 16:03:01 -03:00
// i. Let unit be the code unit whose numeric value is that of C.
// ii. Set product to the string-concatenation of product and UnicodeEscape(unit).
2021-06-30 21:24:04 -03:00
builder . appendff ( " \\ u{:04x} " , code_point ) ;
2023-01-22 16:03:01 -03:00
}
// c. Else,
else {
// i. Set product to the string-concatenation of product and UTF16EncodeCodePoint(C).
2021-06-30 21:24:04 -03:00
builder . append_code_point ( code_point ) ;
2020-06-10 15:01:00 -03:00
}
}
}
2025-08-02 20:27:29 -03:00
2023-01-22 16:03:01 -03:00
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
2020-06-10 15:01:00 -03:00
builder . append ( ' " ' ) ;
}
2021-06-12 20:22:35 -03:00
// 25.5.1 JSON.parse ( text [ , reviver ] ), https://tc39.es/ecma262/#sec-json.parse
2021-10-28 18:27:25 -03:00
JS_DEFINE_NATIVE_FUNCTION ( JSONObject : : parse )
2020-06-10 15:01:00 -03:00
{
2022-08-22 07:48:08 -03:00
auto & realm = * vm . current_realm ( ) ;
2022-08-15 20:20:49 -03:00
2025-04-28 17:22:48 -03:00
auto text = vm . argument ( 0 ) ;
2020-09-27 13:36:49 -03:00
auto reviver = vm . argument ( 1 ) ;
2020-06-11 03:30:36 -03:00
2025-04-28 17:22:48 -03:00
// 1. Let jsonString be ? ToString(text).
auto json_string = TRY ( text . to_string ( vm ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// 2. Let parseResult be ? ParseJSON(jsonString).
// 3. Let unfiltered be parseResult.[[Value]].
// NB: We build the JSON Parse Record snapshot inline during parsing, but only
// when a reviver is present and may observe the matched source text.
JSONParseRecord root_record ;
auto unfiltered = TRY ( parse_json ( vm , json_string , reviver . is_function ( ) ? & root_record : nullptr ) ) ;
// 4. If IsCallable(reviver) is false, return unfiltered.
if ( ! reviver . is_function ( ) )
return unfiltered ;
// 5. Let root be OrdinaryObjectCreate(%Object.prototype%).
auto root = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
// 6. Let rootName be the empty String.
Utf16String root_name ;
// 7. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
MUST ( root - > create_data_property_or_throw ( root_name , unfiltered ) ) ;
// 8. Let snapshot be CreateJSONParseRecord(parseResult.[[ParseNode]], rootName, unfiltered).
// NB: Keep every value referenced by the parse record snapshot rooted: the records
// live in heap-allocated storage the GC does not scan, and the reviver may
// detach the original values from the object graph while still running.
GC : : RootVector < Value > kept_alive ;
Function < void ( JSONParseRecord const & ) > keep_alive = [ & ] ( JSONParseRecord const & record ) {
kept_alive . append ( record . value ) ;
for ( auto const & element : record . elements )
keep_alive ( element ) ;
for ( auto const & entry : record . entries )
keep_alive ( entry ) ;
} ;
keep_alive ( root_record ) ;
2025-04-28 17:22:48 -03:00
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// 9. Return ? InternalizeJSONProperty(root, rootName, reviver, snapshot).
return internalize_json_property ( vm , root , root_name , reviver . as_function ( ) , & root_record ) ;
2020-06-11 03:30:36 -03:00
}
2026-01-11 17:09:41 -03:00
// Unescape a JSON string, properly handling \uXXXX escape sequences including lone surrogates.
// simdjson validates UTF-8 strictly and rejects lone surrogates, but JSON allows them.
// Returns {} on malformed escape sequences.
static Optional < Utf16String > unescape_json_string ( StringView raw )
2025-04-28 17:22:48 -03:00
{
2026-01-11 17:09:41 -03:00
StringBuilder builder ( StringBuilder : : Mode : : UTF16 , raw . length ( ) ) ;
2025-04-28 17:22:48 -03:00
2026-01-11 17:09:41 -03:00
GenericLexer lexer { raw } ;
2025-04-28 17:22:48 -03:00
2026-01-11 17:09:41 -03:00
auto consume_hex4 = [ & ] ( ) - > Optional < u16 > {
if ( lexer . tell_remaining ( ) < 4 )
return { } ;
u16 value = 0 ;
for ( int i = 0 ; i < 4 ; + + i ) {
auto ch = lexer . consume ( ) ;
value < < = 4 ;
if ( ch > = ' 0 ' & & ch < = ' 9 ' )
value | = ch - ' 0 ' ;
else if ( ch > = ' a ' & & ch < = ' f ' )
value | = ch - ' a ' + 10 ;
else if ( ch > = ' A ' & & ch < = ' F ' )
value | = ch - ' A ' + 10 ;
else
return { } ;
}
return value ;
} ;
2025-04-28 17:22:48 -03:00
2026-01-11 17:09:41 -03:00
while ( ! lexer . is_eof ( ) ) {
if ( lexer . consume_specific ( ' \\ ' ) ) {
if ( lexer . is_eof ( ) )
return { } ;
auto escaped = lexer . consume ( ) ;
switch ( escaped ) {
case ' " ' :
builder . append_code_unit ( ' " ' ) ;
break ;
case ' \\ ' :
builder . append_code_unit ( ' \\ ' ) ;
break ;
case ' / ' :
builder . append_code_unit ( ' / ' ) ;
break ;
case ' b ' :
builder . append_code_unit ( ' \b ' ) ;
break ;
case ' f ' :
builder . append_code_unit ( ' \f ' ) ;
break ;
case ' n ' :
builder . append_code_unit ( ' \n ' ) ;
break ;
case ' r ' :
builder . append_code_unit ( ' \r ' ) ;
break ;
case ' t ' :
builder . append_code_unit ( ' \t ' ) ;
break ;
case ' u ' : {
auto code_unit = consume_hex4 ( ) ;
if ( ! code_unit . has_value ( ) )
return { } ;
builder . append_code_unit ( * code_unit ) ;
break ;
}
default :
return { } ;
}
} else {
// Non-escaped character - copy UTF-8 code point to UTF-16
auto ch = lexer . consume ( ) ;
if ( ( ch & 0x80 ) = = 0 ) {
// ASCII
builder . append_code_unit ( ch ) ;
} else if ( ( ch & 0xE0 ) = = 0xC0 ) {
// 2-byte UTF-8
if ( lexer . is_eof ( ) )
return { } ;
auto ch2 = lexer . consume ( ) ;
u32 code_point = ( ( ch & 0x1F ) < < 6 ) | ( ch2 & 0x3F ) ;
builder . append_code_unit ( code_point ) ;
} else if ( ( ch & 0xF0 ) = = 0xE0 ) {
// 3-byte UTF-8
if ( lexer . tell_remaining ( ) < 2 )
return { } ;
auto ch2 = lexer . consume ( ) ;
auto ch3 = lexer . consume ( ) ;
u32 code_point = ( ( ch & 0x0F ) < < 12 ) | ( ( ch2 & 0x3F ) < < 6 ) | ( ch3 & 0x3F ) ;
builder . append_code_unit ( code_point ) ;
} else if ( ( ch & 0xF8 ) = = 0xF0 ) {
// 4-byte UTF-8 (needs surrogate pair)
if ( lexer . tell_remaining ( ) < 3 )
return { } ;
auto ch2 = lexer . consume ( ) ;
auto ch3 = lexer . consume ( ) ;
auto ch4 = lexer . consume ( ) ;
u32 code_point = ( ( ch & 0x07 ) < < 18 ) | ( ( ch2 & 0x3F ) < < 12 ) | ( ( ch3 & 0x3F ) < < 6 ) | ( ch4 & 0x3F ) ;
builder . append_code_point ( code_point ) ;
} else {
return { } ;
}
}
}
2025-04-28 17:22:48 -03:00
2026-01-11 17:09:41 -03:00
return builder . to_utf16_string ( ) ;
2025-04-28 17:22:48 -03:00
}
2026-01-11 17:09:41 -03:00
template < typename T >
static ALWAYS_INLINE ThrowCompletionOr < void > ensure_simdjson_fully_parsed ( VM & vm , T & value )
2020-06-11 03:30:36 -03:00
{
2026-01-11 17:09:41 -03:00
if constexpr ( IsSame < T , simdjson : : ondemand : : document > ) {
if ( ! value . at_end ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
}
return { } ;
2020-06-11 03:30:36 -03:00
}
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
static ThrowCompletionOr < Value > parse_simdjson_value ( VM & , simdjson : : ondemand : : value , JSONParseRecord * record = nullptr ) ;
// The source text matched by a primitive parse node, used by JSON.parse revivers.
static String json_token_source ( std : : string_view raw )
{
StringView source { raw . data ( ) , raw . size ( ) } ;
return MUST ( String : : from_utf8 ( source . trim_whitespace ( ) ) ) ;
}
2026-01-11 17:09:41 -03:00
template < typename T >
static ThrowCompletionOr < Value > parse_simdjson_number ( VM & vm , T & value , StringView raw_sv )
2020-06-11 03:30:36 -03:00
{
2026-01-11 17:09:41 -03:00
// Validate JSON number format (simdjson is more lenient than spec)
// - No leading zeros (except "0" or "0.xxx")
// - No trailing decimal point (e.g., "1." is invalid)
size_t i = 0 ;
if ( i < raw_sv . length ( ) & & raw_sv [ i ] = = ' - ' )
+ + i ;
if ( i < raw_sv . length ( ) & & raw_sv [ i ] = = ' 0 ' & & i + 1 < raw_sv . length ( ) & & is_ascii_digit ( raw_sv [ i + 1 ] ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ; // Leading zero
while ( i < raw_sv . length ( ) & & is_ascii_digit ( raw_sv [ i ] ) )
+ + i ;
if ( i < raw_sv . length ( ) & & raw_sv [ i ] = = ' . ' ) {
+ + i ;
if ( i > = raw_sv . length ( ) | | ! is_ascii_digit ( raw_sv [ i ] ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ; // Trailing decimal
}
double double_value ;
auto error = value . get_double ( ) . get ( double_value ) ;
if ( ! error ) {
TRY ( ensure_simdjson_fully_parsed ( vm , value ) ) ;
return Value ( double_value ) ;
}
// Handle overflow to infinity (e.g., 1e309)
// simdjson returns NUMBER_ERROR for numbers that overflow double
// Use parse_first_number as fallback - it handles overflow correctly
if ( error = = simdjson : : NUMBER_ERROR ) {
auto result = parse_first_number < double > ( raw_sv , TrimWhitespace : : No ) ;
if ( result . has_value ( ) & & result - > characters_parsed = = raw_sv . length ( ) )
return Value ( result - > value ) ;
}
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2020-06-11 03:30:36 -03:00
}
2026-01-11 17:09:41 -03:00
template < typename T >
static ThrowCompletionOr < Value > parse_simdjson_string ( VM & vm , T & value )
{
// Use get_raw_json_string() to get the raw JSON string content (without quotes, with escapes),
// then unescape ourselves to properly handle lone surrogates like \uD800 which simdjson rejects.
simdjson : : ondemand : : raw_json_string raw_string ;
if ( value . get_raw_json_string ( ) . get ( raw_string ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
char const * raw = raw_string . raw ( ) ;
// Find the length by looking for the closing quote (simdjson validated the structure)
size_t length = 0 ;
while ( raw [ length ] ! = ' " ' ) {
if ( raw [ length ] = = ' \\ ' )
+ + length ; // Skip escaped character
+ + length ;
}
auto unescaped = unescape_json_string ( { raw , length } ) ;
if ( ! unescaped . has_value ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
return PrimitiveString : : create ( vm , unescaped . release_value ( ) ) ;
}
template < typename T >
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
static ThrowCompletionOr < Value > parse_simdjson_array ( VM & vm , T & value , JSONParseRecord * record = nullptr )
2020-06-11 03:30:36 -03:00
{
2022-08-21 13:41:49 -03:00
auto & realm = * vm . current_realm ( ) ;
2026-01-11 17:09:41 -03:00
simdjson : : ondemand : : array simdjson_array ;
if ( value . get_array ( ) . get ( simdjson_array ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2022-12-13 17:49:49 -03:00
auto array = MUST ( Array : : create ( realm , 0 ) ) ;
2020-06-11 03:30:36 -03:00
size_t index = 0 ;
2026-01-11 17:09:41 -03:00
for ( auto element : simdjson_array ) {
simdjson : : ondemand : : value element_value ;
if ( element . get ( element_value ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
JSONParseRecord element_record ;
auto parsed = TRY ( parse_simdjson_value ( vm , element_value , record ? & element_record : nullptr ) ) ;
2026-01-11 17:09:41 -03:00
array - > define_direct_property ( index + + , parsed , default_attributes ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record )
record - > elements . append ( move ( element_record ) ) ;
2026-01-11 17:09:41 -03:00
}
TRY ( ensure_simdjson_fully_parsed ( vm , value ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record )
record - > value = array ;
2020-06-11 03:30:36 -03:00
return array ;
}
2026-01-11 17:09:41 -03:00
template < typename T >
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
static ThrowCompletionOr < Value > parse_simdjson_object ( VM & vm , T & value , JSONParseRecord * record = nullptr )
2026-01-11 17:09:41 -03:00
{
auto & realm = * vm . current_realm ( ) ;
simdjson : : ondemand : : object simdjson_object ;
if ( value . get_object ( ) . get ( simdjson_object ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
auto object = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
for ( auto field : simdjson_object ) {
// Use escaped_key() to get the raw JSON key (with escapes), then unescape ourselves
std : : string_view raw_key ;
if ( field . escaped_key ( ) . get ( raw_key ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
auto unescaped_key = unescape_json_string ( { raw_key . data ( ) , raw_key . size ( ) } ) ;
if ( ! unescaped_key . has_value ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
simdjson : : ondemand : : value field_value ;
if ( field . value ( ) . get ( field_value ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto key = unescaped_key . release_value ( ) ;
JSONParseRecord entry_record ;
auto parsed = TRY ( parse_simdjson_value ( vm , field_value , record ? & entry_record : nullptr ) ) ;
object - > define_direct_property ( key , parsed , default_attributes ) ;
if ( record ) {
entry_record . key = key ;
// Duplicate keys keep the last value, matching object property semantics.
if ( auto existing = record - > entries . find_if ( [ & ] ( auto & entry ) { return entry . key = = key ; } ) ; existing ! = record - > entries . end ( ) )
* existing = move ( entry_record ) ;
else
record - > entries . append ( move ( entry_record ) ) ;
}
2026-01-11 17:09:41 -03:00
}
TRY ( ensure_simdjson_fully_parsed ( vm , value ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record )
record - > value = object ;
2026-01-11 17:09:41 -03:00
return object ;
}
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
static ThrowCompletionOr < Value > parse_simdjson_value ( VM & vm , simdjson : : ondemand : : value value , JSONParseRecord * record )
2026-01-11 17:09:41 -03:00
{
simdjson : : ondemand : : json_type type ;
if ( value . type ( ) . get ( type ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// NB: raw_json_token() must be captured before the value is consumed by the get_* calls below.
2026-01-11 17:09:41 -03:00
switch ( type ) {
case simdjson : : ondemand : : json_type : : null :
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record ) {
record - > value = js_null ( ) ;
record - > source = json_token_source ( value . raw_json_token ( ) ) ;
}
2026-01-11 17:09:41 -03:00
return js_null ( ) ;
case simdjson : : ondemand : : json_type : : boolean : {
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto token = value . raw_json_token ( ) ;
2026-01-11 17:09:41 -03:00
bool boolean_value ;
if ( value . get_bool ( ) . get ( boolean_value ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record ) {
record - > value = Value ( boolean_value ) ;
record - > source = json_token_source ( token ) ;
}
2026-01-11 17:09:41 -03:00
return Value ( boolean_value ) ;
}
case simdjson : : ondemand : : json_type : : number : {
auto raw = value . raw_json_token ( ) ;
StringView raw_sv { raw . data ( ) , raw . size ( ) } ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto parsed = TRY ( parse_simdjson_number ( vm , value , raw_sv ) ) ;
if ( record ) {
record - > value = parsed ;
record - > source = json_token_source ( raw ) ;
}
return parsed ;
}
case simdjson : : ondemand : : json_type : : string : {
auto token = value . raw_json_token ( ) ;
auto parsed = TRY ( parse_simdjson_string ( vm , value ) ) ;
if ( record ) {
record - > value = parsed ;
record - > source = json_token_source ( token ) ;
}
return parsed ;
2026-01-11 17:09:41 -03:00
}
case simdjson : : ondemand : : json_type : : array :
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
return parse_simdjson_array ( vm , value , record ) ;
2026-01-11 17:09:41 -03:00
case simdjson : : ondemand : : json_type : : object :
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
return parse_simdjson_object ( vm , value , record ) ;
2026-01-15 21:48:48 -03:00
case simdjson : : ondemand : : json_type : : unknown :
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2026-01-11 17:09:41 -03:00
}
2026-01-15 21:48:48 -03:00
2026-01-11 17:09:41 -03:00
VERIFY_NOT_REACHED ( ) ;
}
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
static ThrowCompletionOr < Value > parse_simdjson_document ( VM & vm , simdjson : : ondemand : : document & document , JSONParseRecord * record = nullptr )
2026-01-11 17:09:41 -03:00
{
simdjson : : ondemand : : json_type type ;
if ( document . type ( ) . get ( type ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// NB: raw_json_token() must be captured before the value is consumed by the get_* calls below.
std : : string_view raw_token ;
if ( ( type = = simdjson : : ondemand : : json_type : : boolean | | type = = simdjson : : ondemand : : json_type : : number )
& & document . raw_json_token ( ) . get ( raw_token ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2026-01-11 17:09:41 -03:00
switch ( type ) {
case simdjson : : ondemand : : json_type : : null : {
if ( document . is_null ( ) . error ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
if ( ! document . at_end ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record ) {
std : : string_view null_token ;
if ( document . raw_json_token ( ) . get ( null_token ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
record - > value = js_null ( ) ;
record - > source = json_token_source ( null_token ) ;
}
2026-01-11 17:09:41 -03:00
return js_null ( ) ;
}
case simdjson : : ondemand : : json_type : : boolean : {
bool boolean_value ;
if ( document . get_bool ( ) . get ( boolean_value ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
if ( ! document . at_end ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
if ( record ) {
record - > value = Value ( boolean_value ) ;
record - > source = json_token_source ( raw_token ) ;
}
2026-01-11 17:09:41 -03:00
return Value ( boolean_value ) ;
}
case simdjson : : ondemand : : json_type : : number : {
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
StringView raw_sv { raw_token . data ( ) , raw_token . size ( ) } ;
2026-01-11 17:09:41 -03:00
auto trimmed = raw_sv . trim_whitespace ( ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto parsed = TRY ( parse_simdjson_number ( vm , document , trimmed ) ) ;
if ( record ) {
record - > value = parsed ;
record - > source = json_token_source ( raw_token ) ;
}
return parsed ;
}
case simdjson : : ondemand : : json_type : : string : {
std : : string_view string_token ;
if ( record & & document . raw_json_token ( ) . get ( string_token ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
auto parsed = TRY ( parse_simdjson_string ( vm , document ) ) ;
if ( record ) {
record - > value = parsed ;
record - > source = json_token_source ( string_token ) ;
}
return parsed ;
2026-01-11 17:09:41 -03:00
}
case simdjson : : ondemand : : json_type : : array :
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
return parse_simdjson_array ( vm , document , record ) ;
2026-01-11 17:09:41 -03:00
case simdjson : : ondemand : : json_type : : object :
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
return parse_simdjson_object ( vm , document , record ) ;
2026-01-15 21:48:48 -03:00
case simdjson : : ondemand : : json_type : : unknown :
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2026-01-11 17:09:41 -03:00
}
2026-01-15 21:48:48 -03:00
2026-01-11 17:09:41 -03:00
VERIFY_NOT_REACHED ( ) ;
}
// 25.5.1.1 ParseJSON ( text ), https://tc39.es/ecma262/#sec-ParseJSON
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
ThrowCompletionOr < Value > JSONObject : : parse_json ( VM & vm , StringView text , JSONParseRecord * root_record )
2026-01-11 17:09:41 -03:00
{
// 1. If StringToCodePoints(text) is not a valid JSON text as specified in ECMA-404, throw a SyntaxError exception.
// NB: Per ECMA-404, the BOM is not valid JSON whitespace. simdjson silently skips it, so we must reject it explicitly.
if ( text . length ( ) > = 3
& & static_cast < u8 > ( text [ 0 ] ) = = 0xEF
& & static_cast < u8 > ( text [ 1 ] ) = = 0xBB
& & static_cast < u8 > ( text [ 2 ] ) = = 0xBF ) {
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
}
simdjson : : ondemand : : parser parser ;
simdjson : : padded_string padded ( text . characters_without_null_termination ( ) , text . length ( ) ) ;
simdjson : : ondemand : : document document ;
if ( parser . iterate ( padded ) . get ( document ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
// 2. Let scriptString be the string-concatenation of "(", text, and ");".
// 3. Let script be ParseText(scriptString, Script).
// 4. NOTE: The early error rules defined in 13.2.5.1 have special handling for the above invocation of ParseText.
// 5. Assert: script is a Parse Node.
// 6. Let result be ! Evaluation of script.
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto result = TRY ( parse_simdjson_document ( vm , document , root_record ) ) ;
2026-01-11 17:09:41 -03:00
// 7. NOTE: The PropertyDefinitionEvaluation semantics defined in 13.2.5.5 have special handling for the above evaluation.
// 8. Assert: result is either a String, a Number, a Boolean, an Object that is defined by either an ArrayLiteral or an ObjectLiteral, or null.
// 9. Return result.
return result ;
}
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// 25.5.1.1 InternalizeJSONProperty ( holder, name, reviver, parseRecord ), https://tc39.es/ecma262/#sec-internalizejsonproperty
ThrowCompletionOr < Value > JSONObject : : internalize_json_property ( VM & vm , Object * holder , PropertyKey const & name , FunctionObject & reviver , JSONParseRecord const * parse_record )
2020-06-11 03:30:36 -03:00
{
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto & realm = * vm . current_realm ( ) ;
// 1. Let value be ? Get(holder, name).
2021-10-28 18:27:25 -03:00
auto value = TRY ( holder - > get ( name ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// 2. Let context be OrdinaryObjectCreate(%Object.prototype%).
auto context = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
// 3. If parseRecord is a JSON Parse Record and SameValue(parseRecord.[[Value]], value) is true, then
// NB: An empty List of records is represented as a null pointer here.
Vector < JSONParseRecord > const * element_records = nullptr ;
Vector < JSONParseRecord > const * entry_records = nullptr ;
if ( parse_record & & same_value ( parse_record - > value , value ) ) {
// a. If value is not an Object, then
if ( ! value . is_object ( ) ) {
// i. Let parseNode be parseRecord.[[ParseNode]].
// ii. Assert: parseNode is neither an ArrayLiteral Parse Node nor an ObjectLiteral Parse Node.
// iii. Let sourceText be the source text matched by parseNode.
// iv. Perform ! CreateDataPropertyOrThrow(context, "source", CodePointsToString(sourceText)).
// NB: We captured sourceText while parsing rather than from a retained parse node.
VERIFY ( parse_record - > source . has_value ( ) ) ;
MUST ( context - > create_data_property_or_throw ( vm . names . source , PrimitiveString : : create ( vm , * parse_record - > source ) ) ) ;
}
// b. Let elementRecords be parseRecord.[[Elements]].
element_records = & parse_record - > elements ;
// c. Let entryRecords be parseRecord.[[Entries]].
entry_records = & parse_record - > entries ;
}
// 4. Else,
// a. Let elementRecords be a new empty List.
// b. Let entryRecords be a new empty List.
// 5. If value is an Object, then
2020-06-11 03:30:36 -03:00
if ( value . is_object ( ) ) {
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// a. Let isArray be ? IsArray(value).
2022-08-21 10:00:56 -03:00
auto is_array = TRY ( value . is_array ( vm ) ) ;
2020-06-11 03:30:36 -03:00
2021-06-30 21:54:24 -03:00
auto & value_object = value . as_object ( ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
auto process_property = [ & ] ( PropertyKey const & key , JSONParseRecord const * child_record ) - > ThrowCompletionOr < void > {
// i/ii/iii. Let newElement be ? InternalizeJSONProperty(value, propertyKey, reviver, elementRecord/entryRecord).
auto new_element = TRY ( internalize_json_property ( vm , & value_object , key , reviver , child_record ) ) ;
// If newElement is undefined, perform ? value.[[Delete]](propertyKey).
if ( new_element . is_undefined ( ) )
2021-09-29 14:45:33 -03:00
TRY ( value_object . internal_delete ( key ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// Else, perform ? CreateDataProperty(value, propertyKey, newElement).
2021-10-02 20:53:06 -03:00
else
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
TRY ( value_object . create_data_property ( key , new_element ) ) ;
2021-09-29 14:45:33 -03:00
return { } ;
2020-06-11 03:30:36 -03:00
} ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// b. If isArray is true, then
2021-06-30 21:54:24 -03:00
if ( is_array ) {
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// i. Let elementRecordsLength be the number of elements in elementRecords.
auto element_records_length = element_records ? element_records - > size ( ) : 0 ;
// ii. Let length be ? LengthOfArrayLike(value).
2022-08-21 15:24:32 -03:00
auto length = TRY ( length_of_array_like ( vm , value_object ) ) ;
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// iii-iv. Repeat, while index < length,
for ( size_t index = 0 ; index < length ; + + index ) {
// 2. If index < elementRecordsLength, let elementRecord be elementRecords[index]; else let elementRecord be empty.
auto const * element_record = index < element_records_length ? & element_records - > at ( index ) : nullptr ;
TRY ( process_property ( index , element_record ) ) ;
}
}
// c. Else,
else {
// i. Let keys be ? EnumerableOwnProperties(value, key).
auto keys = TRY ( value_object . enumerable_own_property_names ( Object : : PropertyKind : : Key ) ) ;
// ii. For each String propertyKey of keys, do
for ( auto & property_key : keys ) {
auto key = property_key . as_string ( ) . utf16_string ( ) ;
// 1. If there exists an element entry of entryRecords such that entry.[[Key]] is propertyKey,
// let entryRecord be entry; else let entryRecord be empty.
JSONParseRecord const * entry_record = nullptr ;
if ( entry_records ) {
if ( auto entry = entry_records - > find_if ( [ & ] ( auto & entry ) { return entry . key = = key ; } ) ; entry ! = entry_records - > end ( ) )
entry_record = & * entry ;
}
TRY ( process_property ( key , entry_record ) ) ;
}
2020-06-11 03:30:36 -03:00
}
}
2020-08-25 14:48:32 -03:00
LibJS: Support source text access in JSON.parse revivers
The JSON.parse-with-source proposal (now part of ES2026) gives a
reviver a third "context" argument. For a primitive value that was
not modified by an earlier reviver call, the context has a "source"
property holding the matched JSON source text; for objects, arrays,
and forward-modified values it is an empty object.
We already had JSON.rawJSON and JSON.isRawJSON, but the reviver only
received two arguments. Implement the missing half by building a JSON
Parse Record snapshot while parsing: each primitive records the
trimmed raw token from simdjson, and arrays and objects record their
child records keyed by index and property name. InternalizeJSONProperty
threads the matching record down the tree, creates the context object,
and only attaches "source" when the record's stored value still equals
the live value (SameValue), which suppresses source for values a
reviver replaced or appended.
The record values live in heap storage the GC does not scan, and a
reviver can detach the originals from the object graph mid-walk, so
the snapshot's values are kept rooted for the duration of the walk.
Closes the six json-parse-with-source test262 failures and adds
test-js coverage for primitive source text and forward modification.
2026-06-17 07:33:22 -03:00
// 6. Return ? Call(reviver, holder, « name, value, context »).
return TRY ( call ( vm , reviver , holder , PrimitiveString : : create ( vm , name . to_string ( ) ) , value , context ) ) ;
2020-06-10 15:01:00 -03:00
}
2025-04-23 14:43:00 -03:00
// 1.3 JSON.rawJSON ( text ), https://tc39.es/proposal-json-parse-with-source/#sec-json.rawjson
JS_DEFINE_NATIVE_FUNCTION ( JSONObject : : raw_json )
{
auto & realm = * vm . current_realm ( ) ;
// 1. Let jsonString be ? ToString(text).
auto json_string = TRY ( vm . argument ( 0 ) . to_string ( vm ) ) ;
// 2. Throw a SyntaxError exception if jsonString is the empty String, or if either the first or last code unit of
// jsonString is any of 0x0009 (CHARACTER TABULATION), 0x000A (LINE FEED), 0x000D (CARRIAGE RETURN), or
// 0x0020 (SPACE).
auto bytes = json_string . bytes_as_string_view ( ) ;
if ( bytes . is_empty ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
static constexpr AK : : Array invalid_code_points { 0x09 , 0x0A , 0x0D , 0x20 } ;
auto first_char = bytes [ 0 ] ;
auto last_char = bytes [ bytes . length ( ) - 1 ] ;
if ( invalid_code_points . contains_slow ( first_char ) | | invalid_code_points . contains_slow ( last_char ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
// 3. Parse StringToCodePoints(jsonString) as a JSON text as specified in ECMA-404. Throw a SyntaxError exception
// if it is not a valid JSON text as defined in that specification, or if its outermost value is an object or
// array as defined in that specification.
2026-01-11 17:09:41 -03:00
simdjson : : ondemand : : parser parser ;
simdjson : : padded_string padded ( json_string . bytes_as_string_view ( ) . characters_without_null_termination ( ) , json_string . bytes_as_string_view ( ) . length ( ) ) ;
simdjson : : ondemand : : document doc ;
if ( parser . iterate ( padded ) . get ( doc ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
simdjson : : ondemand : : json_type type ;
2026-01-15 21:48:48 -03:00
if ( doc . type ( ) . get ( type ) | | type = = simdjson : : ondemand : : json_type : : unknown )
2025-04-23 14:43:00 -03:00
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2026-01-11 17:09:41 -03:00
if ( type = = simdjson : : ondemand : : json_type : : object | | type = = simdjson : : ondemand : : json_type : : array )
2025-04-23 14:43:00 -03:00
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonRawJSONNonPrimitive ) ;
2026-01-11 17:09:41 -03:00
// Consume the value to advance past it, then check for trailing content
switch ( type ) {
case simdjson : : ondemand : : json_type : : null :
( void ) doc . is_null ( ) ;
break ;
case simdjson : : ondemand : : json_type : : boolean :
( void ) doc . get_bool ( ) ;
break ;
case simdjson : : ondemand : : json_type : : number :
( void ) doc . get_double ( ) ;
break ;
case simdjson : : ondemand : : json_type : : string :
( void ) doc . get_string ( ) ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
if ( ! doc . at_end ( ) )
return vm . throw_completion < SyntaxError > ( ErrorType : : JsonMalformed ) ;
2025-04-23 14:43:00 -03:00
// 4. Let internalSlotsList be « [[IsRawJSON]] ».
// 5. Let obj be OrdinaryObjectCreate(null, internalSlotsList).
auto object = RawJSONObject : : create ( realm , nullptr ) ;
// 6. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString).
MUST ( object - > create_data_property_or_throw ( vm . names . rawJSON , PrimitiveString : : create ( vm , json_string ) ) ) ;
// 7. Perform ! SetIntegrityLevel(obj, frozen).
MUST ( object - > set_integrity_level ( Object : : IntegrityLevel : : Frozen ) ) ;
// 8. Return obj.
return object ;
}
// 1.1 JSON.isRawJSON ( O ), https://tc39.es/proposal-json-parse-with-source/#sec-json.israwjson
JS_DEFINE_NATIVE_FUNCTION ( JSONObject : : is_raw_json )
{
// 1. If Type(O) is Object and O has an [[IsRawJSON]] internal slot, return true.
// 2. Return false.
2026-02-27 12:05:55 -03:00
return vm . argument ( 0 ) . is < RawJSONObject > ( ) ;
2025-04-23 14:43:00 -03:00
}
2020-06-10 15:01:00 -03:00
}