Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Frends.JSON.ConvertXMLStringToJToken/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -10,4 +14,4 @@

## [1.0.0] - 2023-02-13
### Added
- Initial implementation
- Initial implementation
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,48 @@ public void ShouldConvertXmlStringToJToken()
Assert.IsTrue(result.Success);
Assert.IsInstanceOfType(result.Jtoken, typeof(JObject));
}
}

[TestMethod]
public void ShouldUseXsdToMapSingleElementAsArray()
{
var input = new Input()
{
XML = @"<?xml version='1.0' standalone='no'?>
<root>
<person id='1'>
<name>Alan</name>
</person>
</root>",
XSD = @"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>
<xs:element name='root'>
<xs:complexType>
<xs:sequence>
<xs:element name='person' maxOccurs='unbounded'>
<xs:complexType>
<xs:sequence>
<xs:element name='name' type='xs:string' />
</xs:sequence>
<xs:attribute name='id' type='xs:string' />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"
};

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());
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -11,6 +15,8 @@ namespace Frends.JSON.ConvertXMLStringToJToken;
/// </summary>
public class JSON
{
private const string JsonNamespace = "http://james.newtonking.com/projects/json";

/// <summary>
/// Convert XML string to JToken.
/// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.JSON.ConvertXMLStringToJToken)
Expand All @@ -19,9 +25,85 @@ public class JSON
/// <returns>Object { bool Success, object Jtoken }</returns>
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));
}
}

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ public class Input
/// </summary>
/// <example>&lt;?xml version='1.0' standalone='no'?&gt;&lt;root&gt;&lt;foos id = '1' &gt;&lt;foo&gt;bar&lt;/name&gt;&lt;/foos&gt;&lt;/root&gt;</example>
public string XML { get; set; }
}

/// <summary>
/// Optional XSD schema used for XML validation and JSON type mapping.
/// </summary>
/// <example>&lt;xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'&gt;...&lt;/xs:schema&gt;</example>
public string XSD { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>1.1.0</Version>
<Version>1.2.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand All @@ -24,4 +24,4 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
</Project>
Loading