@ -13,17 +13,20 @@
//! [json]: https://www.json.org/json-en.html
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
use std ::collections ::HashSet ;
use crate ::{
builtins ::BuiltIn ,
object ::Object ,
object ::ObjectInitializer ,
property ::{ Attribute , PropertyDescriptor , PropertyKey } ,
builtins ::{
string ::{ is_leading_surrogate , is_trailing_surrogate } ,
BuiltIn ,
} ,
object ::{ JsObject , ObjectInitializer , RecursionLimiter } ,
property ::{ Attribute , PropertyKey , PropertyNameKind } ,
symbol ::WellKnownSymbols ,
value ::IntegerOrInfinity ,
BoaProfiler , Context , JsResult , JsValue ,
BoaProfiler , Context , JsResult , JsString , Js Value ,
} ;
use serde ::Serialize ;
use serde_json ::{ self , ser ::PrettyFormatter , Serializer , Value as JSONValue } ;
use serde_json ::{ self , Value as JSONValue } ;
#[ cfg(test) ]
mod tests ;
@ -128,7 +131,7 @@ impl Json {
///
/// This `JSON` method converts a JavaScript object or value to a JSON string.
///
/// This med hod optionally replaces values if a `replacer` function is specified or
/// This met hod optionally replaces values if a `replacer` function is specified or
/// optionally including only the specified properties if a replacer array is specified.
///
/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert
@ -145,133 +148,533 @@ impl Json {
args : & [ JsValue ] ,
context : & mut Context ,
) -> JsResult < JsValue > {
let object = match args . get ( 0 ) {
None = > return Ok ( JsValue ::undefined ( ) ) ,
Some ( obj ) = > obj ,
} ;
const SPACE_INDENT : & str = " " ;
let gap = if let Some ( space ) = args . get ( 2 ) {
let space = if let Some ( space_obj ) = space . as_object ( ) {
if let Some ( space ) = space_obj . borrow ( ) . as_number ( ) {
JsValue ::new ( space )
} else if let Some ( space ) = space_obj . borrow ( ) . as_string ( ) {
JsValue ::new ( space )
// 1. Let stack be a new empty List.
let stack = Vec ::new ( ) ;
// 2. Let indent be the empty String.
let indent = JsString ::new ( "" ) ;
// 3. Let PropertyList and ReplacerFunction be undefined.
let mut property_list = None ;
let mut replacer_function = None ;
let replacer = args . get ( 1 ) . cloned ( ) . unwrap_or_default ( ) ;
// 4. If Type(replacer) is Object, then
if let Some ( replacer_obj ) = replacer . as_object ( ) {
// a. If IsCallable(replacer) is true, then
if replacer_obj . is_callable ( ) {
// i. Set ReplacerFunction to replacer.
replacer_function = Some ( replacer_obj )
// b. Else,
} else {
space . clone ( )
// i. Let isArray be ? IsArray(replacer).
// ii. If isArray is true, then
if replacer_obj . is_array ( ) {
// 1. Set PropertyList to a new empty List.
let mut property_set = HashSet ::new ( ) ;
// 2. Let len be ? LengthOfArrayLike(replacer).
let len = replacer_obj . length_of_array_like ( context ) ? ;
// 3. Let k be 0.
let mut k = 0 ;
// 4. Repeat, while k < len,
while k < len {
// a. Let prop be ! ToString(𝔽(k)).
// b. Let v be ? Get(replacer, prop).
let v = replacer_obj . get ( k , context ) ? ;
// c. Let item be undefined.
// d. If Type(v) is String, set item to v.
// e. Else if Type(v) is Number, set item to ! ToString(v).
// f. Else if Type(v) is Object, then
// g. If item is not undefined and item is not currently an element of PropertyList, then
// i. Append item to the end of PropertyList.
if let Some ( s ) = v . as_string ( ) {
property_set . insert ( s . to_owned ( ) ) ;
} else if v . is_number ( ) {
property_set . insert (
v . to_string ( context )
. expect ( "ToString cannot fail on number value" ) ,
) ;
} else if let Some ( obj ) = v . as_object ( ) {
// i. If v has a [[StringData]] or [[NumberData]] internal slot, set item to ? ToString(v).
if obj . is_string ( ) | | obj . is_number ( ) {
property_set . insert ( v . to_string ( context ) ? ) ;
}
}
// h. Set k to k + 1.
k + = 1 ;
}
property_list = Some ( property_set . into_iter ( ) . collect ( ) ) ;
}
}
}
let mut space = args . get ( 2 ) . cloned ( ) . unwrap_or_default ( ) ;
// 5. If Type(space) is Object, then
if let Some ( space_obj ) = space . as_object ( ) {
// a. If space has a [[NumberData]] internal slot, then
if space_obj . is_number ( ) {
// i. Set space to ? ToNumber(space).
space = space . to_number ( context ) ? . into ( ) ;
}
// b. Else if space has a [[StringData]] internal slot, then
else if space_obj . is_string ( ) {
// i. Set space to ? ToString(space).
space = space . to_string ( context ) ? . into ( ) ;
}
}
// 6. If Type(space) is Number, then
let gap = if space . is_number ( ) {
// a. Let spaceMV be ! ToIntegerOrInfinity(space).
// b. Set spaceMV to min(10, spaceMV).
// c. If spaceMV < 1, let gap be the empty String; otherwise let gap be the String value containing spaceMV occurrences of the code unit 0x0020 (SPACE).
match space
. to_integer_or_infinity ( context )
. expect ( "ToIntegerOrInfinity cannot fail on number" )
{
IntegerOrInfinity ::PositiveInfinity = > JsString ::new ( " " ) ,
IntegerOrInfinity ::NegativeInfinity = > JsString ::new ( "" ) ,
IntegerOrInfinity ::Integer ( i ) if i < 1 = > JsString ::new ( "" ) ,
IntegerOrInfinity ::Integer ( i ) = > {
let mut s = String ::new ( ) ;
let i = std ::cmp ::min ( 10 , i ) ;
for _ in 0 .. i {
s . push ( ' ' ) ;
}
s . into ( )
}
}
// 7. Else if Type(space) is String, then
} else if let Some ( s ) = space . as_string ( ) {
// a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10.
String ::from_utf16_lossy ( & s . encode_utf16 ( ) . take ( 10 ) . collect ::< Vec < u16 > > ( ) ) . into ( )
// 8. Else,
} else {
space . clone ( )
// a. Let gap be the empty String.
JsString ::new ( "" )
} ;
if space . is_number ( ) {
let space_mv = match space . to_integer_or_infinity ( context ) ? {
IntegerOrInfinity ::NegativeInfinity = > 0 ,
IntegerOrInfinity ::PositiveInfinity = > 10 ,
IntegerOrInfinity ::Integer ( i ) if i < 1 = > 0 ,
IntegerOrInfinity ::Integer ( i ) = > std ::cmp ::min ( i , 10 ) as usize ,
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
let wrapper = context . construct_object ( ) ;
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper
. create_data_property_or_throw ( "" , args . get ( 0 ) . cloned ( ) . unwrap_or_default ( ) , context )
. expect ( "CreateDataPropertyOrThrow should never fail here" ) ;
// 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }.
let mut state = StateRecord {
replacer_function ,
stack ,
indent ,
gap ,
property_list ,
} ;
JsValue ::new ( & SPACE_INDENT [ .. space_mv ] )
} else if let Some ( string ) = space . as_string ( ) {
JsValue ::new ( & string [ .. std ::cmp ::min ( string . len ( ) , 10 ) ] )
} else {
JsValue ::new ( "" )
// 12. Return ? SerializeJSONProperty(state, the empty String, wrapper).
Ok (
Self ::serialize_json_property ( & mut state , JsString ::new ( "" ) , wrapper , context ) ?
. map ( | s | s . into ( ) )
. unwrap_or_default ( ) ,
)
}
} else {
JsValue ::new ( "" )
/// `25.5.2.1 SerializeJSONProperty ( state, key, holder )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonproperty
fn serialize_json_property (
state : & mut StateRecord ,
key : JsString ,
holder : JsObject ,
context : & mut Context ,
) -> JsResult < Option < JsString > > {
// 1. Let value be ? Get(holder, key).
let mut value = holder . get ( key . clone ( ) , context ) ? ;
// 2. If Type(value) is Object or BigInt, then
if value . is_object ( ) | | value . is_bigint ( ) {
// a. Let toJSON be ? GetV(value, "toJSON").
let to_json = value . get_field ( "toJSON" , context ) ? ;
// b. If IsCallable(toJSON) is true, then
if let Some ( obj ) = to_json . as_object ( ) {
if obj . is_callable ( ) {
// i. Set value to ? Call(toJSON, value, « key »).
value = obj . call ( & value , & [ key . clone ( ) . into ( ) ] , context ) ? ;
}
}
}
// 3. If state.[[ReplacerFunction]] is not undefined, then
if let Some ( obj ) = & state . replacer_function {
// a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »).
value = obj . call ( & holder . into ( ) , & [ key . into ( ) , value ] , context ) ?
}
// 4. If Type(value) is Object, then
if let Some ( obj ) = value . as_object ( ) {
// a. If value has a [[NumberData]] internal slot, then
if obj . is_number ( ) {
// i. Set value to ? ToNumber(value).
value = value . to_number ( context ) ? . into ( ) ;
}
// b. Else if value has a [[StringData]] internal slot, then
else if obj . is_string ( ) {
// i. Set value to ? ToString(value).
value = value . to_string ( context ) ? . into ( ) ;
}
// c. Else if value has a [[BooleanData]] internal slot, then
else if let Some ( boolean ) = obj . borrow ( ) . as_boolean ( ) {
// i. Set value to value.[[BooleanData]].
value = boolean . into ( )
}
// d. Else if value has a [[BigIntData]] internal slot, then
else if let Some ( bigint ) = obj . borrow ( ) . as_bigint ( ) {
// i. Set value to value.[[BigIntData]].
value = bigint . clone ( ) . into ( )
}
}
// 5. If value is null, return "null".
if value . is_null ( ) {
return Ok ( Some ( JsString ::new ( "null" ) ) ) ;
}
// 6. If value is true, return "true".
// 7. If value is false, return "false".
if value . is_boolean ( ) {
return match value . to_boolean ( ) {
true = > Ok ( Some ( JsString ::new ( "true" ) ) ) ,
false = > Ok ( Some ( JsString ::new ( "false" ) ) ) ,
} ;
}
// 8. If Type(value) is String, return QuoteJSONString(value).
if let Some ( s ) = value . as_string ( ) {
return Ok ( Some ( Self ::quote_json_string ( s ) ) ) ;
}
let gap = & gap . to_string ( context ) ? ;
// 9. If Type(value) is Number, then
if let Some ( n ) = value . as_number ( ) {
// a. If value is finite, return ! ToString(value).
if n . is_finite ( ) {
return Ok ( Some (
value
. to_string ( context )
. expect ( "ToString should never fail here" ) ,
) ) ;
}
// b. Return "null".
return Ok ( Some ( JsString ::new ( "null" ) ) ) ;
}
// 10. If Type(value) is BigInt, throw a TypeError exception.
if value . is_bigint ( ) {
return Err ( context . construct_type_error ( "cannot serialize bigint to JSON" ) ) ;
}
let replacer = match args . get ( 1 ) {
Some ( replacer ) if replacer . is_object ( ) = > replacer ,
_ = > {
if let Some ( value ) = object . to_json ( context ) ? {
return Ok ( JsValue ::new ( json_to_pretty_string ( & value , gap ) ) ) ;
// 11. If Type(value) is Object and IsCallable(value) is false, then
if let Some ( obj ) = value . as_object ( ) {
if ! obj . is_callable ( ) {
// a. Let isArray be ? IsArray(value).
// b. If isArray is true, return ? SerializeJSONArray(state, value).
// c. Return ? SerializeJSONObject(state, value).
return if obj . is_array ( ) {
Ok ( Some ( Self ::serialize_json_array ( state , obj , context ) ? ) )
} else {
return Ok ( JsValue ::undefined ( ) ) ;
Ok ( Some ( Self ::serialize_json_object ( state , obj , context ) ? ) )
} ;
}
}
// 12. Return undefined.
Ok ( None )
}
/// `25.5.2.2 QuoteJSONString ( value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-quotejsonstring
fn quote_json_string ( value : & JsString ) -> JsString {
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
let mut product = String ::from ( '"' ) ;
// 2. For each code point C of ! StringToCodePoints(value), do
for code_point in value . encode_utf16 ( ) {
match code_point {
// a. If C is listed in the “Code Point” column of Table 73, 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.
0x8 = > product . push_str ( "\\b" ) ,
0x9 = > product . push_str ( "\\t" ) ,
0xA = > product . push_str ( "\\n" ) ,
0xC = > product . push_str ( "\\f" ) ,
0xD = > product . push_str ( "\\r" ) ,
0x22 = > product . push_str ( "\\\"" ) ,
0x5C = > product . push_str ( "\\\\" ) ,
// 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
code_point
if is_leading_surrogate ( code_point ) | | is_trailing_surrogate ( code_point ) = >
{
// 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).
product . push_str ( & format! ( "\\\\uAA{:x}" , code_point ) ) ;
}
// c. Else,
code_point = > {
// i. Set product to the string-concatenation of product and ! UTF16EncodeCodePoint(C).
product . push (
char ::from_u32 ( code_point as u32 )
. expect ( "char from code point cannot fail here" ) ,
) ;
}
}
}
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
product . push ( '"' ) ;
// 4. Return product.
product . into ( )
}
/// `25.5.2.4 SerializeJSONObject ( state, value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonobject
fn serialize_json_object (
state : & mut StateRecord ,
value : JsObject ,
context : & mut Context ,
) -> JsResult < JsString > {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter ::new ( & value ) ;
if limiter . live {
return Err ( context . construct_type_error ( "cyclic object value" ) ) ;
}
// 2. Append value to state.[[Stack]].
state . stack . push ( value . clone ( ) . into ( ) ) ;
// 3. Let stepback be state.[[Indent]].
let stepback = state . indent . clone ( ) ;
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
state . indent = JsString ::concat ( & state . indent , & state . gap ) ;
// 5. If state.[[PropertyList]] is not undefined, then
let mut k = if let Some ( p ) = & state . property_list {
// a. Let K be state.[[PropertyList]].
p . clone ( )
// 6. Else,
} else {
// a. Let K be ? EnumerableOwnPropertyNames(value, key).
let keys = value . enumerable_own_property_names ( PropertyNameKind ::Key , context ) ? ;
// Unwrap is safe, because EnumerableOwnPropertyNames with kind "key" only returns string values.
keys . iter ( ) . map ( | v | v . to_string ( context ) . unwrap ( ) ) . collect ( )
} ;
let replacer_as_object = replacer
. as_object ( )
. expect ( "JSON.stringify replacer was an object" ) ;
if replacer_as_object . is_callable ( ) {
object
. as_object ( )
. map ( | obj | {
let object_to_return = JsValue ::new ( Object ::default ( ) ) ;
for key in obj . borrow ( ) . properties ( ) . keys ( ) {
let val = obj . __get__ ( & key , obj . clone ( ) . into ( ) , context ) ? ;
let this_arg = object . clone ( ) ;
object_to_return . set_property (
key . to_owned ( ) ,
PropertyDescriptor ::builder ( )
. value ( context . call (
replacer ,
& this_arg ,
& [ JsValue ::new ( key . clone ( ) ) , val . clone ( ) ] ,
) ? )
. writable ( true )
. enumerable ( true )
. configurable ( true ) ,
// Sort the property key list, because the internal property hashmap of objects does not sort the properties.
k . sort ( ) ;
// 7. Let partial be a new empty List.
let mut partial = Vec ::new ( ) ;
// 8. For each element P of K, do
for p in & k {
// a. Let strP be ? SerializeJSONProperty(state, P, value).
let str_p = Self ::serialize_json_property ( state , p . clone ( ) , value . clone ( ) , context ) ? ;
// b. If strP is not undefined, then
if let Some ( str_p ) = str_p {
// i. Let member be QuoteJSONString(P).
// ii. Set member to the string-concatenation of member and ":".
// iii. If state.[[Gap]] is not the empty String, then
// 1. Set member to the string-concatenation of member and the code unit 0x0020 (SPACE).
// iv. Set member to the string-concatenation of member and strP.
let member = if state . gap . is_empty ( ) {
format! ( "{}:{}" , Self ::quote_json_string ( p ) . as_str ( ) , str_p . as_str ( ) )
} else {
format! (
"{}: {}" ,
Self ::quote_json_string ( p ) . as_str ( ) ,
str_p . as_str ( )
)
} ;
// v. Append member to partial.
partial . push ( member ) ;
}
if let Some ( value ) = object_to_return . to_json ( context ) ? {
Ok ( JsValue ::new ( json_to_pretty_string ( & value , gap ) ) )
}
// 9. If partial is empty, then
let r#final = if partial . is_empty ( ) {
// a. Let final be "{}".
JsString ::new ( "{}" )
// 10. Else,
} else {
Ok ( JsValue ::undefined ( ) )
}
} )
. ok_or_else ( JsValue ::undefined ) ?
} else if replacer_as_object . is_array ( ) {
let mut obj_to_return = serde_json ::Map ::new ( ) ;
let replacer_as_object = replacer_as_object . borrow ( ) ;
let fields = replacer_as_object . properties ( ) . keys ( ) . filter_map ( | key | {
if key = = "length" {
None
// a. If state.[[Gap]] is the empty String, then
if state . gap . is_empty ( ) {
// i. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
// A comma is not inserted either before the first String or after the last String.
// ii. Let final be the string-concatenation of "{", properties, and "}".
format! ( "{{{}}}" , partial . join ( "," ) ) . into ( )
// b. Else,
} else {
Some (
replacer
. get_property ( key )
. as_ref ( )
. map ( | d | d . value ( ) )
. flatten ( )
. cloned ( )
. unwrap_or_default ( ) ,
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let separator = format! ( ",{}{}" , '\u{A}' , state . indent . as_str ( ) ) ;
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
let properties = partial . join ( & separator ) ;
// iii. Let final be the string-concatenation of "{", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "}".
format! (
"{{{}{}{}{}{}}}" ,
'\u{A}' ,
state . indent . as_str ( ) ,
& properties ,
'\u{A}' ,
stepback . as_str ( )
)
. into ( )
}
} ) ;
for field in fields {
let v = object . get_field ( field . to_string ( context ) ? , context ) ? ;
if ! v . is_undefined ( ) {
if let Some ( value ) = v . to_json ( context ) ? {
obj_to_return . insert ( field . to_string ( context ) ? . to_string ( ) , value ) ;
}
} ;
// 11. Remove the last element of state.[[Stack]].
state . stack . pop ( ) ;
// 12. Set state.[[Indent]] to stepback.
state . indent = stepback ;
// 13. Return final.
Ok ( r#final )
}
/// `25.5.2.5 SerializeJSONArray ( state, value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonarray
fn serialize_json_array (
state : & mut StateRecord ,
value : JsObject ,
context : & mut Context ,
) -> JsResult < JsString > {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter ::new ( & value ) ;
if limiter . live {
return Err ( context . construct_type_error ( "cyclic object value" ) ) ;
}
Ok ( JsValue ::new ( json_to_pretty_string (
& JSONValue ::Object ( obj_to_return ) ,
gap ,
) ) )
} else if let Some ( value ) = object . to_json ( context ) ? {
Ok ( JsValue ::new ( json_to_pretty_string ( & value , gap ) ) )
// 2. Append value to state.[[Stack]].
state . stack . push ( value . clone ( ) . into ( ) ) ;
// 3. Let stepback be state.[[Indent]].
let stepback = state . indent . clone ( ) ;
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
state . indent = JsString ::concat ( & state . indent , & state . gap ) ;
// 5. Let partial be a new empty List.
let mut partial = Vec ::new ( ) ;
// 6. Let len be ? LengthOfArrayLike(value).
let len = value . length_of_array_like ( context ) ? ;
// 7. Let index be 0.
let mut index = 0 ;
// 8. Repeat, while index < len,
while index < len {
// a. Let strP be ? SerializeJSONProperty(state, ! ToString(𝔽(index)), value).
let str_p = Self ::serialize_json_property (
state ,
index . to_string ( ) . into ( ) ,
value . clone ( ) ,
context ,
) ? ;
// b. If strP is undefined, then
if let Some ( str_p ) = str_p {
// i. Append strP to partial.
partial . push ( str_p )
// c. Else,
} else {
Ok ( JsValue ::undefined ( ) )
// i. Append "null" to partial.
partial . push ( "null" . into ( ) )
}
// d. Set index to index + 1.
index + = 1 ;
}
}
fn json_to_pretty_string ( json : & JSONValue , gap : & str ) -> String {
if gap . is_empty ( ) {
return json . to_string ( ) ;
// 9. If partial is empty, then
let r#final = if partial . is_empty ( ) {
// a. Let final be "[]".
JsString ::from ( "[]" )
// 10. Else,
} else {
// a. If state.[[Gap]] is the empty String, then
if state . gap . is_empty ( ) {
// i. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
// A comma is not inserted either before the first String or after the last String.
// ii. Let final be the string-concatenation of "[", properties, and "]".
format! ( "[{}]" , partial . join ( "," ) ) . into ( )
// b. Else,
} else {
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let separator = format! ( ",{}{}" , '\u{A}' , state . indent . as_str ( ) ) ;
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
let properties = partial . join ( & separator ) ;
// iii. Let final be the string-concatenation of "[", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "]".
format! (
"[{}{}{}{}{}]" ,
'\u{A}' ,
state . indent . as_str ( ) ,
& properties ,
'\u{A}' ,
stepback . as_str ( )
)
. into ( )
}
let formatter = PrettyFormatter ::with_indent ( gap . as_bytes ( ) ) ;
let mut writer = Vec ::with_capacity ( 128 ) ;
let mut serializer = Serializer ::with_formatter ( & mut writer , formatter ) ;
json . serialize ( & mut serializer )
. expect ( "JSON serialization failed" ) ;
unsafe {
// The serde json serializer always produce correct UTF-8
String ::from_utf8_unchecked ( writer )
} ;
// 11. Remove the last element of state.[[Stack]].
state . stack . pop ( ) ;
// 12. Set state.[[Indent]] to stepback.
state . indent = stepback ;
// 13. Return final.
Ok ( r#final )
}
}
struct StateRecord {
replacer_function : Option < JsObject > ,
stack : Vec < JsValue > ,
indent : JsString ,
gap : JsString ,
property_list : Option < Vec < JsString > > ,
}