From f5db4b457cb4b18f2b25ab900b754eeeea19c57f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 7 May 2026 07:20:18 +0000
Subject: [PATCH 1/3] Add XSD-aware XML conversion and test coverage
Agent-Logs-Url: https://github.com/FrendsPlatform/Frends.JSON2/sessions/73a1e239-08c8-420b-a943-a42ec2ba075d
Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com>
---
.../CHANGELOG.md | 6 +-
.../UnitTests.cs | 39 ++++++++-
.../ConvertXMLStringToJToken.cs | 82 ++++++++++++++++++-
.../Definitions/Input.cs | 8 +-
...rends.JSON.ConvertXMLStringToJToken.csproj | 4 +-
5 files changed, 130 insertions(+), 9 deletions(-)
diff --git a/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md b/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md
index 20be4d0..49110bb 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md
+++ b/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [1.2.0] - 2026-05-07
+### Added
+- Added optional XSD input support to validate XML and preserve schema-defined array mapping during XML to JToken conversion.
+
## [1.1.0] - 2024-08-20
### Updated
- Updated Newtonsoft.Json library to the latest version 13.0.3.
@@ -10,4 +14,4 @@
## [1.0.0] - 2023-02-13
### Added
-- Initial implementation
\ No newline at end of file
+- Initial implementation
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
index 77a4a9a..0104224 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
@@ -29,4 +29,41 @@ public void ShouldConvertXmlStringToJToken()
Assert.IsTrue(result.Success);
Assert.IsInstanceOfType(result.Jtoken, typeof(JObject));
}
-}
\ No newline at end of file
+
+ [TestMethod]
+ public void ShouldUseXsdToMapSingleElementAsArray()
+ {
+ var input = new Input()
+ {
+ XML = @"
+
+
+ Alan
+
+ ",
+ XSD = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+ };
+
+ var result = JSON.ConvertXMLStringToJToken(input);
+ var root = ((JObject)result.Jtoken)["root"];
+
+ Assert.IsTrue(result.Success);
+ Assert.IsNotNull(root);
+ Assert.IsInstanceOfType(root["person"], typeof(JArray));
+ }
+}
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
index d03892e..50f6215 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
@@ -1,8 +1,11 @@
-using Frends.JSON.ConvertXMLStringToJToken.Definitions;
+using Frends.JSON.ConvertXMLStringToJToken.Definitions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.ComponentModel;
+using System.IO;
using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Schema;
namespace Frends.JSON.ConvertXMLStringToJToken;
@@ -11,6 +14,8 @@ namespace Frends.JSON.ConvertXMLStringToJToken;
///
public class JSON
{
+ private const string JsonNamespace = "http://james.newtonking.com/projects/json";
+
///
/// Convert XML string to JToken.
/// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.JSON.ConvertXMLStringToJToken)
@@ -19,9 +24,78 @@ public class JSON
/// Object { bool Success, object Jtoken }
public static Result ConvertXMLStringToJToken([PropertyTab] Input input)
{
- var doc = new XmlDocument();
- doc.LoadXml(input.XML);
+ var doc = string.IsNullOrWhiteSpace(input.XSD)
+ ? LoadXmlDocument(input.XML)
+ : LoadXmlDocumentWithSchemaHints(input.XML, input.XSD);
+
var jsonString = JsonConvert.SerializeXmlNode(doc);
return new Result(true, JToken.Parse(jsonString));
}
-}
\ No newline at end of file
+
+ private static XmlDocument LoadXmlDocument(string xml)
+ {
+ var doc = new XmlDocument();
+ doc.LoadXml(xml);
+ return doc;
+ }
+
+ private static XmlDocument LoadXmlDocumentWithSchemaHints(string xml, string xsd)
+ {
+ var schemaSet = CreateSchemaSet(xsd);
+ ValidateXmlWithSchema(xml, schemaSet);
+
+ var xDocument = XDocument.Parse(xml);
+ xDocument.Validate(schemaSet, null, true);
+ AddJsonArrayAttributesFromSchema(xDocument);
+
+ return LoadXmlDocument(xDocument.ToString(SaveOptions.DisableFormatting));
+ }
+
+ private static XmlSchemaSet CreateSchemaSet(string xsd)
+ {
+ var schemaSet = new XmlSchemaSet();
+ using var schemaReader = XmlReader.Create(new StringReader(xsd));
+ schemaSet.Add(null, schemaReader);
+ schemaSet.Compile();
+ return schemaSet;
+ }
+
+ private static void ValidateXmlWithSchema(string xml, XmlSchemaSet schemaSet)
+ {
+ var settings = new XmlReaderSettings
+ {
+ ValidationType = ValidationType.Schema,
+ Schemas = schemaSet
+ };
+
+ settings.ValidationEventHandler += (_, args) =>
+ throw args.Exception ?? new XmlSchemaValidationException(args.Message);
+
+ using var xmlReader = XmlReader.Create(new StringReader(xml), settings);
+ while (xmlReader.Read())
+ {
+ // Read whole document to trigger schema validation.
+ }
+ }
+
+ private static void AddJsonArrayAttributesFromSchema(XDocument document)
+ {
+ if (document.Root == null) return;
+
+ XNamespace jsonNs = JsonNamespace;
+ var hasArray = false;
+
+ foreach (var element in document.Root.DescendantsAndSelf())
+ {
+ var schemaElement = element.GetSchemaInfo()?.SchemaElement;
+ if (schemaElement?.MaxOccurs > 1m)
+ {
+ element.SetAttributeValue(jsonNs + "Array", "true");
+ hasArray = true;
+ }
+ }
+
+ if (hasArray && document.Root.GetPrefixOfNamespace(jsonNs) == null)
+ document.Root.SetAttributeValue(XNamespace.Xmlns + "json", JsonNamespace);
+ }
+}
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs
index bb92971..4b8f764 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs
@@ -10,4 +10,10 @@ public class Input
///
/// <?xml version='1.0' standalone='no'?><root><foos id = '1' ><foo>bar</name></foos></root>
public string XML { get; set; }
-}
\ No newline at end of file
+
+ ///
+ /// Optional XSD schema used for XML validation and JSON type mapping.
+ ///
+ /// <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>...</xs:schema>
+ public string XSD { get; set; }
+}
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.csproj b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.csproj
index 6a0ddfb..a79bb1a 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.csproj
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.csproj
@@ -2,7 +2,7 @@
net6.0
- 1.1.0
+ 1.2.0
Frends
Frends
Frends
@@ -24,4 +24,4 @@
-
\ No newline at end of file
+
From 502e88b4cf6f7681356b1ae80744be0c20924347 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 7 May 2026 07:22:17 +0000
Subject: [PATCH 2/3] Refine schema validation messaging and comments
Agent-Logs-Url: https://github.com/FrendsPlatform/Frends.JSON2/sessions/73a1e239-08c8-420b-a943-a42ec2ba075d
Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com>
---
.../ConvertXMLStringToJToken.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
index 50f6215..cd7763f 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
@@ -45,6 +45,7 @@ private static XmlDocument LoadXmlDocumentWithSchemaHints(string xml, string xsd
ValidateXmlWithSchema(xml, schemaSet);
var xDocument = XDocument.Parse(xml);
+ // Populate schema info to map XML elements to JSON arrays based on schema occurrences.
xDocument.Validate(schemaSet, null, true);
AddJsonArrayAttributesFromSchema(xDocument);
@@ -69,7 +70,7 @@ private static void ValidateXmlWithSchema(string xml, XmlSchemaSet schemaSet)
};
settings.ValidationEventHandler += (_, args) =>
- throw args.Exception ?? new XmlSchemaValidationException(args.Message);
+ throw new XmlSchemaValidationException($"XML schema validation failed: {args.Message}", args.Exception);
using var xmlReader = XmlReader.Create(new StringReader(xml), settings);
while (xmlReader.Read())
From bf9b75eaa0d646dce51108292cb30734214b1748 Mon Sep 17 00:00:00 2001
From: MichalFrends1
Date: Thu, 7 May 2026 20:46:49 +0200
Subject: [PATCH 3/3] small refactor
---
.../UnitTests.cs | 7 +++
.../ConvertXMLStringToJToken.cs | 57 +++++++++++--------
2 files changed, 39 insertions(+), 25 deletions(-)
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
index 0104224..9149b9f 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs
@@ -65,5 +65,12 @@ public void ShouldUseXsdToMapSingleElementAsArray()
Assert.IsTrue(result.Success);
Assert.IsNotNull(root);
Assert.IsInstanceOfType(root["person"], typeof(JArray));
+
+ var persons = root["person"] as JArray;
+
+ Assert.IsNotNull(persons);
+
+ Assert.AreEqual(1, persons.Count);
+ Assert.AreEqual("Alan", persons[0]["name"]?.ToString());
}
}
diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
index cd7763f..e9731f4 100644
--- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
+++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs
@@ -3,6 +3,7 @@
using Newtonsoft.Json.Linq;
using System.ComponentModel;
using System.IO;
+using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
@@ -42,14 +43,27 @@ private static XmlDocument LoadXmlDocument(string xml)
private static XmlDocument LoadXmlDocumentWithSchemaHints(string xml, string xsd)
{
var schemaSet = CreateSchemaSet(xsd);
- ValidateXmlWithSchema(xml, schemaSet);
var xDocument = XDocument.Parse(xml);
- // Populate schema info to map XML elements to JSON arrays based on schema occurrences.
- xDocument.Validate(schemaSet, null, true);
+
+ xDocument.Validate(
+ schemaSet,
+ (sender, args) =>
+ {
+ throw new XmlSchemaValidationException(
+ $"XML schema validation failed: {args.Message}",
+ args.Exception);
+ },
+ true);
+
AddJsonArrayAttributesFromSchema(xDocument);
- return LoadXmlDocument(xDocument.ToString(SaveOptions.DisableFormatting));
+ var xmlDocument = new XmlDocument();
+
+ using var reader = xDocument.CreateReader();
+ xmlDocument.Load(reader);
+
+ return xmlDocument;
}
private static XmlSchemaSet CreateSchemaSet(string xsd)
@@ -61,27 +75,10 @@ private static XmlSchemaSet CreateSchemaSet(string xsd)
return schemaSet;
}
- private static void ValidateXmlWithSchema(string xml, XmlSchemaSet schemaSet)
- {
- var settings = new XmlReaderSettings
- {
- ValidationType = ValidationType.Schema,
- Schemas = schemaSet
- };
-
- settings.ValidationEventHandler += (_, args) =>
- throw new XmlSchemaValidationException($"XML schema validation failed: {args.Message}", args.Exception);
-
- using var xmlReader = XmlReader.Create(new StringReader(xml), settings);
- while (xmlReader.Read())
- {
- // Read whole document to trigger schema validation.
- }
- }
-
private static void AddJsonArrayAttributesFromSchema(XDocument document)
{
- if (document.Root == null) return;
+ if (document.Root == null)
+ return;
XNamespace jsonNs = JsonNamespace;
var hasArray = false;
@@ -89,6 +86,7 @@ private static void AddJsonArrayAttributesFromSchema(XDocument document)
foreach (var element in document.Root.DescendantsAndSelf())
{
var schemaElement = element.GetSchemaInfo()?.SchemaElement;
+
if (schemaElement?.MaxOccurs > 1m)
{
element.SetAttributeValue(jsonNs + "Array", "true");
@@ -96,7 +94,16 @@ private static void AddJsonArrayAttributesFromSchema(XDocument document)
}
}
- if (hasArray && document.Root.GetPrefixOfNamespace(jsonNs) == null)
- document.Root.SetAttributeValue(XNamespace.Xmlns + "json", JsonNamespace);
+ var existing = document.Root.Attributes()
+ .FirstOrDefault(a =>
+ a.IsNamespaceDeclaration &&
+ a.Value == JsonNamespace);
+
+ if (hasArray && existing == null)
+ {
+ document.Root.SetAttributeValue(
+ XNamespace.Xmlns + "json",
+ JsonNamespace);
+ }
}
}