Skip to content

Commit

Permalink
Merge pull request #551 from daneshk/master
Browse files Browse the repository at this point in the history
Support xsi:nil Attribute in xml to json/record conversion
  • Loading branch information
daneshk authored Dec 12, 2024
2 parents afe3e24 + cea39c3 commit 6309acd
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 10 deletions.
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
[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

[[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"
4 changes: 2 additions & 2 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"}
Expand Down
62 changes: 62 additions & 0 deletions ballerina/tests/from_xml_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<book><name>Sherlock Holmes</name>
<details xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<author xsi:nil="true"/>
<language>English</language>
</details></book>`;
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 `<book><name>Sherlock Holmes</name>
<details xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<author xsi:nil="true"/>
<language>English</language>
</details></book>`;
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 '()'"));
}
}
10 changes: 10 additions & 0 deletions ballerina/tests/xml_from_json_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<book><name>Sherlock Holmes</name><details><author/><language>English</language></details></book>`;
test:assertEquals(result, expected, msg = "testFromJsonWithNull result incorrect");
}
27 changes: 27 additions & 0 deletions ballerina/tests/xml_to_json_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<name>Sherlock Holmes</name>
<details xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<author xsi:nil="true"/>
<language>English</language>
</details>`;
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 `<name>Sherlock Holmes</name>
<details xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<author xsi:nil="true"/>
<language>English</language>
</details>`;
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");
}
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 14 additions & 5 deletions native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -224,7 +232,8 @@ private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attr
@SuppressWarnings("unchecked")
private static BMap<BString, Object> insertDataToMap(BMap<BString, Object> childrenData, Object children,
BMap<BString, Object> 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<BString, Object> data = (BMap<BString, Object>) children;
Expand All @@ -248,9 +257,9 @@ private static BMap<BString, Object> insertDataToMap(BMap<BString, Object> 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);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 6309acd

Please sign in to comment.