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"
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"}
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 HolmesEnglish `;
+ 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 aaa246af..69d9b5a0 100644
--- a/ballerina/tests/xml_to_json_test.bal
+++ b/ballerina/tests/xml_to_json_test.bal
@@ -730,3 +730,30 @@ 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(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/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)
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..9a14bc73 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,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) throws Exception {
+ Type fieldType, FieldDetails fieldDetails, boolean nilValue)
+ throws Exception {
if (childrenData.size() > 0) {
if (children instanceof BMap) {
BMap data = (BMap) children;
@@ -248,9 +257,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);
@@ -645,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;