From 8647dce13a0c214b0d3ac6b9b2043a9bda38c12c Mon Sep 17 00:00:00 2001 From: MaryamZi Date: Mon, 11 Nov 2024 17:30:00 +0530 Subject: [PATCH] Fix toJson for cyclic values --- ballerina/json_api.bal | 14 +----- ballerina/tests/to_json_test.bal | 46 +++++++++++++++++++ .../lib/data/jsondata/json/Native.java | 42 +++++++++++++++++ .../jsondata/utils/DiagnosticErrorCode.java | 3 +- .../src/main/resources/json_error.properties | 3 ++ 5 files changed, 95 insertions(+), 13 deletions(-) diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index d88fb8e..e81e808 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -55,18 +55,8 @@ public isolated function parseStream(stream s, Options options = # # + v - Source anydata value # + return - representation of `v` as value of type json -public isolated function toJson(anydata v) returns json { - if v is anydata[] { - return from anydata elem in v - select toJson(elem); - } else if v is map { - return map from var [key, feild] in v.entries() - select [getNameAnnotation(v, key), toJson(feild)]; - } - return v.toJson(); -} - -isolated function getNameAnnotation(map data, string key) returns string = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; +public isolated function toJson(anydata v) returns json = + @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Prettifies a `json` value to print it. # diff --git a/ballerina/tests/to_json_test.bal b/ballerina/tests/to_json_test.bal index 9e274d5..538951d 100644 --- a/ballerina/tests/to_json_test.bal +++ b/ballerina/tests/to_json_test.bal @@ -173,3 +173,49 @@ function testToJsonWithNameANnotation() { test:assertTrue(j2 is json); test:assertEquals(j2, out2); } + +type TestRecord4 record {| + @Name { + value: "a-o" + } + string a; + @Name { + value: "b-o" + } + string b; + int c; + TestRecord4[] d; +|}; + +@test:Config +function testToJsonWithCyclicValues() { + json[] v1 = []; + v1.push(v1); + json|error r1 = trap toJsonWithCyclicValues(v1); + test:assertTrue(r1 is error); + error r1Err = r1; + test:assertEquals("the value has a cyclic reference", r1Err.message()); + + map v2 = {}; + v2["val"] = v2; + json|error r2 = trap toJsonWithCyclicValues(v2); + test:assertTrue(r2 is error); + error r2Err = r2; + test:assertEquals("the value has a cyclic reference", r2Err.message()); + + TestRecord4 v3 = { + a: "a-v", + b: "b-v", + c: 1, + d: [] + }; + v3.d.push(v3); + json|error r3 = trap toJsonWithCyclicValues(v3); + test:assertTrue(r3 is error); + error r3Err = r3; + test:assertEquals("the value has a cyclic reference", r3Err.message()); +} + +function toJsonWithCyclicValues(anydata val) returns json { + return toJson(val); +} diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java index 017a2ff..e8bdd9e 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/Native.java @@ -21,9 +21,15 @@ import io.ballerina.lib.data.jsondata.io.DataReaderTask; import io.ballerina.lib.data.jsondata.io.DataReaderThreadPool; import io.ballerina.lib.data.jsondata.utils.Constants; +import io.ballerina.lib.data.jsondata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.jsondata.utils.DiagnosticLog; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Future; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; @@ -36,7 +42,9 @@ import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.io.StringReader; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import static io.ballerina.lib.data.jsondata.json.JsonCreator.getModifiedName; import static io.ballerina.lib.data.jsondata.utils.DataUtils.unescapeIdentifier; @@ -82,6 +90,40 @@ public static Object parseStream(Environment env, BStream json, BMap()); + } + + public static Object toJson(Object value, Set visitedValues) { + if (!visitedValues.add(value)) { + throw DiagnosticLog.error(DiagnosticErrorCode.CYCLIC_REFERENCE); + } + + if (value instanceof BArray listValue) { + int length = (int) listValue.getLength(); + Object[] convertedValues = new Object[length]; + for (int i = 0; i < length; i++) { + convertedValues[i] = toJson(listValue.get(i), visitedValues); + } + return ValueCreator.createArrayValue(convertedValues, PredefinedTypes.TYPE_JSON_ARRAY); + } + + if (value instanceof BMap) { + BMap mapValue = (BMap) value; + BMap jsonObject = + ValueCreator.createMapValue(TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); + + for (BString entryKey : mapValue.getKeys()) { + Object entryValue = mapValue.get(entryKey); + jsonObject.put(getNameAnnotation(mapValue, entryKey), toJson(entryValue, visitedValues)); + } + + return jsonObject; + } + + return JsonUtils.convertToJson(value); + } + public static BString getNameAnnotation(BMap value, BString key) { if (!(value.getType() instanceof RecordType recordType)) { return key; diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java index de34ddb..30214a3 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/DiagnosticErrorCode.java @@ -36,7 +36,8 @@ public enum DiagnosticErrorCode { INVALID_TYPE_FOR_FIELD("JSON_ERROR_009", "invalid.type.for.field"), DUPLICATE_FIELD("JSON_ERROR_010", "duplicate.field"), CANNOT_CONVERT_TO_EXPECTED_TYPE("JSON_ERROR_011", "cannot.convert.to.expected.type"), - UNDEFINED_FIELD("JSON_ERROR_012", "undefined.field"); + UNDEFINED_FIELD("JSON_ERROR_012", "undefined.field"), + CYCLIC_REFERENCE("JSON_ERROR_013", "cyclic.reference"); String diagnosticId; String messageKey; diff --git a/native/src/main/resources/json_error.properties b/native/src/main/resources/json_error.properties index 3db6b8b..fbbce3e 100644 --- a/native/src/main/resources/json_error.properties +++ b/native/src/main/resources/json_error.properties @@ -55,3 +55,6 @@ error.cannot.convert.to.expected.type=\ error.undefined.field=\ undefined field ''{0}'' + +error.cyclic.reference=\ + the value has a cyclic reference