diff --git a/ballerina/json_api.bal b/ballerina/json_api.bal index efc9555..f6c165c 100644 --- a/ballerina/json_api.bal +++ b/ballerina/json_api.bal @@ -13,7 +13,6 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - import ballerina/jballerina.java; # Convert value of type `json` to subtype of `anydata`. @@ -56,16 +55,20 @@ 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) +public isolated function toJson(anydata v) returns json|Error = @java:Method {'class: "io.ballerina.lib.data.jsondata.json.Native"} external; # Represent the options that can be used to modify the behaviour of the projection. # -# + allowDataProjection - enable or disable projection +# + allowDataProjection - Enable or disable projection. public type Options record { - boolean nilAsOptionalField = false; - boolean absentAsNilableType = false; -}|false; + record { + # If true, nil values will be considered as optional fields in the projection. + boolean nilAsOptionalField = false; + # If true, absent fields will be considered as nilable types in the projection. + boolean absentAsNilableType = false; + }|false allowDataProjection = {}; +}; # Represents the error type of the ballerina/data.jsondata module. This error type represents any error that can occur # during the execution of jsondata APIs. diff --git a/ballerina/tests/from_json_with_disable_projection_option.bal b/ballerina/tests/from_json_with_disable_projection_option.bal index 648d5ee..1d2d565 100644 --- a/ballerina/tests/from_json_with_disable_projection_option.bal +++ b/ballerina/tests/from_json_with_disable_projection_option.bal @@ -16,7 +16,7 @@ import ballerina/test; -const options = false; +const options = {allowDataProjection: false}; @test:Config isolated function testDisableDataProjectionInArrayTypeForParseString() { diff --git a/ballerina/tests/from_json_with_projection_options_test.bal b/ballerina/tests/from_json_with_projection_options_test.bal index d39c19d..44c8559 100644 --- a/ballerina/tests/from_json_with_projection_options_test.bal +++ b/ballerina/tests/from_json_with_projection_options_test.bal @@ -15,15 +15,38 @@ // under the License. import ballerina/io; -import ballerina/test; import ballerina/lang.value; +import ballerina/test; const PATH = "tests/resources/"; -const options1 = {nilAsOptionalField: true, absentAsNilableType: false}; -const options2 = {nilAsOptionalField: false, absentAsNilableType: true}; -const options3 = {nilAsOptionalField: true, absentAsNilableType: true}; -const options4 = {nilAsOptionalField: false, absentAsNilableType: false}; +const options1 = { + allowDataProjection: { + nilAsOptionalField: true, + absentAsNilableType: false + } +}; + +const options2 = { + allowDataProjection: { + nilAsOptionalField: false, + absentAsNilableType: true + } +}; + +const options3 = { + allowDataProjection: { + nilAsOptionalField: true, + absentAsNilableType: true + } +}; + +const options4 = { + allowDataProjection: { + nilAsOptionalField: false, + absentAsNilableType: false + } +}; type Sales record {| @Name { @@ -251,7 +274,7 @@ type ResponseEcom record { isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseString() returns error? { string jsonData = check io:fileReadString(PATH + "product_list_response.json"); ResponseEcom response = check parseString(jsonData, options3); - + test:assertEquals(response.status, "success"); test:assertEquals(response.data.products[0].length(), 6); test:assertEquals(response.data.products[0].id, 1); @@ -313,7 +336,7 @@ isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseString2() string jsonData = check io:fileReadString(PATH + "product_list_response.json"); json productJson = check value:fromJsonString(jsonData); ResponseEcom response = check parseAsType(productJson, options3); - + test:assertEquals(response.status, "success"); test:assertEquals(response.data.products[0].length(), 6); test:assertEquals(response.data.products[0].id, 1); diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java index 98b7f40..48c71a5 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/io/DataReaderTask.java @@ -24,7 +24,9 @@ import io.ballerina.runtime.api.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; import java.io.InputStreamReader; @@ -44,9 +46,10 @@ public class DataReaderTask implements Runnable { private final BObject iteratorObj; private final Future future; private final BTypedesc typed; - private final Object options; + private final BMap options; - public DataReaderTask(Environment env, BObject iteratorObj, Future future, BTypedesc typed, Object options) { + public DataReaderTask(Environment env, BObject iteratorObj, Future future, BTypedesc typed, + BMap options) { this.env = env; this.iteratorObj = iteratorObj; this.future = future; diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java index dee7cbd..0e1ee37 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonParser.java @@ -66,7 +66,7 @@ public class JsonParser { * @return JSON structure * @throws BError for any parsing error */ - public static Object parse(Reader reader, Object options, Type type) + public static Object parse(Reader reader, BMap options, Type type) throws BError { StateMachine sm = tlStateMachine.get(); try { @@ -197,9 +197,8 @@ private void processLocation(char ch) { } } - public Object execute(Reader reader, Object options, Type type) throws BError { + public Object execute(Reader reader, BMap options, Type type) throws BError { switch (type.getTag()) { - // TODO: Handle readonly and singleton type as expType. case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) type; expectedTypes.push(recordType); @@ -249,10 +248,15 @@ public Object execute(Reader reader, Object options, Type type) throws BError { default -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type); } - if (options instanceof BMap) { - allowDataProjection = true; - absentAsNilableType = (Boolean) ((BMap) options).get(Constants.ABSENT_AS_NILABLE_TYPE); - nilAsOptionalField = (Boolean) ((BMap) options).get(Constants.NIL_AS_OPTIONAL_FIELD); + Object allowDataProjection = options.get(Constants.ALLOW_DATA_PROJECTION); + if (allowDataProjection instanceof Boolean) { + this.allowDataProjection = false; + } else if (allowDataProjection instanceof BMap) { + this.allowDataProjection = true; + this.absentAsNilableType = + (Boolean) ((BMap) allowDataProjection).get(Constants.ABSENT_AS_NILABLE_TYPE); + this.nilAsOptionalField = + (Boolean) ((BMap) allowDataProjection).get(Constants.NIL_AS_OPTIONAL_FIELD); } State currentState = DOC_START_STATE; diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java index 3526ad3..127ded3 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/json/JsonTraverse.java @@ -57,13 +57,18 @@ public class JsonTraverse { private static final ThreadLocal tlJsonTree = ThreadLocal.withInitial(JsonTree::new); - public static Object traverse(Object json, Object options, Type type) { + public static Object traverse(Object json, BMap options, Type type) { JsonTree jsonTree = tlJsonTree.get(); try { - if (options instanceof BMap) { + Object allowDataProjection = options.get(Constants.ALLOW_DATA_PROJECTION); + if (allowDataProjection instanceof Boolean) { + jsonTree.allowDataProjection = false; + } else if (allowDataProjection instanceof BMap) { jsonTree.allowDataProjection = true; - jsonTree.absentAsNilableType = (Boolean) ((BMap) options).get(Constants.ABSENT_AS_NILABLE_TYPE); - jsonTree.nilAsOptionalField = (Boolean) ((BMap) options).get(Constants.NIL_AS_OPTIONAL_FIELD); + jsonTree.absentAsNilableType = + (Boolean) ((BMap) allowDataProjection).get(Constants.ABSENT_AS_NILABLE_TYPE); + jsonTree.nilAsOptionalField = + (Boolean) ((BMap) allowDataProjection).get(Constants.NIL_AS_OPTIONAL_FIELD); } return jsonTree.traverseJson(json, type); } catch (BError e) { 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 e054a6c..23a9d3d 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 @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.utils.JsonUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; @@ -41,7 +42,7 @@ */ public class Native { - public static Object parseAsType(Object json, Object options, BTypedesc typed) { + public static Object parseAsType(Object json, BMap options, BTypedesc typed) { try { return JsonTraverse.traverse(json, options, typed.getDescribingType()); } catch (BError e) { @@ -49,7 +50,7 @@ public static Object parseAsType(Object json, Object options, BTypedesc typed) { } } - public static Object parseString(BString json, Object options, BTypedesc typed) { + public static Object parseString(BString json, BMap options, BTypedesc typed) { try { return JsonParser.parse(new StringReader(json.getValue()), options, typed.getDescribingType()); } catch (BError e) { @@ -57,7 +58,7 @@ public static Object parseString(BString json, Object options, BTypedesc typed) } } - public static Object parseBytes(BArray json, Object options, BTypedesc typed) { + public static Object parseBytes(BArray json, BMap options, BTypedesc typed) { try { byte[] bytes = json.getBytes(); return JsonParser.parse(new InputStreamReader(new ByteArrayInputStream(bytes)), options, @@ -67,7 +68,7 @@ public static Object parseBytes(BArray json, Object options, BTypedesc typed) { } } - public static Object parseStream(Environment env, BStream json, Object options, BTypedesc typed) { + public static Object parseStream(Environment env, BStream json, BMap options, BTypedesc typed) { final BObject iteratorObj = json.getIteratorObj(); final Future future = env.markAsync(); DataReaderTask task = new DataReaderTask(env, iteratorObj, future, typed, options); diff --git a/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java index 8e4ca39..f2d37ea 100644 --- a/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/lib/data/jsondata/utils/Constants.java @@ -36,6 +36,7 @@ public class Constants { public static final String FIELD = "$field$."; public static final String FIELD_REGEX = "\\$field\\$\\."; public static final String NAME = "Name"; + public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); public static final BString NIL_AS_OPTIONAL_FIELD = StringUtils.fromString("nilAsOptionalField"); public static final BString ABSENT_AS_NILABLE_TYPE = StringUtils.fromString("absentAsNilableType"); public static final String NULL_VALUE = "null";