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/4] 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/4] 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/4] 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); + } } } From 73f0f8db66012e3f76baa4da507b4979ef9c89d5 Mon Sep 17 00:00:00 2001 From: MichalFrends1 Date: Fri, 8 May 2026 09:35:06 +0200 Subject: [PATCH 4/4] docs change --- Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md | 2 +- .../Definitions/Input.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md b/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md index 49110bb..c0835d4 100644 --- a/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md +++ b/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md @@ -2,7 +2,7 @@ ## [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. +- Added optional XSD input support to ensure consistent array/object mapping ## [1.1.0] - 2024-08-20 ### Updated diff --git a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs index 4b8f764..2f21d84 100644 --- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs +++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs @@ -12,8 +12,11 @@ public class Input public string XML { get; set; } /// - /// Optional XSD schema used for XML validation and JSON type mapping. + /// Optional XSD schema used for XML validation and for determining + /// whether XML elements should be serialized as JSON arrays. /// - /// <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>...</xs:schema> + /// + /// <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>...</xs:schema> + /// public string XSD { get; set; } }