1/*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/base/fast_json_stringifier.h"
17
18#include "ecmascript/base/json_helper.h"
19#include "ecmascript/global_dictionary-inl.h"
20#include "ecmascript/interpreter/interpreter.h"
21#include "ecmascript/js_primitive_ref.h"
22#include "ecmascript/object_fast_operator-inl.h"
23
24namespace panda::ecmascript::base {
25JSHandle<JSTaggedValue> FastJsonStringifier::Stringify(const JSHandle<JSTaggedValue> &value)
26{
27    factory_ = thread_->GetEcmaVM()->GetFactory();
28    JSHandle<JSTaggedValue> jsonCache = thread_->GetEcmaVM()->GetGlobalEnv()->GetJsonObjectHclassCache();
29    if (jsonCache->IsHole()) {
30        hclassCache_ = factory_->NewTaggedArray(JSON_CACHE_SIZE);
31    } else {
32        hclassCache_ = JSHandle<TaggedArray>::Cast(jsonCache);
33    }
34    JSTaggedValue tagValue = value.GetTaggedValue();
35    handleValue_ = JSMutableHandle<JSTaggedValue>(thread_, tagValue);
36    handleKey_ = JSMutableHandle<JSTaggedValue>(thread_, factory_->GetEmptyString());
37
38    if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
39        JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
40        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
41        handleValue_.Update(serializeValue);
42    }
43
44    JSTaggedValue result = SerializeJSONProperty(handleValue_);
45
46    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
47    if (!result.IsUndefined()) {
48        return JSHandle<JSTaggedValue>(
49            factory_->NewFromUtf8Literal(reinterpret_cast<const uint8_t *>(result_.c_str()), result_.size()));
50    }
51    return thread_->GlobalConstants()->GetHandledUndefined();
52}
53
54JSTaggedValue FastJsonStringifier::GetSerializeValue(const JSHandle<JSTaggedValue> &key,
55                                                     const JSHandle<JSTaggedValue> &value)
56{
57    JSTaggedValue tagValue = value.GetTaggedValue();
58    JSHandle<JSTaggedValue> undefined = thread_->GlobalConstants()->GetHandledUndefined();
59    // a. Let toJSON be Get(value, "toJSON").
60    JSHandle<JSTaggedValue> toJson = thread_->GlobalConstants()->GetHandledToJsonString();
61    JSHandle<JSTaggedValue> toJsonFun(
62        thread_, ObjectFastOperator::FastGetPropertyByValue(thread_, tagValue, toJson.GetTaggedValue()));
63    // b. ReturnIfAbrupt(toJSON).
64    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
65    tagValue = value.GetTaggedValue();
66    // c. If IsCallable(toJSON) is true
67    if (UNLIKELY(toJsonFun->IsCallable())) {
68        // Let value be Call(toJSON, value, «key»).
69        EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, toJsonFun, value, undefined, 1);
70        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
71        info->SetCallArg(key.GetTaggedValue());
72        tagValue = JSFunction::Call(info);
73        // ii. ReturnIfAbrupt(value).
74        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
75    }
76    return tagValue;
77}
78
79JSTaggedValue FastJsonStringifier::SerializeJSONProperty(const JSHandle<JSTaggedValue> &value)
80{
81    JSTaggedValue tagValue = value.GetTaggedValue();
82    if (!tagValue.IsHeapObject()) {
83        JSTaggedType type = tagValue.GetRawData();
84        switch (type) {
85            // If value is false, return "false".
86            case JSTaggedValue::VALUE_FALSE:
87                result_ += "false";
88                return tagValue;
89            // If value is true, return "true".
90            case JSTaggedValue::VALUE_TRUE:
91                result_ += "true";
92                return tagValue;
93            // If value is null, return "null".
94            case JSTaggedValue::VALUE_NULL:
95                result_ += "null";
96                return tagValue;
97            default:
98                // If Type(value) is Number, then
99                if (tagValue.IsNumber()) {
100                    // a. If value is finite, return ToString(value).
101                    if (std::isfinite(tagValue.GetNumber())) {
102                        result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, tagValue));
103                    } else {
104                        // b. Else, return "null".
105                        result_ += "null";
106                    }
107                    return tagValue;
108                }
109        }
110    } else {
111        JSType jsType = tagValue.GetTaggedObject()->GetClass()->GetObjectType();
112        JSHandle<JSTaggedValue> valHandle(thread_, tagValue);
113        switch (jsType) {
114            case JSType::JS_ARRAY:
115            case JSType::JS_SHARED_ARRAY: {
116                SerializeJSArray(valHandle);
117                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
118                return tagValue;
119            }
120            // If Type(value) is String, return QuoteJSONString(value).
121            case JSType::LINE_STRING:
122            case JSType::CONSTANT_STRING:
123            case JSType::TREE_STRING:
124            case JSType::SLICED_STRING: {
125                JSHandle<EcmaString> strHandle = JSHandle<EcmaString>(valHandle);
126                auto string = JSHandle<EcmaString>(thread_,
127                    EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle));
128                CString str = ConvertToString(*string, StringConvertedUsage::LOGICOPERATION);
129                JsonHelper::AppendValueToQuotedString(str, result_);
130                return tagValue;
131            }
132            case JSType::JS_PRIMITIVE_REF: {
133                SerializePrimitiveRef(valHandle);
134                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, JSTaggedValue::Exception());
135                return tagValue;
136            }
137            case JSType::SYMBOL:
138                return JSTaggedValue::Undefined();
139            case JSType::BIGINT: {
140                THROW_TYPE_ERROR_AND_RETURN(thread_, "cannot serialize a BigInt", JSTaggedValue::Exception());
141            }
142            default: {
143                if (!tagValue.IsCallable()) {
144                    JSHClass *jsHclass = tagValue.GetTaggedObject()->GetClass();
145                    if (UNLIKELY(jsHclass->IsJSProxy() &&
146                        JSProxy::Cast(tagValue.GetTaggedObject())->IsArray(thread_))) {
147                        SerializeJSProxy(valHandle);
148                        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
149                    } else {
150                        SerializeJSONObject(valHandle);
151                        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
152                    }
153                    return tagValue;
154                }
155            }
156        }
157    }
158    return JSTaggedValue::Undefined();
159}
160
161CString FastJsonStringifier::SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent)
162{
163    if (hasContent) {
164        result_ += ",";
165    }
166
167    CString str;
168    if (key->IsString()) {
169        str = ConvertToString(EcmaString::Cast(key->GetTaggedObject()), StringConvertedUsage::LOGICOPERATION);
170    } else if (key->IsInt()) {
171        str = NumberHelper::IntToString(static_cast<int32_t>(key->GetInt()));
172    } else {
173        str = ConvertToString(*JSTaggedValue::ToString(thread_, key), StringConvertedUsage::LOGICOPERATION);
174    }
175    JsonHelper::AppendValueToQuotedString(str, result_);
176    result_ += ":";
177
178    return str;
179}
180
181bool FastJsonStringifier::PushValue(const JSHandle<JSTaggedValue> &value)
182{
183    uint32_t thisLen = stack_.size();
184
185    for (uint32_t i = 0; i < thisLen; i++) {
186        bool equal = JSTaggedValue::SameValue(stack_[i].GetTaggedValue(), value.GetTaggedValue());
187        if (equal) {
188            return true;
189        }
190    }
191
192    stack_.emplace_back(value);
193    return false;
194}
195
196void FastJsonStringifier::PopValue()
197{
198    stack_.pop_back();
199}
200
201bool FastJsonStringifier::SerializeJSONObject(const JSHandle<JSTaggedValue> &value)
202{
203    bool isContain = PushValue(value);
204    if (isContain) {
205        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
206    }
207
208    result_ += "{";
209    bool hasContent = false;
210
211    ASSERT(!value->IsAccessor());
212    JSHandle<JSObject> obj(value);
213    if (UNLIKELY(value->IsJSProxy() || value->IsTypedArray())) {  // serialize proxy and typedArray
214        JSHandle<TaggedArray> propertyArray = JSObject::EnumerableOwnNames(thread_, obj);
215        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
216        uint32_t arrLength = propertyArray->GetLength();
217        for (uint32_t i = 0; i < arrLength; i++) {
218            handleKey_.Update(propertyArray->Get(i));
219            JSHandle<JSTaggedValue> valueHandle = JSTaggedValue::GetProperty(thread_, value, handleKey_).GetValue();
220            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
221            if (UNLIKELY(valueHandle->IsECMAObject() || valueHandle->IsBigInt())) {
222                JSTaggedValue serializeValue = GetSerializeValue(handleKey_, valueHandle);
223                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
224                if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
225                    (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
226                    continue;
227                }
228                handleValue_.Update(serializeValue);
229            } else {
230                handleValue_.Update(valueHandle);
231            }
232            SerializeObjectKey(handleKey_, hasContent);
233            JSTaggedValue res = SerializeJSONProperty(handleValue_);
234            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
235            if (!res.IsUndefined()) {
236                hasContent = true;
237            }
238        }
239    } else {
240        uint32_t numOfKeys = obj->GetNumberOfKeys();
241        uint32_t numOfElements = obj->GetNumberOfElements();
242        if (numOfKeys + numOfElements < CACHE_MINIMUN_SIZIE || !cacheable_) {
243            if (numOfElements > 0) {
244                hasContent = DefaultSerializeElements(obj, hasContent);
245                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
246            }
247            if (numOfKeys > 0) {
248                hasContent = DefaultSerializeKeys(obj, hasContent);
249                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
250            }
251        } else {
252            JSHClass *jsHclass = value->GetTaggedObject()->GetClass();
253            int32_t index = FindCache(jsHclass, numOfKeys + numOfElements);
254            if (index != INVALID_INDEX) {
255                auto strCache = thread_->GetCurrentEcmaContext()->GetJsonStringifyCache(index);
256                uint32_t cacheIndex = 0;
257                if (numOfElements > 0) {
258                    hasContent = SerializeElementsWithCache(obj, hasContent, strCache, cacheIndex, numOfElements);
259                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
260                }
261                if (numOfKeys > 0) {
262                    hasContent = SerializeKeysWithCache(obj, hasContent, strCache, cacheIndex);
263                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
264                }
265            } else {
266                CVector<std::pair<CString, int>> strCache;
267                if (numOfElements > 0) {
268                    hasContent = TryCacheSerializeElements(obj, hasContent, strCache);
269                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
270                }
271                if (numOfKeys > 0) {
272                    hasContent = TryCacheSerializeKeys(obj, hasContent, strCache);
273                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
274                }
275                if (cacheable_) {
276                    SetCache(value->GetTaggedObject()->GetClass(), numOfElements + numOfKeys, strCache);
277                }
278            }
279        }
280    }
281
282    result_ += "}";
283    PopValue();
284    return true;
285}
286
287bool FastJsonStringifier::SerializeJSProxy(const JSHandle<JSTaggedValue> &object)
288{
289    bool isContain = PushValue(object);
290    if (isContain) {
291        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
292    }
293
294    result_ += "[";
295    JSHandle<JSProxy> proxy(object);
296    JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
297    JSHandle<JSTaggedValue> lenghHandle = JSProxy::GetProperty(thread_, proxy, lengthKey).GetValue();
298    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
299    JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenghHandle);
300    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
301    uint32_t length = lenNumber.ToUint32();
302    for (uint32_t i = 0; i < length; i++) {
303        handleKey_.Update(JSTaggedValue(i));
304        JSHandle<JSTaggedValue> valHandle = JSProxy::GetProperty(thread_, proxy, handleKey_).GetValue();
305        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
306        if (i > 0) {
307            result_ += ",";
308        }
309        if (UNLIKELY(valHandle->IsECMAObject() || valHandle->IsBigInt())) {
310            JSTaggedValue serializeValue = GetSerializeValue(handleKey_, valHandle);
311            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
312            handleValue_.Update(serializeValue);
313        } else {
314            handleValue_.Update(valHandle);
315        }
316        JSTaggedValue res = SerializeJSONProperty(handleValue_);
317        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
318        if (res.IsUndefined()) {
319            result_ += "null";
320        }
321    }
322
323    result_ += "]";
324    PopValue();
325    return true;
326}
327
328bool FastJsonStringifier::SerializeJSArray(const JSHandle<JSTaggedValue> &value)
329{
330    // If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
331    bool isContain = PushValue(value);
332    if (isContain) {
333        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
334    }
335
336    result_ += "[";
337    uint32_t len = 0;
338    if (value->IsJSArray()) {
339        JSHandle<JSArray> jsArr(value);
340        len = jsArr->GetArrayLength();
341    } else if (value->IsJSSharedArray()) {
342        JSHandle<JSSharedArray> jsArr(value);
343        len = jsArr->GetArrayLength();
344    }
345    if (len > 0) {
346        for (uint32_t i = 0; i < len; i++) {
347            JSTaggedValue tagVal = ObjectFastOperator::FastGetPropertyByIndex(thread_, value.GetTaggedValue(), i);
348            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
349            if (UNLIKELY(tagVal.IsAccessor())) {
350                tagVal = JSObject::CallGetter(thread_, AccessorData::Cast(tagVal.GetTaggedObject()), value);
351                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
352            }
353            handleKey_.Update(JSTaggedValue(i));
354            handleValue_.Update(tagVal);
355
356            if (i > 0) {
357                result_ += ",";
358            }
359            if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
360                JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
361                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
362                handleValue_.Update(serializeValue);
363            }
364            JSTaggedValue res = SerializeJSONProperty(handleValue_);
365            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
366            if (res.IsUndefined()) {
367                result_ += "null";
368            }
369        }
370    }
371
372    result_ += "]";
373    PopValue();
374    return true;
375}
376
377void FastJsonStringifier::SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef)
378{
379    JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue();
380    if (primitive.IsString()) {
381        auto priStr = JSTaggedValue::ToString(thread_, primitiveRef);
382        RETURN_IF_ABRUPT_COMPLETION(thread_);
383        CString str = ConvertToString(*priStr, StringConvertedUsage::LOGICOPERATION);
384        JsonHelper::AppendValueToQuotedString(str, result_);
385    } else if (primitive.IsNumber()) {
386        auto priNum = JSTaggedValue::ToNumber(thread_, primitiveRef);
387        RETURN_IF_ABRUPT_COMPLETION(thread_);
388        if (std::isfinite(priNum.GetNumber())) {
389            result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, priNum));
390        } else {
391            result_ += "null";
392        }
393    } else if (primitive.IsBoolean()) {
394        result_ += primitive.IsTrue() ? "true" : "false";
395    } else if (primitive.IsBigInt()) {
396        THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt");
397    }
398}
399
400bool FastJsonStringifier::TryCacheSerializeElements(const JSHandle<JSObject> &obj, bool hasContent,
401                                                    CVector<std::pair<CString, int>> &strCache)
402{
403    if (!ElementAccessor::IsDictionaryMode(obj)) {
404        uint32_t elementsLen = ElementAccessor::GetElementsLength(obj);
405        for (uint32_t i = 0; i < elementsLen; ++i) {
406            if (!ElementAccessor::Get(obj, i).IsHole()) {
407                handleKey_.Update(JSTaggedValue(i));
408                handleValue_.Update(ElementAccessor::Get(obj, i));
409                hasContent = AppendJsonString(hasContent, strCache, i);
410                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
411            }
412        }
413    } else {
414        JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements());
415        JSHandle<NumberDictionary> numberDic(elementsArr);
416        CVector<JSHandle<JSTaggedValue>> sortArr;
417        int size = numberDic->Size();
418        for (int hashIndex = 0; hashIndex < size; hashIndex++) {
419            JSTaggedValue key = numberDic->GetKey(hashIndex);
420            if (!key.IsUndefined() && !key.IsHole()) {
421                PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
422                if (attr.IsEnumerable()) {
423                    JSTaggedValue numberKey = JSTaggedValue(static_cast<uint32_t>(key.GetInt()));
424                    sortArr.emplace_back(JSHandle<JSTaggedValue>(thread_, numberKey));
425                }
426            }
427        }
428        std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
429        for (const auto &entry : sortArr) {
430            JSTaggedValue entryKey = entry.GetTaggedValue();
431            handleKey_.Update(entryKey);
432            int index = numberDic->FindEntry(entryKey);
433            JSTaggedValue value = numberDic->GetValue(index);
434            if (UNLIKELY(value.IsAccessor())) {
435                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
436                                             JSHandle<JSTaggedValue>(obj));
437                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
438            }
439            handleValue_.Update(value);
440            hasContent = AppendJsonString(hasContent, strCache, index);
441            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
442        }
443    }
444    return hasContent;
445}
446
447bool FastJsonStringifier::SerializeElementsWithCache(const JSHandle<JSObject> &obj, bool hasContent,
448    CVector<std::pair<CString, int>> &strCache, uint32_t &cacheIndex, uint32_t elementSize)
449{
450    if (!ElementAccessor::IsDictionaryMode(obj)) {
451        uint32_t elementsLen = ElementAccessor::GetElementsLength(obj);
452        for (uint32_t i = 0; i < elementsLen; ++i) {
453            if (!ElementAccessor::Get(obj, i).IsHole()) {
454                CString key = strCache[cacheIndex++].first;
455                handleValue_.Update(ElementAccessor::Get(obj, i));
456                hasContent = FastAppendJsonString(hasContent, key);
457                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
458            }
459        }
460    } else {
461        JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements());
462        JSHandle<NumberDictionary> numberDic(elementsArr);
463        for (; cacheIndex < elementSize; cacheIndex++) {
464            CString key = strCache[cacheIndex].first;
465            int index = strCache[cacheIndex].second;
466            JSTaggedValue value = numberDic->GetValue(index);
467            if (UNLIKELY(value.IsAccessor())) {
468                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
469                                             JSHandle<JSTaggedValue>(obj));
470                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
471            }
472            handleValue_.Update(value);
473            hasContent = FastAppendJsonString(hasContent, key);
474            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
475        }
476    }
477    return hasContent;
478}
479
480bool FastJsonStringifier::TryCacheSerializeKeys(const JSHandle<JSObject> &obj, bool hasContent,
481                                                CVector<std::pair<CString, int>> &strCache)
482{
483    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
484    if (!propertiesArr->IsDictionaryMode()) {
485        return TryCacheSerializeKeysFromPropertiesArray(obj, hasContent, strCache);
486    }
487
488    if (obj->IsJSGlobalObject()) {
489        return TryCacheSerializeKeysFromGlobalObject(obj, hasContent, strCache);
490    }
491
492    return TryCacheSerializeKeysFromNameDictionary(obj, hasContent, strCache);
493}
494
495bool FastJsonStringifier::TryCacheSerializeKeysFromPropertiesArray(const JSHandle<JSObject> &obj, bool hasContent,
496                                                                   CVector<std::pair<CString, int>> &strCache)
497{
498    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
499    JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
500    JSTaggedValue enumCache = jsHclass->GetEnumCache();
501    if (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) {
502        return TryCacheSerializeKeysFromEnumCache(obj, hasContent, strCache);
503    }
504
505    int end = static_cast<int>(jsHclass->NumberOfProps());
506    if (end <= 0) {
507        return hasContent;
508    }
509
510    for (int i = 0; i < end; i++) {
511        LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
512        JSTaggedValue key = layoutInfo->GetKey(i);
513        if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
514            handleKey_.Update(key);
515            JSTaggedValue value;
516            int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
517            PropertyAttributes attr(layoutInfo->GetAttr(index));
518            ASSERT(static_cast<int>(attr.GetOffset()) == index);
519            value = attr.IsInlinedProps()
520                    ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
521                    : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
522            if (attr.IsInlinedProps() && value.IsHole()) {
523                continue;
524            }
525            if (UNLIKELY(value.IsAccessor())) {
526                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
527                                             JSHandle<JSTaggedValue>(obj));
528                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
529            }
530            handleValue_.Update(value);
531            hasContent = AppendJsonString(hasContent, strCache, index);
532            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
533        }
534    }
535    return hasContent;
536}
537
538bool FastJsonStringifier::TryCacheSerializeKeysFromEnumCache(const JSHandle<JSObject> &obj, bool hasContent,
539                                                             CVector<std::pair<CString, int>> &strCache)
540{
541    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
542    JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
543    JSHandle<TaggedArray> cache(thread_, jsHclass->GetEnumCache());
544    uint32_t length = cache->GetLength();
545    for (uint32_t i = 0; i < length; i++) {
546        JSTaggedValue key = cache->Get(i);
547        if (!key.IsString()) {
548            continue;
549        }
550        handleKey_.Update(key);
551        JSTaggedValue value;
552        LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
553        int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
554        PropertyAttributes attr(layoutInfo->GetAttr(index));
555        ASSERT(static_cast<int>(attr.GetOffset()) == index);
556        value = attr.IsInlinedProps()
557                ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
558                : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
559        if (attr.IsInlinedProps() && value.IsHole()) {
560            continue;
561        }
562        if (UNLIKELY(value.IsAccessor())) {
563            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
564                                         JSHandle<JSTaggedValue>(obj));
565            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
566        }
567        handleValue_.Update(value);
568        hasContent = AppendJsonString(hasContent, strCache, index);
569        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
570    }
571    return hasContent;
572}
573
574bool FastJsonStringifier::TryCacheSerializeKeysFromGlobalObject(const JSHandle<JSObject> &obj, bool hasContent,
575                                                                CVector<std::pair<CString, int>> &strCache)
576{
577    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
578    JSHandle<GlobalDictionary> globalDic(propertiesArr);
579    int size = globalDic->Size();
580    CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
581    for (int hashIndex = 0; hashIndex < size; hashIndex++) {
582        JSTaggedValue key = globalDic->GetKey(hashIndex);
583        if (!key.IsString()) {
584            continue;
585        }
586        PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
587        if (!attr.IsEnumerable()) {
588            continue;
589        }
590        std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
591        sortArr.emplace_back(pair);
592    }
593    std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
594    for (const auto &entry : sortArr) {
595        JSTaggedValue entryKey = entry.first.GetTaggedValue();
596        handleKey_.Update(entryKey);
597        int index = globalDic->FindEntry(entryKey);
598        JSTaggedValue value = globalDic->GetValue(index);
599        if (UNLIKELY(value.IsAccessor())) {
600            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
601                                         JSHandle<JSTaggedValue>(obj));
602            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
603        }
604        handleValue_.Update(value);
605        hasContent = AppendJsonString(hasContent, strCache, index);
606        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
607    }
608    return hasContent;
609}
610
611bool FastJsonStringifier::TryCacheSerializeKeysFromNameDictionary(const JSHandle<JSObject> &obj, bool hasContent,
612                                                                  CVector<std::pair<CString, int>> &strCache)
613{
614    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
615    JSHandle<NameDictionary> nameDic(propertiesArr);
616    int size = nameDic->Size();
617    CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
618    for (int hashIndex = 0; hashIndex < size; hashIndex++) {
619        JSTaggedValue key = nameDic->GetKey(hashIndex);
620        if (!key.IsString()) {
621            continue;
622        }
623        PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
624        if (!attr.IsEnumerable()) {
625            continue;
626        }
627        std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
628        sortArr.emplace_back(pair);
629    }
630    std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
631    for (const auto &entry : sortArr) {
632        JSTaggedValue entryKey = entry.first.GetTaggedValue();
633        handleKey_.Update(entryKey);
634        int index = nameDic->FindEntry(entryKey);
635        JSTaggedValue value = nameDic->GetValue(index);
636        if (UNLIKELY(value.IsAccessor())) {
637            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
638                                         JSHandle<JSTaggedValue>(obj));
639            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
640        }
641        handleValue_.Update(value);
642        hasContent = AppendJsonString(hasContent, strCache, index);
643        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
644    }
645    return hasContent;
646}
647
648bool FastJsonStringifier::SerializeKeysWithCache(const JSHandle<JSObject> &obj, bool hasContent,
649                                                 CVector<std::pair<CString, int>> &strCache, uint32_t &cacheIndex)
650{
651    JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
652    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
653    if (!propertiesArr->IsDictionaryMode()) {
654        for (; cacheIndex < strCache.size(); cacheIndex++) {
655            auto cacheValue = strCache[cacheIndex];
656            CString str = cacheValue.first;
657            int index = cacheValue.second;
658            LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
659            PropertyAttributes attr(layoutInfo->GetAttr(index));
660            JSTaggedValue value = attr.IsInlinedProps()
661                    ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
662                    : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
663            if (UNLIKELY(value.IsAccessor())) {
664                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
665                                             JSHandle<JSTaggedValue>(obj));
666                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
667            }
668            handleValue_.Update(value);
669            hasContent = FastAppendJsonString(hasContent, str);
670            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
671        }
672        return hasContent;
673    }
674    if (obj->IsJSGlobalObject()) {
675        JSHandle<GlobalDictionary> globalDic(propertiesArr);
676        for (; cacheIndex < strCache.size(); cacheIndex++) {
677            auto cacheValue = strCache[cacheIndex];
678            CString str = cacheValue.first;
679            int index = cacheValue.second;
680            JSTaggedValue value = globalDic->GetValue(index);
681            if (UNLIKELY(value.IsAccessor())) {
682                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
683                                             JSHandle<JSTaggedValue>(obj));
684                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
685            }
686            handleValue_.Update(value);
687            hasContent = FastAppendJsonString(hasContent, str);
688            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
689        }
690        return hasContent;
691    }
692    JSHandle<NameDictionary> nameDic(propertiesArr);
693    for (; cacheIndex < strCache.size(); cacheIndex++) {
694        auto cacheValue = strCache[cacheIndex];
695        CString str = cacheValue.first;
696        int index = cacheValue.second;
697        JSTaggedValue value = nameDic->GetValue(index);
698        if (UNLIKELY(value.IsAccessor())) {
699            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
700                                         JSHandle<JSTaggedValue>(obj));
701            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
702        }
703        handleValue_.Update(value);
704        hasContent = FastAppendJsonString(hasContent, str);
705        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
706    }
707    return hasContent;
708}
709
710bool FastJsonStringifier::AppendJsonString(bool hasContent, CVector<std::pair<CString, int>> &strCache, int index)
711{
712    if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
713        JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
714        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
715        if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
716            (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
717            return hasContent;
718        }
719        handleValue_.Update(serializeValue);
720    }
721    CString keyStr = SerializeObjectKey(handleKey_, hasContent);
722    strCache.emplace_back(std::pair<CString, int>(keyStr, index));
723    JSTaggedValue res = SerializeJSONProperty(handleValue_);
724    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
725    if (!res.IsUndefined()) {
726        return true;
727    }
728    EraseKeyString(keyStr, hasContent);
729    return hasContent;
730}
731
732bool FastJsonStringifier::FastAppendJsonString(bool hasContent, CString &key)
733{
734    if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
735        JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
736        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
737        if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
738            (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
739            return hasContent;
740        }
741        handleValue_.Update(serializeValue);
742    }
743    FastSerializeObjectKey(key, hasContent);
744    JSTaggedValue res = SerializeJSONProperty(handleValue_);
745    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
746    if (!res.IsUndefined()) {
747        return true;
748    }
749    EraseKeyString(key, hasContent);
750    return hasContent;
751}
752
753bool FastJsonStringifier::DefaultSerializeElements(const JSHandle<JSObject> &obj, bool hasContent)
754{
755    if (!ElementAccessor::IsDictionaryMode(obj)) {
756        uint32_t elementsLen = ElementAccessor::GetElementsLength(obj);
757        for (uint32_t i = 0; i < elementsLen; ++i) {
758            if (!ElementAccessor::Get(obj, i).IsHole()) {
759                handleKey_.Update(JSTaggedValue(i));
760                handleValue_.Update(ElementAccessor::Get(obj, i));
761                hasContent = AppendJsonString(hasContent);
762                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
763            }
764        }
765    } else {
766        JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements());
767        JSHandle<NumberDictionary> numberDic(elementsArr);
768        CVector<JSHandle<JSTaggedValue>> sortArr;
769        int size = numberDic->Size();
770        for (int hashIndex = 0; hashIndex < size; hashIndex++) {
771            JSTaggedValue key = numberDic->GetKey(hashIndex);
772            if (!key.IsUndefined() && !key.IsHole()) {
773                PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
774                if (attr.IsEnumerable()) {
775                    JSTaggedValue numberKey = JSTaggedValue(static_cast<uint32_t>(key.GetInt()));
776                    sortArr.emplace_back(JSHandle<JSTaggedValue>(thread_, numberKey));
777                }
778            }
779        }
780        std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
781        for (const auto &entry : sortArr) {
782            JSTaggedValue entryKey = entry.GetTaggedValue();
783            handleKey_.Update(entryKey);
784            int index = numberDic->FindEntry(entryKey);
785            JSTaggedValue value = numberDic->GetValue(index);
786            if (UNLIKELY(value.IsAccessor())) {
787                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
788                                             JSHandle<JSTaggedValue>(obj));
789                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
790            }
791            handleValue_.Update(value);
792            hasContent = AppendJsonString(hasContent);
793            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
794        }
795    }
796    return hasContent;
797}
798
799bool FastJsonStringifier::DefaultSerializeKeys(const JSHandle<JSObject> &obj, bool hasContent)
800{
801    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
802    if (!propertiesArr->IsDictionaryMode()) {
803        JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
804        JSTaggedValue enumCache = jsHclass->GetEnumCache();
805        if (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) {
806            return SerializeKeysFromCache(obj, enumCache, propertiesArr, hasContent);
807        } else {
808            return SerializeKeysFromLayout(obj, jsHclass, propertiesArr, hasContent);
809        }
810    } else if (obj->IsJSGlobalObject()) {
811        return SerializeKeysFromGlobalDictionary(obj, propertiesArr, hasContent);
812    } else {
813        return SerializeKeysFromNameDictionary(obj, propertiesArr, hasContent);
814    }
815}
816
817bool FastJsonStringifier::SerializeKeysFromCache(const JSHandle<JSObject> &obj, JSTaggedValue enumCache,
818                                                 const JSHandle<TaggedArray> &propertiesArr, bool hasContent)
819{
820    JSHandle<TaggedArray> cache(thread_, enumCache);
821    uint32_t length = cache->GetLength();
822    for (uint32_t i = 0; i < length; i++) {
823        JSTaggedValue key = cache->Get(i);
824        if (!key.IsString()) {
825            continue;
826        }
827        hasContent = SerializeKeyValue(obj, key, propertiesArr, hasContent);
828        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
829    }
830    return hasContent;
831}
832
833bool FastJsonStringifier::SerializeKeysFromLayout(const JSHandle<JSObject> &obj, const JSHandle<JSHClass> &jsHclass,
834                                                  const JSHandle<TaggedArray> &propertiesArr, bool hasContent)
835{
836    int end = static_cast<int>(jsHclass->NumberOfProps());
837    if (end <= 0) {
838        return hasContent;
839    }
840    for (int i = 0; i < end; i++) {
841        LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
842        JSTaggedValue key = layoutInfo->GetKey(i);
843        if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
844            hasContent = SerializeKeyValue(obj, key, propertiesArr, hasContent);
845            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
846        }
847    }
848    return hasContent;
849}
850
851bool FastJsonStringifier::SerializeKeysFromGlobalDictionary(const JSHandle<JSObject> &obj,
852                                                            const JSHandle<TaggedArray> &propertiesArr,
853                                                            bool hasContent)
854{
855    JSHandle<GlobalDictionary> globalDic(propertiesArr);
856    int size = globalDic->Size();
857    CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
858    for (int hashIndex = 0; hashIndex < size; hashIndex++) {
859        JSTaggedValue key = globalDic->GetKey(hashIndex);
860        if (!key.IsString()) {
861            continue;
862        }
863        PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
864        if (!attr.IsEnumerable()) {
865            continue;
866        }
867        std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
868        sortArr.emplace_back(pair);
869    }
870    std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
871    for (const auto &entry : sortArr) {
872        JSTaggedValue entryKey = entry.first.GetTaggedValue();
873        handleKey_.Update(entryKey);
874        int index = globalDic->FindEntry(entryKey);
875        JSTaggedValue value = globalDic->GetValue(index);
876        if (UNLIKELY(value.IsAccessor())) {
877            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
878                                         JSHandle<JSTaggedValue>(obj));
879            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
880        }
881        handleValue_.Update(value);
882        hasContent = AppendJsonString(hasContent);
883        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
884    }
885    return hasContent;
886}
887
888bool FastJsonStringifier::SerializeKeysFromNameDictionary(const JSHandle<JSObject> &obj,
889                                                          const JSHandle<TaggedArray> &propertiesArr,
890                                                          bool hasContent)
891{
892    JSHandle<NameDictionary> nameDic(propertiesArr);
893    int size = nameDic->Size();
894    CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
895    for (int hashIndex = 0; hashIndex < size; hashIndex++) {
896        JSTaggedValue key = nameDic->GetKey(hashIndex);
897        if (!key.IsString()) {
898            continue;
899        }
900        PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
901        if (!attr.IsEnumerable()) {
902            continue;
903        }
904        std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
905        sortArr.emplace_back(pair);
906    }
907    std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
908    for (const auto &entry : sortArr) {
909        JSTaggedValue entryKey = entry.first.GetTaggedValue();
910        handleKey_.Update(entryKey);
911        int index = nameDic->FindEntry(entryKey);
912        JSTaggedValue value = nameDic->GetValue(index);
913        if (UNLIKELY(value.IsAccessor())) {
914            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
915                                         JSHandle<JSTaggedValue>(obj));
916            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
917        }
918        handleValue_.Update(value);
919        hasContent = AppendJsonString(hasContent);
920        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
921    }
922    return hasContent;
923}
924
925bool FastJsonStringifier::SerializeKeyValue(const JSHandle<JSObject> &obj, JSTaggedValue key,
926                                            const JSHandle<TaggedArray> &propertiesArr, bool hasContent)
927{
928    handleKey_.Update(key);
929    JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
930    int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
931    LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
932    PropertyAttributes attr(layoutInfo->GetAttr(index));
933    ASSERT(static_cast<int>(attr.GetOffset()) == index);
934    JSTaggedValue value = attr.IsInlinedProps()
935                          ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
936                          : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
937    if (attr.IsInlinedProps() && value.IsHole()) {
938        return hasContent;
939    }
940    if (UNLIKELY(value.IsAccessor())) {
941        value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
942                                     JSHandle<JSTaggedValue>(obj));
943        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
944    }
945    handleValue_.Update(value);
946    hasContent = AppendJsonString(hasContent);
947    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
948    return hasContent;
949}
950
951bool FastJsonStringifier::AppendJsonString(bool hasContent)
952{
953    if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
954        JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
955        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
956        if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
957            (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
958            return hasContent;
959        }
960        handleValue_.Update(serializeValue);
961    }
962    CString keyStr = SerializeObjectKey(handleKey_, hasContent);
963    JSTaggedValue res = SerializeJSONProperty(handleValue_);
964    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
965    if (!res.IsUndefined()) {
966        return true;
967    }
968    EraseKeyString(keyStr, hasContent);
969    return hasContent;
970}
971
972bool FastJsonStringifier::DefaultSerializeObject(const JSTaggedValue &object, uint32_t numOfKeys,
973                                                 uint32_t numOfElements)
974{
975    JSHandle<JSTaggedValue> value(thread_, object);
976    bool isContain = PushValue(value);
977    if (isContain) {
978        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
979    }
980
981    result_ += "{";
982    bool hasContent = false;
983
984    JSHandle<JSObject> obj(value);
985    if (numOfElements > 0) {
986        hasContent = DefaultSerializeElements(obj, hasContent);
987        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
988    }
989    if (numOfKeys > 0) {
990        hasContent = DefaultSerializeKeys(obj, hasContent);
991        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
992        }
993
994    result_ += "}";
995    PopValue();
996    return true;
997}
998}  // namespace panda::ecmascript::base
999