CSharp · Console
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Data;
using System.Text.Json;
using NUnit.Framework; //NUnit.Framework.AssertionException
#nullable enable
//https://stackoverflow.com/questions/76950645/c-sharp-deserialize-known-unknownelement-from-xml
public static partial class XmlExtensions
{
public static T? Deserialize<T>(this XmlElement element, bool useElementName)
{
var serializer = useElementName
? XmlSerializerFactory.Create(typeof(T), element.LocalName, element.NamespaceURI)
: new XmlSerializer(typeof(T));
return element.Deserialize<T>(serializer);
}
public static T? Deserialize<T>(this XmlElement element, XmlSerializer? serializer = null)
{
using (var reader = new ProperXmlNodeReader(element))
return reader.Deserialize<T>(serializer);
}
public static T? Deserialize<T>(this XmlReader reader, XmlSerializer? serializer = null)
=> (T?)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
class ProperXmlNodeReader : XmlNodeReader
{
// Bug fix from this answer https://stackoverflow.com/a/30115691/3744182
// By https://stackoverflow.com/users/8799/nathan-baulch
// To https://stackoverflow.com/questions/30102275/deserialize-object-property-with-stringreader-vs-xmlnodereader
// You may need to test whether this is still necessary,
public ProperXmlNodeReader(XmlNode node) : base(node) {}
public override string? LookupNamespace(string prefix) => base.LookupNamespace(prefix) is {} ns ? NameTable.Add(ns) : null;
}
}
public static class XmlSerializerFactory
{
// To avoid a memory leak the serializer must be cached.
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
// This factory taken from
// https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache = new ();
readonly static object padlock = new ();
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
lock (padlock)
{
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out var serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
namespace Version1
{
[Serializable()]
public class A
{
public string TheFooString { set; get; }
}
}
namespace Version2
{
public class A
{
public string TheBarString { set; get; }
[XmlAnyElement]
public List<XmlElement> UnknownElements { get; set; }
public static A Deserialize( string _sXml )
{
if( _sXml != string.Empty )
{
XmlSerializer serializer = new XmlSerializer(typeof(A));
XmlReader xmlReader = XmlReader.Create(new StringReader(_sXml));
A instance = (A)serializer.Deserialize(xmlReader);
instance.AfterDeserialize();
return instance;
}
return null;
}
public void AfterDeserialize()
{
if (UnknownElements == null)
return;
for (var i = UnknownElements.Count-1; i >= 0; i--)
{
var xmlElement = UnknownElements[i];
if( xmlElement.LocalName == "TheFooString" )
{
try
{
var BorrowString = xmlElement.Deserialize<string>(useElementName : true);
// Postprocess BorrowString as required. For testing purposes I'm setting TheBarString to the deserialized value:
TheBarString = BorrowString;
// Maybe you want to remove the obsolete xmlElement at this point? If so, do:
UnknownElements.RemoveAt(i);
}
catch( Exception ex )
{
Console.WriteLine( ex.ToString() );
}
}
}
}
}
}
public class TestClass
{
public static void Test()
{
Test1();
Test2();
}
public static void Test1()
{
var s = "value of TheFooString";
Console.WriteLine(s.GetXml(omitStandardNamespaces : true));
}
public static void Test2()
{
var original = new Version1.A { TheFooString = "I'm from Foo.A" };
var xml = original.GetXml();
var test = Version2.A.Deserialize(xml);
test.Dump();
Assert.AreEqual(original.TheFooString, test.TheBarString);
Console.WriteLine("Re-serialized {0}:", test);
Console.WriteLine(test.GetXml());
}
}
public static partial class XmlExtensions
{
public static T? LoadFromXml<T>(this string xmlString, XmlSerializer? serial = null)
{
serial = serial ?? new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T?)serial.Deserialize(reader);
}
}
public static string GetXml<T>(this T obj, bool omitStandardNamespaces)
{
return obj.GetXml(null, omitStandardNamespaces);
}
public static string GetXml<T>(this T obj, XmlSerializer? serializer = null, bool omitStandardNamespaces = false)
{
XmlSerializerNamespaces? ns = null;
if (omitStandardNamespaces)
{
ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
}
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj?.GetType() ?? typeof(T))).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: {0} ({1}, {2})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}