diff --git a/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md b/Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md index 20be4d0..c0835d4 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 ensure consistent array/object mapping + ## [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..9149b9f 100644 --- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs +++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken.Tests/UnitTests.cs @@ -29,4 +29,48 @@ 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)); + + 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 d03892e..e9731f4 100644 --- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs +++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/ConvertXMLStringToJToken.cs @@ -1,8 +1,12 @@ -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.Linq; using System.Xml; +using System.Xml.Linq; +using System.Xml.Schema; namespace Frends.JSON.ConvertXMLStringToJToken; @@ -11,6 +15,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 +25,85 @@ 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); + + var xDocument = XDocument.Parse(xml); + + xDocument.Validate( + schemaSet, + (sender, args) => + { + throw new XmlSchemaValidationException( + $"XML schema validation failed: {args.Message}", + args.Exception); + }, + true); + + AddJsonArrayAttributesFromSchema(xDocument); + + var xmlDocument = new XmlDocument(); + + using var reader = xDocument.CreateReader(); + xmlDocument.Load(reader); + + return xmlDocument; + } + + 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 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; + } + } + + var existing = document.Root.Attributes() + .FirstOrDefault(a => + a.IsNamespaceDeclaration && + a.Value == JsonNamespace); + + if (hasArray && existing == 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..2f21d84 100644 --- a/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs +++ b/Frends.JSON.ConvertXMLStringToJToken/Frends.JSON.ConvertXMLStringToJToken/Definitions/Input.cs @@ -10,4 +10,13 @@ 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 for determining + /// whether XML elements should be serialized as JSON arrays. + /// + /// + /// <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 +