diff --git a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp index e36bacb3b319ef8fc02ef502fb3580cca61d93b9..bf279e84120ab46b0f65446a86779844f6db79dd 100644 --- a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp @@ -23,6 +23,7 @@ namespace ark::ets::intrinsics::helpers { static constexpr int RECORD_HEAD_ENTRY_INDEX = 3; +static constexpr int RECORD_SIZE_INDEX = 5; static constexpr int RECORD_NEXT_FIELD_INDEX = 1; static constexpr int RECORD_KEY_FIELD_INDEX = 2; static constexpr int RECORD_VAL_FIELD_INDEX = 3; @@ -122,10 +123,7 @@ bool JSONStringifier::SerializeJSONObjectArray(EtsHandle &value) for (size_t i = 0; i < length; ++i) { auto elem = EtsHandle(coro, realArray->Get(i)); - if (elem.GetPtr() == nullptr) { - continue; - } - if (elem->GetClass()->IsFunction()) { + if (elem.GetPtr() == nullptr || elem->GetClass()->IsFunction()) { buffer_ += "null"; isSuccessful = true; } else { @@ -345,11 +343,93 @@ bool JSONStringifier::SerializeJSONNullValue() return true; } +template +PandaString JSONStringifier::HandleNumeric(EtsHandle &value) +{ + PandaString result; + auto coro = EtsCoroutine::GetCurrent(); + if constexpr (std::is_same_v) { + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetDoubleToStringCache(); + auto v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + result = "null"; + } else { + result = v; + } + } else if constexpr (std::is_same_v) { + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetFloatToStringCache(); + auto v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + result = "null"; + } else { + result = v; + } + } else if constexpr (std::is_same_v) { + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetLongToStringCache(); + auto v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + result = "null"; + } else { + result = v; + } + } else if constexpr (std::is_same()) { + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + result = val == 0 ? "false" : "true"; + } else if constexpr (std::is_same()) { + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + result = static_cast(val); + } else { + T val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + result = std::to_string(val); + } + return result; +} + +bool JSONStringifier::HandleRecordKey(EtsHandle &key) +{ + if (key->IsStringClass()) { + key_ = EtsString::FromEtsObject(key.GetPtr())->GetMutf8(); + } else { + auto platformTypes = PlatformTypes(EtsCoroutine::GetCurrent()); + if (key->IsInstanceOf(platformTypes->coreBoolean)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreDouble)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreFloat)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreLong)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreByte)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreShort)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreChar)) { + key_ = HandleNumeric(key); + } else if (key->IsInstanceOf(platformTypes->coreInt)) { + key_ = HandleNumeric(key); + } + } + return true; +} + bool JSONStringifier::SerializeJSONRecord(EtsHandle &value) { - buffer_ += "{"; + bool isContain = PushValue(value); + if (isContain) { + ThrowEtsException(EtsCoroutine::GetCurrent(), panda_file_items::class_descriptors::TYPE_ERROR, + "cyclic object value"); + return false; + } auto cls = value->GetClass(); auto headEntry = cls->GetFieldByIndex(RECORD_HEAD_ENTRY_INDEX); + auto size = cls->GetFieldByIndex(RECORD_SIZE_INDEX); + if (value->GetFieldPrimitive(size) == 0) { + buffer_ += "{}"; + return true; + } auto coro = EtsCoroutine::GetCurrent(); EtsHandleScope scope(coro); @@ -357,6 +437,7 @@ bool JSONStringifier::SerializeJSONRecord(EtsHandle &value) auto hasContent = false; EtsHandle next(coro, head->GetFieldObject(head->GetClass()->GetFieldByIndex(RECORD_NEXT_FIELD_INDEX))); + buffer_ += "{"; do { EtsHandle key(coro, next->GetFieldObject(next->GetClass()->GetFieldByIndex(RECORD_KEY_FIELD_INDEX))); EtsHandle val(coro, next->GetFieldObject(next->GetClass()->GetFieldByIndex(RECORD_VAL_FIELD_INDEX))); @@ -368,12 +449,7 @@ bool JSONStringifier::SerializeJSONRecord(EtsHandle &value) if (val->GetClass()->IsFunction()) { continue; } - if (key->IsStringClass()) { - key_ = EtsString::FromEtsObject(key.GetPtr())->GetMutf8(); - } else { - auto intVal = EtsBoxPrimitive::FromCoreType(key.GetPtr())->GetValue(); - key_ = std::to_string(intVal); - } + HandleRecordKey(key); AppendJSONString(val, hasContent); hasContent = true; } while (head.GetPtr() != next.GetPtr() && next.GetPtr() != nullptr); @@ -432,21 +508,29 @@ bool JSONStringifier::SerializeObject(EtsHandle &value) auto desc = value->GetClass()->GetDescriptor(); if (desc == panda_file_items::class_descriptors::BOX_BOOLEAN) { - isSuccessful = SerializeJSONBoxedBoolean(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_DOUBLE) { - isSuccessful = SerializeJSONBoxedDouble(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_FLOAT) { - isSuccessful = SerializeJSONBoxedFloat(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_LONG) { - isSuccessful = SerializeJSONBoxedLong(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_BYTE) { - isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_SHORT) { - isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_CHAR) { - isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + buffer_ += "\"" + HandleNumeric(value) + "\""; + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::BOX_INT) { - isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + buffer_ += HandleNumeric(value); + isSuccessful = true; } else if (desc == panda_file_items::class_descriptors::STRING) { isSuccessful = SerializeJSONString(value); } else if (desc == panda_file_items::class_descriptors::RECORD) { @@ -492,6 +576,7 @@ bool JSONStringifier::SerializeObject(EtsHandle &value) auto retobj = EtsHandle(coro, EtsObject::FromCoreType(ret.GetAs())); coro->ManagedCodeEnd(); { + ScopedManagedCodeThread v(coro); isSuccessful = SerializeJSONRecord(retobj); } coro->ManagedCodeBegin(); diff --git a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h index 2f40433b2caae10358ad89b3e5700ddc1bdf2e50..fb6a25df99a0ba8a85a7a22389d1bfae1a17824e 100644 --- a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h +++ b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h @@ -82,6 +82,9 @@ private: bool SerializeObject(EtsHandle &value); bool HandleField(EtsHandle &obj, EtsField *field, bool &hasContent, PandaUnorderedSet &keys); + bool HandleRecordKey(EtsHandle &key); + template + PandaString HandleNumeric(EtsHandle &value); bool CheckUnsupportedAnnotation(EtsField *field); diff --git a/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonStringifyTest.ets b/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonStringifyTest.ets index 8d371ba4ba97a7565f198104749a2db373b6b0c9..62df0dab2ae8490a87b6e536949f7d5b99fd933e 100644 --- a/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonStringifyTest.ets +++ b/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonStringifyTest.ets @@ -250,6 +250,16 @@ function jsonStringifyRecordWithNullWithSpace(): void { arktest.assertEQ(JSON.stringify(record, undefined, ' '), '{\n "nullable": null\n}') } +function jsonStringifyEmptyRecord(): void { + const record: Record = {} + arktest.assertEQ(JSON.stringify(record), '{}') +} + +function jsonStringifyRecordWithNumericKey(): void { + let record: Record = { "1": 1, 2: "2", 3: true } + arktest.assertEQ(JSON.stringify(record), '{"1":1,"2":"2","3":true}') +} + function jsonStringifyNestedMap(): void { const map = new Map(); let record: Record = new Record(); @@ -315,6 +325,8 @@ function main(): int { suite.addTest('JSON.stringify for Record with array with space', jsonStringifyRecordWithArrayWithSpace); suite.addTest('JSON.stringify for Record with null', jsonStringifyRecordWithNull); suite.addTest('JSON.stringify for Record with null with space', jsonStringifyRecordWithNullWithSpace); + suite.addTest('JSON.stringify for empty Record', jsonStringifyEmptyRecord); + suite.addTest('JSON.stringify for Record with numeric key', jsonStringifyRecordWithNumericKey); suite.addTest('JSON.stringify for nested Map', jsonStringifyNestedMap); suite.addTest('JSON.stringify for cyclic Record', jsonStringifyCyclicRecord); suite.addTest('JSON.stringify for control character', jsonStringifyControlCharacter);