From e42ed095eb2e2018435b9e6b7dde826e6ce11b71 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Wed, 11 Dec 2024 11:51:41 +0530 Subject: [PATCH 1/5] Support xsi:nil Attribute --- ballerina/tests/xml_to_json_test.bal | 13 +++++++++++++ .../io/ballerina/stdlib/xmldata/XmlToJson.java | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ballerina/tests/xml_to_json_test.bal b/ballerina/tests/xml_to_json_test.bal index aaa246af..af07489e 100644 --- a/ballerina/tests/xml_to_json_test.bal +++ b/ballerina/tests/xml_to_json_test.bal @@ -730,3 +730,16 @@ function testToJsonComplexXmlElementWithBackSlash() returns Error? { }; test:assertEquals(j, expectedOutput, msg = "testToJsonComplexXmlElement result incorrect"); } + +@test:Config { + groups: ["toJson"] +} +function testToJsonWithNilElementAndWithoutPreserveNS() returns Error? { + xml x1 = xml `Sherlock Holmes +
+ + English +
`; + json j = check toJson(x, {preserveNamespaces: false}); + test:assertEquals(j, {"name":"Sherlock Holmes", "details":{"author":null, "language":"English"}}, msg = "testToJsonWithNilElement result incorrect"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java index 9db49028..73cf899b 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java @@ -71,6 +71,8 @@ public class XmlToJson { private static final String EMPTY_STRING = ""; public static final int NS_PREFIX_BEGIN_INDEX = BXmlItem.XMLNS_NS_URI_PREFIX.length(); private static final String COLON = ":"; + public static final String XMLSCHEMA_INSTANCE_NIL = "{http://www.w3.org/2001/XMLSchema-instance}nil"; + public static final BString BSTRING_XMLSCHEMA_INSTANCE_NIL = fromString(XMLSCHEMA_INSTANCE_NIL); /** * Converts an XML to the corresponding JSON representation. @@ -190,7 +192,13 @@ private static Object convertElement(BXmlItem xmlItem, String attributePrefix, children = convertToArray(fieldType, children); } fieldDetail.setParentArrayName(fieldName); - return insertDataToMap(childrenData, children, rootNode, fieldName, fieldType, fieldDetail); + boolean nilValue = false; + // If the children is null and has `{http://www.w3.org/2001/XMLSchema-instance}nil` attribute set to true, + // then it is a nil value + if (children == null && attributeMap.containsKey(BSTRING_XMLSCHEMA_INSTANCE_NIL)) { + nilValue = Boolean.parseBoolean(attributeMap.get(BSTRING_XMLSCHEMA_INSTANCE_NIL).getValue()); + } + return insertDataToMap(childrenData, children, rootNode, fieldName, fieldType, fieldDetail, nilValue); } private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attributePrefix, @@ -224,7 +232,7 @@ private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attr @SuppressWarnings("unchecked") private static BMap insertDataToMap(BMap childrenData, Object children, BMap rootNode, String keyValue, - Type fieldType, FieldDetails fieldDetails) throws Exception { + Type fieldType, FieldDetails fieldDetails, boolean nilValue) throws Exception { if (childrenData.size() > 0) { if (children instanceof BMap) { BMap data = (BMap) children; @@ -248,9 +256,9 @@ private static BMap insertDataToMap(BMap child put(rootNode, keyValue, children); } else if (children == null) { if (fieldType instanceof ReferenceType && TypeUtils.getReferredType(fieldType) instanceof RecordType) { - put(rootNode, keyValue, ValueCreator.createMapValue(Constants.JSON_MAP_TYPE)); + put(rootNode, keyValue, nilValue ? null : ValueCreator.createMapValue(Constants.JSON_MAP_TYPE)); } else { - putAsFieldTypes(rootNode, keyValue, EMPTY_STRING, fieldType, fieldDetails); + putAsFieldTypes(rootNode, keyValue, nilValue ? null : EMPTY_STRING, fieldType, fieldDetails); } } else if (children instanceof BArray) { put(rootNode, keyValue, children); From 0f451dd849c207e663ffe319b81c93a88db5dd23 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Wed, 11 Dec 2024 12:04:19 +0530 Subject: [PATCH 2/5] [Automated] Update native jar versions in toml files --- ballerina/Ballerina.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 0224a1a9..7c34dced 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,13 +1,13 @@ [package] org = "ballerina" name = "xmldata" -version = "2.8.1" +version = "2.9.0" authors = ["Ballerina"] keywords = ["xml", "json"] repository = "https://github.com/ballerina-platform/module-ballerina-xmldata" icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.10.0" +distribution = "2201.11.0" [platform.java21] graalvmCompatible = true @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "xmldata-native" -version = "2.8.1" +version = "2.9.0" path = "../native/build/libs/xmldata-native-2.9.0-SNAPSHOT.jar" From ec5fa61a21d551bffeb954d281ac5adfb65a838c Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Wed, 11 Dec 2024 12:05:54 +0530 Subject: [PATCH 3/5] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d406f0d1..c60813a6 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.11.0-20241112-214900-6b80ab87" +distribution-version = "2201.11.0-20241209-162400-0c015833" [[package]] org = "ballerina" @@ -67,7 +67,7 @@ modules = [ [[package]] org = "ballerina" name = "xmldata" -version = "2.8.1" +version = "2.9.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} From 3c69c0ca33af2bc563323c0c39f745b63368f41f Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Wed, 11 Dec 2024 16:17:50 +0530 Subject: [PATCH 4/5] Support xsi:nil Attribute in xml to json/record conversion --- ballerina/tests/from_xml_test.bal | 62 +++++++++++++++++++ ballerina/tests/xml_from_json_test.bal | 10 +++ ballerina/tests/xml_to_json_test.bal | 16 ++++- .../ballerina/stdlib/xmldata/XmlToJson.java | 5 +- 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/ballerina/tests/from_xml_test.bal b/ballerina/tests/from_xml_test.bal index 276a2485..cfe61828 100644 --- a/ballerina/tests/from_xml_test.bal +++ b/ballerina/tests/from_xml_test.bal @@ -1294,3 +1294,65 @@ isolated function testFromXmlWithNameAnnotation() returns error? { Appointments result = check fromXml(xmlPayload); test:assertEquals(result, expected, msg = "testFromXmlWithNameAnnotation result incorrect"); } + +@Name { + value: "book" +} +type Book record {| + string name; + BookDetails details; +|}; + +type BookDetails record {| + string? author; + string language; +|}; + +@test:Config { + groups: ["fromXml"] +} +function testFromXmlWithNilElementAndWithoutPreserveNS() returns Error? { + xml x1 = xml `Sherlock Holmes +
+ + English +
`; + Book expected = { + name: "Sherlock Holmes", + details: { + author: (), + language: "English" + } + }; + Book result = check fromXml(x1); + test:assertEquals(result, expected, msg = "testFromXmlWithNilElement result incorrect"); +} + +@Name { + value: "book" +} +type Book1 record {| + string name; + BookDetails1 details; +|}; + +type BookDetails1 record {| + string author; + string language; +|}; + +@test:Config { + groups: ["fromXml"] +} +function testFromXmlWithNilElementAndWithoutNillableField() returns Error? { + xml x1 = xml `Sherlock Holmes +
+ + English +
`; + Book1|Error result = fromXml(x1); + test:assertTrue(result is error, msg = "testFromXmlWithNilElementAndWithoutNillableField result incorrect"); + if (result is error) { + test:assertTrue(result.message().includes("field 'details.author' in record 'xmldata:BookDetails1' should be of type 'string', found '()'")); + } +} diff --git a/ballerina/tests/xml_from_json_test.bal b/ballerina/tests/xml_from_json_test.bal index eb1ff332..98271dcc 100644 --- a/ballerina/tests/xml_from_json_test.bal +++ b/ballerina/tests/xml_from_json_test.bal @@ -1096,3 +1096,13 @@ isolated function testWithRootTagConfig() { test:assertFail("failed to convert json to xml"); } } + +@test:Config { + groups: ["fromJson"] +} +function testFromJsonWithNull() returns error? { + json data = null; + xml? result = check fromJson({"name":"Sherlock Holmes", "details":{"author":null, "language":"English"}}, {rootTag: "book"}); + xml expected = xml `Sherlock Holmes
English
`; + test:assertEquals(result, expected, msg = "testFromJsonWithNull result incorrect"); +} diff --git a/ballerina/tests/xml_to_json_test.bal b/ballerina/tests/xml_to_json_test.bal index af07489e..69d9b5a0 100644 --- a/ballerina/tests/xml_to_json_test.bal +++ b/ballerina/tests/xml_to_json_test.bal @@ -740,6 +740,20 @@ function testToJsonWithNilElementAndWithoutPreserveNS() returns Error? { English `; - json j = check toJson(x, {preserveNamespaces: false}); + json j = check toJson(x1, {preserveNamespaces: false}); test:assertEquals(j, {"name":"Sherlock Holmes", "details":{"author":null, "language":"English"}}, msg = "testToJsonWithNilElement result incorrect"); } + +@test:Config { + groups: ["toJson"] +} +function testToJsonWithNilElementAndPreserveNS() returns Error? { + xml x1 = xml `Sherlock Holmes +
+ + English +
`; + json j = check toJson(x1, {preserveNamespaces: true}); + test:assertEquals(j, {"name":"Sherlock Holmes","details":{"author":{"@xsi:nil":"true"},"language":"English", + "@xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance"}}, msg = "testToJsonWithNilElement result incorrect"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java index 73cf899b..9a14bc73 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java @@ -232,7 +232,8 @@ private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attr @SuppressWarnings("unchecked") private static BMap insertDataToMap(BMap childrenData, Object children, BMap rootNode, String keyValue, - Type fieldType, FieldDetails fieldDetails, boolean nilValue) throws Exception { + Type fieldType, FieldDetails fieldDetails, boolean nilValue) + throws Exception { if (childrenData.size() > 0) { if (children instanceof BMap) { BMap data = (BMap) children; @@ -653,7 +654,7 @@ private static Object validateResult(Object result, BString elementName) { Object validateResult; if (result == null) { validateResult = fromString(EMPTY_STRING); - } else if (result instanceof BMap && ((BMap) result).get(elementName) != null) { + } else if (result instanceof BMap && ((BMap) result).containsKey(elementName)) { validateResult = ((BMap) result).get(elementName); } else { validateResult = result; From cea39c33a786c523e60a20c3c379e3d44aac4ef9 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Wed, 11 Dec 2024 19:39:09 +0530 Subject: [PATCH 5/5] update the change logs --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index cede5140..5f2ee217 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- [Add support for xsi:nil Attribute in xml to json/record conversion](https://github.com/ballerina-platform/ballerina-library/issues/7462) + +## [2.6.2] - 2023-08-17 + ### Fixed [Fix the mismatch error by `fromXml` API while the field has the name annotation](https://github.com/ballerina-platform/ballerina-standard-library/issues/3802) [Make some of the Java classes proper utility classes](https://github.com/ballerina-platform/ballerina-standard-library/issues/4902)