using System.Collections.Generic;
using System.Globalization;
using System.Xml.Serialization;
using System.Runtime.Serialization;
[DataContract(Namespace = "https://stackoverflow.com/questions/75912029")]
public class MyType : IParsable<MyType>
public MyType(string? value1, string? value2) => (this.Value1, this.Value2) = (value1, value2);
public string? Value1 { get; }
public string? Value2 { get; }
public override string ToString() => JsonSerializer.Serialize(this);
public static MyType Parse (string s, IFormatProvider? provider) => JsonSerializer.Deserialize<MyType>(s) ?? throw new ArgumentException();
public static bool TryParse (string? s, IFormatProvider? provider, out MyType result) => throw new NotImplementedException("not needed for the question");
[XmlSchemaProvider("GetSchemaMethod")]
public sealed class ParsableSurrogate<TSelf> : IXmlSerializable where TSelf : IParsable<TSelf>
public TSelf? Value { get; set; }
public void ReadXml(XmlReader reader)
Value = TSelf.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
public void WriteXml(XmlWriter writer) => writer.WriteValue(Value?.ToString());
public XmlSchema? GetSchema() => null;
const string DataContractNamespacePrefix = "http://schemas.datacontract.org/2004/07/";
static void GetDataContractNamespaceAndName(Type type, out string name, out string @namespace)
(name, @namespace) = (type.Name, DataContractNamespacePrefix + type.Namespace);
if (type.GetCustomAttribute<DataContractAttribute>() is {} attr)
(name, @namespace) = (attr.Name ?? name, attr.Namespace ?? @namespace);
public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs)
GetDataContractNamespaceAndName(typeof(TSelf), out var name, out var @namespace);
var tSelfType = new XmlSchemaSimpleType
Content = new XmlSchemaSimpleTypeRestriction { BaseTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName },
var tSelfElement = new XmlSchemaElement
SchemaTypeName = new XmlQualifiedName(tSelfType.Name, @namespace),
var tSelfSchema = new XmlSchema
TargetNamespace = @namespace,
Items = { tSelfElement, tSelfType },
return new XmlQualifiedName(name, @namespace);
public class SerializationSurrogateProvider : ISerializationSurrogateProvider
readonly Dictionary<Type, (Type SurrogateType, Func<object, Type, object> ToSurrogate)> toSurrogateDictionary = new();
readonly Dictionary<Type, (Type OriginalType, Func<object, Type, object> ToOriginal)> fromSurrogateDictionary = new();
public Type GetSurrogateType(Type type) =>
toSurrogateDictionary.TryGetValue(type, out var entry) ? entry.SurrogateType : type;
public object GetObjectToSerialize(object obj, Type targetType) =>
toSurrogateDictionary.TryGetValue(obj.GetType(), out var entry) ? entry.ToSurrogate(obj, targetType) : obj;
public object GetDeserializedObject(object obj, Type targetType) =>
fromSurrogateDictionary.TryGetValue(obj.GetType(), out var entry) ? entry.ToOriginal(obj, targetType) : obj;
public SerializationSurrogateProvider AddParsable<TParsable>() where TParsable : IParsable<TParsable>
toSurrogateDictionary.Add(typeof(TParsable), (typeof(ParsableSurrogate<TParsable>), (obj, t) => new ParsableSurrogate<TParsable> { Value = (TParsable)obj }));
fromSurrogateDictionary.Add(typeof(ParsableSurrogate<TParsable>), (typeof(TParsable), (obj, t) => ((ParsableSurrogate<TParsable>)obj).Value!));
public List<MyType?> MyTypes { get; set; } = new ();
public static void Test()
public static void TestMyType()
var model = new MyType("hello", "there");
var serializer = new DataContractSerializer(model.GetType());
serializer.SetSerializationSurrogateProvider(new SerializationSurrogateProvider().AddParsable<MyType>());
var xml = model.ToContractXml(serializer : serializer);
var model2 = DataContractSerializerHelper.FromContractXml<MyType>(xml, serializer);
Assert.That(model.Value1 == model2?.Value1 && model.Value2 == model2?.Value2);
public static void TestNested()
MyTypes = { new MyType("hello", "there"), null, new MyType("foo", "bar") },
var serializer = new DataContractSerializer(model.GetType());
serializer.SetSerializationSurrogateProvider(new SerializationSurrogateProvider().AddParsable<MyType>());
var xml = model.ToContractXml(serializer : serializer);
var model2 = DataContractSerializerHelper.FromContractXml<Model>(xml, serializer);
Assert.That(model2 != null);
Assert.AreEqual(model.MyTypes.Count, model2?.MyTypes.Count);
Assert.That(model.MyTypes.Select(m => m?.ToString()).SequenceEqual(model2!.MyTypes.Select(m => m?.ToString())));
var xml2 = model2.ToContractXml(serializer : serializer);
Assert.AreEqual(xml, xml2);
static void TestSchema(string xml)
var xs = new XmlSchemaSet();
var name = ParsableSurrogate<MyType>.GetSchemaMethod(xs);
var schema = xs.Schemas().Cast<XmlSchema>().First();
using var writer = new StringWriter();
Console.WriteLine("Schema for {0} using root name \"{1}\"", nameof(MyType), name);
Console.WriteLine(writer);
var settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
settings.ValidationEventHandler += (o, e) => throw new XmlException();
using (var xmlReader = XmlReader.Create(new StringReader(xml)))
Console.WriteLine("\n{0} XML Validated successfully.", nameof(MyType));
public static partial class DataContractSerializerHelper
public static string ToContractXml<T>(this T obj, DataContractSerializer? serializer = null, XmlWriterSettings? settings = null)
serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
using (var textWriter = new StringWriter())
settings = settings ?? new XmlWriterSettings { Indent = true };
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
serializer.WriteObject(xmlWriter, obj);
return textWriter.ToString();
public static T? FromContractXml<T>(string xml, DataContractSerializer? serializer = null)
using (var textReader = new StringReader(xml ?? ""))
using (var xmlReader = XmlReader.Create(textReader))
return (T?)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("Failed with unhandled exception: ");