[Fork] https://stackoverflow.com/questions/76950645/c-sharp-deserialize-known-unknownelement-from-xml

CSharp · Console

/Program.cs

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;
			}
		}
	}
;