From 9cfad8a564e7d5f5b576067274a29003baa05cec Mon Sep 17 00:00:00 2001 From: lijincheng Date: Sat, 24 May 2025 22:03:22 +0800 Subject: [PATCH] Support new annotations in builtin Json 1.Support @JSONRename @JSONStringifyIgnore and @JSONParseIgnore Issue:https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICAATS Signed-off-by: lijincheng --- .../plugins/ets/runtime/CMakeLists.txt | 1 + .../ets/runtime/ets_libbase_runtime.yaml | 54 ++++ .../ets/runtime/ets_panda_file_items.h | 5 + .../ets/runtime/intrinsics/escompat_JSON.cpp | 142 +++++++++ .../plugins/ets/stdlib/escompat/json.ets | 92 +++++- static_core/plugins/ets/subproject_sources.gn | 1 + .../escompat/JsonAnnotationTest.ets | 270 ++++++++++++++++++ 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp create mode 100644 static_core/plugins/ets/tests/ets_func_tests/escompat/JsonAnnotationTest.ets diff --git a/static_core/plugins/ets/runtime/CMakeLists.txt b/static_core/plugins/ets/runtime/CMakeLists.txt index b915c5b670..2e41458580 100644 --- a/static_core/plugins/ets/runtime/CMakeLists.txt +++ b/static_core/plugins/ets/runtime/CMakeLists.txt @@ -38,6 +38,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/intrinsics/escompat_ArrayBuffer.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_TypedArrays.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_Date.cpp + ${ETS_EXT_SOURCES}/intrinsics/escompat_JSON.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_RegExp.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_taskpool.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_Reflect.cpp diff --git a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml index 54cc602be5..ad6f9fb869 100644 --- a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -1373,6 +1373,60 @@ intrinsics: impl: ark::intrinsics::MinF64 safe_intrinsic: true +################# +# escompat.JSON # +################# + - name: EscompatJSONGetJSONStringifyIgnoreByIdx + space: ets + class_name: escompat.JSONAPI + method_name: getJSONStringifyIgnoreByIdx + static: true + signature: + ret: u1 + args: [ std.core.Class, i64 ] + impl: ark::ets::intrinsics::EscompatJSONGetJSONStringifyIgnoreByIdx + + - name: EscompatJSONGetJSONStringifyIgnoreByName + space: ets + class_name: escompat.JSONAPI + method_name: getJSONStringifyIgnoreByName + static: true + signature: + ret: u1 + args: [ std.core.Class, std.core.String ] + impl: ark::ets::intrinsics::EscompatJSONGetJSONStringifyIgnoreByName + + - name: EscompatJSONGetJSONParseIgnoreFromAnnotation + space: ets + class_name: escompat.JSONAPI + method_name: getJSONParseIgnoreFromAnnotation + static: true + signature: + ret: u1 + args: [ std.core.Class, i64 ] + impl: ark::ets::intrinsics::EscompatJSONGetJSONParseIgnoreFromAnnotation + + - name: EscompatJSONGetJSONRenameByIdx + space: ets + class_name: escompat.JSONAPI + method_name: getJSONRenameByIdx + static: true + signature: + ret: std.core.String + args: [ std.core.Class, i64 ] + impl: ark::ets::intrinsics::EscompatJSONGetJSONRenameByIdx + + - name: EscompatJSONGetJSONRenameByName + space: ets + class_name: escompat.JSONAPI + method_name: getJSONRenameByName + static: true + signature: + ret: std.core.String + args: [ std.core.Class, std.core.String ] + impl: ark::ets::intrinsics::EscompatJSONGetJSONRenameByName + + ################# # escompat.Date # ################# diff --git a/static_core/plugins/ets/runtime/ets_panda_file_items.h b/static_core/plugins/ets/runtime/ets_panda_file_items.h index 4bf9ab0ff1..41393bfe0e 100644 --- a/static_core/plugins/ets/runtime/ets_panda_file_items.h +++ b/static_core/plugins/ets/runtime/ets_panda_file_items.h @@ -226,6 +226,11 @@ static constexpr std::string_view SET = "Lescom static constexpr std::string_view RECORD = "Lescompat/Record;"; static constexpr std::string_view PROCESS = "Lescompat/StdProcess/process;"; +// Json Annotations +static constexpr std::string_view JSON_STRINGIFY_IGNORE = "Lescompat/JSONStringifyIgnore;"; +static constexpr std::string_view JSON_PARSE_IGNORE = "Lescompat/JSONParseIgnore;"; +static constexpr std::string_view JSON_RENAME = "Lescompat/JSONRename;"; + // Annotation for optional parameters static constexpr std::string_view OPTIONAL_PARAMETERS_ANNOTATION = "Lstd/annotations/functions/OptionalParametersAnnotation;"; diff --git a/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp b/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp new file mode 100644 index 0000000000..86c598a602 --- /dev/null +++ b/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2021-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "intrinsics.h" +#include "plugins/ets/runtime/ets_utils.h" + +namespace ark::ets::intrinsics { + +static EtsField *GetInstanceFieldByName(EtsClass *cls, EtsString *name) +{ + auto fieldName = name->GetMutf8(); + auto instanceField = ManglingUtils::GetFieldIDByDisplayName(cls, fieldName); + return instanceField; +} + +EtsBoolean EscompatJSONGetJSONStringifyIgnoreByIdx(EtsClass *cls, EtsLong idx) +{ + EtsField *field = cls->GetFieldByIndex(idx); + bool ret = false; + if (field == nullptr) { + return static_cast(ret); + } + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + fda.EnumerateAnnotations([&pf, &ret](panda_file::File::EntityId annId) { + panda_file::AnnotationDataAccessor ada(pf, annId); + const char *className = utf::Mutf8AsCString(pf.GetStringData(ada.GetClassId()).data); + if (className == panda_file_items::class_descriptors::JSON_STRINGIFY_IGNORE) { + ret = true; + } + }); + return static_cast(ret); +} + +EtsBoolean EscompatJSONGetJSONStringifyIgnoreByName(EtsClass *cls, EtsString *name) +{ + EtsField *field = GetInstanceFieldByName(cls, name); + bool ret = false; + if (field == nullptr) { + return static_cast(ret); + } + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + fda.EnumerateAnnotations([&pf, &ret](panda_file::File::EntityId annId) { + panda_file::AnnotationDataAccessor ada(pf, annId); + const char *className = utf::Mutf8AsCString(pf.GetStringData(ada.GetClassId()).data); + if (className == panda_file_items::class_descriptors::JSON_STRINGIFY_IGNORE) { + ret = true; + } + }); + return static_cast(ret); +} + +EtsBoolean EscompatJSONGetJSONParseIgnoreFromAnnotation(EtsClass *cls, EtsLong idx) +{ + EtsField *field = cls->GetFieldByIndex(idx); + bool ret = false; + if (field == nullptr) { + return static_cast(ret); + } + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + fda.EnumerateAnnotations([&pf, &ret](panda_file::File::EntityId annId) { + panda_file::AnnotationDataAccessor ada(pf, annId); + const char *className = utf::Mutf8AsCString(pf.GetStringData(ada.GetClassId()).data); + if (className == panda_file_items::class_descriptors::JSON_PARSE_IGNORE) { + ret = true; + } + }); + return static_cast(ret); +} + +EtsString *EscompatJSONGetJSONRenameByIdx(EtsClass *cls, EtsLong idx) +{ + auto *thread = ManagedThread::GetCurrent(); + [[maybe_unused]] HandleScope scope(thread); + + EtsField *field = cls->GetFieldByIndex(idx); + VMHandle retStrHandle; + if (field == nullptr) { + return VMHandle(thread, EtsString::CreateNewEmptyString()->GetCoreType()).GetPtr(); + } + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + fda.EnumerateAnnotations([&pf, &retStrHandle, &thread](panda_file::File::EntityId annId) { + panda_file::AnnotationDataAccessor ada(pf, annId); + const char *className = utf::Mutf8AsCString(pf.GetStringData(ada.GetClassId()).data); + if (className == panda_file_items::class_descriptors::JSON_RENAME) { + const auto value = ada.GetElement(0).GetScalarValue(); + const auto id = value.Get(); + auto stringData = pf.GetStringData(id); + retStrHandle = VMHandle( + thread, EtsString::CreateFromMUtf8(reinterpret_cast(stringData.data))->GetCoreType()); + } + }); + return retStrHandle.GetPtr(); +} + +EtsString *EscompatJSONGetJSONRenameByName(EtsClass *cls, EtsString *name) +{ + auto *thread = ManagedThread::GetCurrent(); + [[maybe_unused]] HandleScope scope(thread); + + EtsField *field = GetInstanceFieldByName(cls, name); + VMHandle retStrHandle; + if (field == nullptr) { + return VMHandle(thread, EtsString::CreateNewEmptyString()->GetCoreType()).GetPtr(); + } + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + fda.EnumerateAnnotations([&pf, &retStrHandle, &thread](panda_file::File::EntityId annId) { + panda_file::AnnotationDataAccessor ada(pf, annId); + const char *className = utf::Mutf8AsCString(pf.GetStringData(ada.GetClassId()).data); + if (className == panda_file_items::class_descriptors::JSON_RENAME) { + const auto value = ada.GetElement(0).GetScalarValue(); + const auto id = value.Get(); + auto stringData = pf.GetStringData(id); + retStrHandle = VMHandle( + thread, EtsString::CreateFromMUtf8(reinterpret_cast(stringData.data))->GetCoreType()); + } + }); + return retStrHandle.GetPtr(); +} + +} // namespace ark::ets::intrinsics \ No newline at end of file diff --git a/static_core/plugins/ets/stdlib/escompat/json.ets b/static_core/plugins/ets/stdlib/escompat/json.ets index e5f93d3117..28615bfcc9 100644 --- a/static_core/plugins/ets/stdlib/escompat/json.ets +++ b/static_core/plugins/ets/stdlib/escompat/json.ets @@ -32,6 +32,31 @@ export class JsonReplacerTemp implements JsonReplacer { } } +export @interface JSONRename { + newName: string +} + +export @interface JSONStringifyIgnore { +} + +export @interface JSONParseIgnore { +} + +class JSONAPI { + /* + JSON intrinsics + */ + native static getJSONStringifyIgnoreByIdx(cls: Class, idx: long): boolean + + native static getJSONStringifyIgnoreByName(cls: Class, name: string): boolean + + native static getJSONParseIgnoreFromAnnotation(cls: Class, idx: long): boolean + + native static getJSONRenameByIdx(cls: Class, idx: long): string | undefined + + native static getJSONRenameByName(cls: Class, name: string): string | undefined +} + export class JSON { /** * Converts byte to JSON format @@ -980,8 +1005,20 @@ class JSONWriter { return fieldDumped } + private checkSameRename(field: Field, fields: Array<[Field, Value]>): boolean { + return fields.some((value: [Field, Value], index: number, array:Array<[Field, Value]>) + => {return value[0].getName() == field.getName()}); + } + + private findKeyIndex(field: Field, fields: Array<[Field, Value]>): number { + return fields.findIndex((value: [Field, Value], index: number, array:Array<[Field, Value]>) + => {return value[0].getName() == field.getName()}); + } + private getWritableFields(classType: ClassType, classValue: ClassValue): Array<[Field, Value]> { const writableFields = new Array<[Field, Value]>() + let hasJsonIgnore: boolean = false + let fieldRename : string | undefined = undefined if (this.useFieldsFilter) { for (let fieldIdx = 0; fieldIdx < this.fieldsFilter.length; fieldIdx++) { @@ -991,9 +1028,21 @@ class JSONWriter { } const field = classType.getFieldByName(fieldName) - if (field.isStatic()) { + hasJsonIgnore = JSONAPI.getJSONStringifyIgnoreByName(classType.cls, fieldName); + if (hasJsonIgnore || field.isStatic()) { continue } + fieldRename = JSONAPI.getJSONRenameByName(classType.cls, fieldName); + if (fieldRename != undefined) { + if (classType.hasField(fieldRename)) { + throw new Error("Cannot rename " + fieldRename + " in keys of " + classType.getName()) + } else { + field.name = fieldRename + } + if (this.checkSameRename(field, writableFields)) { + throw new Error("Cannot double rename " + field.getName() + " in keys of " + classType.getName()) + } + } const fieldValue = classValue.getFieldByName(fieldName) @@ -1004,14 +1053,31 @@ class JSONWriter { const fieldsCount = classValue.getFieldsNum() for (let fieldIdx = 0; fieldIdx < fieldsCount; fieldIdx++) { const field = classType.getField(fieldIdx) - if (field.isStatic()) { + hasJsonIgnore = JSONAPI.getJSONStringifyIgnoreByIdx(classType.cls, fieldIdx); + if (hasJsonIgnore || field.isStatic()) { continue } + const index: number = this.findKeyIndex(field, writableFields) + fieldRename = JSONAPI.getJSONRenameByIdx(classType.cls, fieldIdx); + if (fieldRename != undefined) { + if (classType.hasField(fieldRename)) { + throw new Error("Cannot rename " + fieldRename + " in keys of " + classType.getName()) + } else { + field.name = fieldRename + } + if (this.checkSameRename(field, writableFields)) { + throw new Error("Cannot double rename " + field.getName() + " in keys of " + classType.getName()) + } + } const fieldValue = classValue.getField(fieldIdx) const fieldTypeValuePair: [Field, Value] = [field, fieldValue] - writableFields.push(fieldTypeValuePair) + if (fieldRename != undefined || index == -1) { + writableFields.push(fieldTypeValuePair) + } else { + writableFields[index] = fieldTypeValuePair + } } } @@ -1662,16 +1728,30 @@ class JSONValueParser { let obj = classType.make() let classVal = Value.of(obj) as ClassValue + let hasJsonParseIgnore: boolean = false + let hasJsonStringifyIgnore: boolean = false const jsonObjFields = jsonObj.getFields() for (let field_num = 0; field_num < classType.getFieldsNum(); field_num++) { let classField = classType.getField(field_num) - if (classField.isStatic()) { + hasJsonParseIgnore = JSONAPI.getJSONParseIgnoreFromAnnotation(classType.cls, field_num); + if (hasJsonParseIgnore || classField.isStatic()) { continue } - const jsonFieldVal = jsonObjFields.get(classField.getName()) + let jsonFieldVal = jsonObjFields.get(classField.getName()) if (jsonFieldVal === undefined) { - throw new Error("Cannot find " + classField.getName() + " in keys of " + classType.getName()) + hasJsonStringifyIgnore = JSONAPI.getJSONStringifyIgnoreByIdx(classType.cls, field_num); + if (hasJsonStringifyIgnore) { + continue + } + let fieldRename = JSONAPI.getJSONRenameByIdx(classType.cls, field_num); + if (fieldRename == undefined) { + throw new Error("Cannot find " + classField.getName() + " in keys of " + classType.getName()) + } + jsonFieldVal = jsonObjFields.get(fieldRename) + if (jsonFieldVal === undefined) { + throw new Error("Cannot find rename " + fieldRename + " in keys of " + classType.getName()) + } } const classFieldVal = this.parse(jsonFieldVal, classField.getType()) diff --git a/static_core/plugins/ets/subproject_sources.gn b/static_core/plugins/ets/subproject_sources.gn index e49fe513fc..278ef1c68d 100644 --- a/static_core/plugins/ets/subproject_sources.gn +++ b/static_core/plugins/ets/subproject_sources.gn @@ -121,6 +121,7 @@ srcs_runtime = [ "runtime/intrinsics/compiler_intrinsics.cpp", "runtime/intrinsics/debugger_api.cpp", "runtime/intrinsics/escompat_Date.cpp", + "runtime/intrinsics/escompat_JSON.cpp", "runtime/intrinsics/escompat_RegExp.cpp", "runtime/intrinsics/escompat_taskpool.cpp", "runtime/intrinsics/escompat_Reflect.cpp", diff --git a/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonAnnotationTest.ets b/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonAnnotationTest.ets new file mode 100644 index 0000000000..3b470d5916 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_func_tests/escompat/JsonAnnotationTest.ets @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const INT8_MAX: number = Byte.MAX_VALUE; //127 +const INT8_MIN: number = Byte.MIN_VALUE; //-128 + +class TestClass1 { + @JSONRename("__backing_user") + user: string = "" + @JSONStringifyIgnore + @JSONParseIgnore + password: number = 0 + @JSONParseIgnore + nickname: string = "" + + setPassword(pwd: number) { + this.password = pwd + } + setUserName(name: string) { + this.user = name + } + setNickName(name: string) { + this.nickname = name + } +} + +class TestClass2 { + @JSONRename("nickname") + user: string = "" + password: number = 0 + nickname: string = "" +} + +class TestClass3 { + @JSONRename("nickname1") + user: string = "" + password: number = 0 + @JSONRename("nickname1") + nickname: string = "" +} + +class TestClass4 { + @JSONRename("nickname1") + user: string = "" + password: number = 0 + nickname: string = "" +} + +class TestClass5 { + name1: string = "class5" +} + +class TestClass6 extends TestClass5 { + name1: string = "class6" + nickname: string = "class6_nickname" +} + +class TestClass7 extends TestClass5 { + @JSONRename("name2") + name1: string = "class7" + nickname: string = "class7_nickname" +} + +class TestClass8 { + @JSONRename("name8") + name1: string = "TestClass8" + nickname: string = "class8_nickname" +} + +class TestClass9 { + name1: string = "TestClass9" + nickname: TestClass8 = new TestClass8() +} + +class TestClass10 extends TestClass8 { + @JSONRename("name8") + name10: string = "TestClass10" +} + +class TestClass11 extends TestClass8 { + @JSONRename("name1") + name11: string = "TestClass11" +} + +class TestClass12 extends TestClass8 { + @JSONRename("name12test") + name12: string = "TestClass12" +} + +class TestClass13 { + @JSONRename("name8") + name1: string = "TestClass13" + nickname: string = "class13_nickname" + nickname2: string = "class13_nickname2" + nickname3: string = "class13_nickname3" +} + +class TestClass14 extends TestClass13 { + @JSONRename("name14test") + name10: string = "TestClass14" + @JSONRename("name9") + name1: string = "TestClass14" + @JSONRename("nicknametest") + nickname: string = "class14_nickname" + nickname3: string = "class14_nickname3" +} + +class TestClass15 { + @JSONRename("record15") + record: Record = new Record() + @JSONRename("map15") + map: Map = new Map() + @JSONRename("nestedRecord15") + nestedRecord: Record = new Record() + @JSONStringifyIgnore + i8Array: Int8Array = Int8Array.of(INT8_MAX,INT8_MIN,100,INT8_MIN + 1) + @JSONRename("fixArray15") + fixArray: FixedArray = ["\a", "\b"] +} + +function jsonStringify() { + let obj: TestClass1 = new TestClass1() + obj.setPassword(123456) + obj.setUserName("tester") + obj.setNickName("test123") + let json = JSON.stringify(obj) + arktest.assertEQ(json, '{"__backing_user":"tester","nickname":"test123"}') + let jsonObj: TestClass1 = JSON.parse(json, Type.of(obj)) as TestClass1 + arktest.assertEQ(Object.keys(jsonObj).length, 3) + arktest.assertEQ(jsonObj?.user, "tester") + arktest.assertEQ(jsonObj?.password, 0) + arktest.assertEQ(jsonObj?.nickname, "") +} + +function checkThrow(fn: () => void throws, expectMsg: string) { + arktest.expectThrow(fn, (e) => { + if (e.message === expectMsg) { + return true + } else { + return e.message + } + }) +} + +function jsonThrowWithRename() { + let obj: TestClass2 = new TestClass2() + obj.password = 123456 + obj.user = "tester" + obj.nickname = "test123" + const expectMsg = "Cannot rename nickname in keys of JsonAnnotationTest.TestClass2" + checkThrow(() => {JSON.stringify(obj)}, expectMsg) +} + +function jsonThrowWithDoubleRename() { + let obj: TestClass3 = new TestClass3() + obj.password = 123456 + obj.user = "tester" + obj.nickname = "test123" + const expectMsg = "Cannot double rename nickname1 in keys of JsonAnnotationTest.TestClass3" + checkThrow(() => {JSON.stringify(obj)}, expectMsg) +} + +function jsonParseThrow() { + let obj: TestClass4 = new TestClass4() + let json1: string = '{"user":"tester","nickname":"test123"}' + let json2: string = '{"nickname2":"tester","nickname":"test123"}' + let json3: string = '{"nickname1":"tester","password":123456,"nickname":"test123"}' + const expectMsg1 = "Cannot find password in keys of JsonAnnotationTest.TestClass4" + const expectMsg2 = "Cannot find rename nickname1 in keys of JsonAnnotationTest.TestClass4" + checkThrow(() => {JSON.parse(json1, Type.of(obj))}, expectMsg1) + checkThrow(() => {JSON.parse(json2, Type.of(obj))}, expectMsg2) + let jsonObj: TestClass4 = JSON.parse(json3, Type.of(obj)) as TestClass4 + arktest.assertEQ(Object.keys(jsonObj).length, 3) + arktest.assertEQ(jsonObj?.user, "tester") + arktest.assertEQ(jsonObj?.password, 123456) + arktest.assertEQ(jsonObj?.nickname, "test123") + arktest.assertEQ(Object.hasOwn(jsonObj, "nickname1"), false) +} + +function jsonStringifyExtend() { + let obj6: TestClass6 = new TestClass6() + let obj7: TestClass7 = new TestClass7() + let json6 = JSON.stringify(obj6) + arktest.assertEQ(json6, '{"name1":"class6","nickname":"class6_nickname"}') + let json7 = JSON.stringify(obj7) + /** + * Attention: after the overriding fields problem is solved, we should only have field in subclass + * the expected output should be "{"name2":"class7","nickname":"class7_nickname"}" + */ + arktest.assertEQ(json7, '{"name1":"class5","name2":"class7","nickname":"class7_nickname"}') +} + +function jsonStringifyNest() { + let obj9: TestClass9 = new TestClass9() + let obj8: TestClass8 = new TestClass8() + let json9 = JSON.stringify(obj9) + arktest.assertEQ(json9, '{"name1":"TestClass9","nickname":{"name8":"TestClass8","nickname":"class8_nickname"}}') + let jsonObj: TestClass9 = JSON.parse(json9, Type.from()) as TestClass9 + arktest.assertEQ(Object.keys(jsonObj).length, 2) + arktest.assertEQ(jsonObj.name1, "TestClass9") + arktest.assertEQ(Type.of(jsonObj.nickname).equals(Type.of(obj8)), true) + let obj3 = (jsonObj.nickname) as TestClass8 + arktest.assertEQ(Object.hasOwn(obj3, "name8"), false) + arktest.assertEQ(obj3.name1, "TestClass8") + arktest.assertEQ(obj3.nickname, "class8_nickname") +} + + +function jsonThrowWithExtends() { + let obj10: TestClass10 = new TestClass10() + let obj11: TestClass11 = new TestClass11() + const expectMsg1 = "Cannot double rename name8 in keys of JsonAnnotationTest.TestClass10" + const expectMsg2 = "Cannot rename name1 in keys of JsonAnnotationTest.TestClass11" + checkThrow(() => {JSON.stringify(obj10)}, expectMsg1) + checkThrow(() => {JSON.stringify(obj11)}, expectMsg2) +} + +function jsonTestWithExtends() { + let obj: TestClass14 = new TestClass14() + /** + * Attention: after the overriding fields problem is solved, we should only have field in subclass + * the expected output may be only "{"nickname2":"class13_nickname2","name14test":"TestClass14" + * ,"name9":"TestClass14","nicknametest":"class14_nickname"}" + */ + const targetOutput = '{"name8":"TestClass13","nickname":"class13_nickname","nickname2":' + + '"class13_nickname2","nickname3":"class14_nickname3","name14test":"TestClass14","' + + 'name9":"TestClass14","nicknametest":"class14_nickname"}' + let json = JSON.stringify(obj) + arktest.assertEQ(json, targetOutput) +} + +function jsonTestWithTypes() { + let obj: TestClass15 = new TestClass15() + obj.record["name"] = "arkts" + obj.record["version"] = 1 + obj.map.set("key1", "value1") + obj.nestedRecord = { "name": "arkts", "version": 1} + const nest: Record = { "desc": "nested" } + obj.nestedRecord["nested"] = nest + let json = JSON.stringify(obj) + const targetOutput = '{"record15":{"name":"arkts","version":1},"map15":{},' + + '"nestedRecord15":{"name":"arkts","version":1,"nested":{"desc":"nested"}},"fixArray15":["a","\\b"]}' + arktest.assertEQ(json, targetOutput) +} + +function main(): int { + const suite = new arktest.ArkTestsuite("Json stringify && parse for annotation tests ") + suite.addTest("JSON.stringify test with annotaion", jsonStringify) + suite.addTest("JSON throw test with annotaion", jsonThrowWithRename) + suite.addTest("JSON throw test with double annotaion", jsonThrowWithDoubleRename) + suite.addTest("JSON parse throw test", jsonParseThrow) + suite.addTest("JSON stringify extends test", jsonStringifyExtend) + suite.addTest("JSON stringify with nest field test", jsonStringifyNest) + suite.addTest("JSON throw test with extends", jsonThrowWithExtends) + suite.addTest("JSON test with extends", jsonTestWithExtends) + suite.addTest("JSON test with different types", jsonTestWithTypes) + return suite.run() +} -- Gitee