I am serializing an object into XML which has DefaultValue attributes on some of the properties. Under certain circumstances I would like to disable all these default values during serialization. Is there a way to remove the attribute during runtime?
[Serializable]
[XmlType(TypeName = "MyType")]
public class MyType
{
public MyType()
{
MyValue = false;
}
[XmlElement(ElementName = "myValue", Form = XmlSchemaForm.Unqualified)]
[DefaultValue(false)]
public bool MyValue { get; set; }
}
public class TestSerializer
{
public void Serialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(MyType));
using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
serializer.Serialize(writer, new MyType());
}
}
}
I was already looking into doing that with reflection, but could not get it working. The XmlAttributeOverrides also do not seem to help, but maybe I just haven't found the correct approach yet. Any ideas?
XmlSerializerIgnoringDefaultValuesKey.Create from this answer to Force XML serialization of XmlDefaultValue values mostly does what you need, however you have overridden the XML element name of MyValue by applying [XmlElement(ElementName = "myValue", Form = XmlSchemaForm.Unqualified)], which is stated to be not implemented in that answer:
When overriding with this XmlAttributes object any attributes that are desired to stay need to be transferred into this new object.
The following enhanced version supports transferring [XmlElement], [XmlArray] and [XmlArrayItem] override attributes:
public abstract class XmlSerializerKey
{
static class XmlSerializerHashTable
{
static Dictionary<object, XmlSerializer> dict;
static XmlSerializerHashTable()
{
dict = new Dictionary<object, XmlSerializer>();
}
public static XmlSerializer GetSerializer(XmlSerializerKey key)
{
lock (dict)
{
XmlSerializer value;
if (!dict.TryGetValue(key, out value))
dict[key] = value = key.CreateSerializer();
return value;
}
}
}
readonly Type serializedType;
protected XmlSerializerKey(Type serializedType)
{
this.serializedType = serializedType;
}
public Type SerializedType { get { return serializedType; } }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
else if (ReferenceEquals(null, obj))
return false;
if (GetType() != obj.GetType())
return false;
XmlSerializerKey other = (XmlSerializerKey)obj;
if (other.serializedType != serializedType)
return false;
return true;
}
public override int GetHashCode()
{
int code = 0;
if (serializedType != null)
code ^= serializedType.GetHashCode();
return code;
}
public override string ToString()
{
return string.Format(base.ToString() + ": for type: " + serializedType.ToString());
}
public XmlSerializer GetSerializer()
{
return XmlSerializerHashTable.GetSerializer(this);
}
protected abstract XmlSerializer CreateSerializer();
}
public abstract class XmlSerializerWithExtraTypesKey : XmlSerializerKey
{
static IEqualityComparer<HashSet<Type>> comparer;
readonly HashSet<Type> extraTypes = new HashSet<Type>();
static XmlSerializerWithExtraTypesKey()
{
comparer = HashSet<Type>.CreateSetComparer();
}
protected XmlSerializerWithExtraTypesKey(Type serializedType, IEnumerable<Type> extraTypes)
: base(serializedType)
{
if (extraTypes != null)
foreach (var type in extraTypes)
this.extraTypes.Add(type);
}
public Type[] ExtraTypes { get { return extraTypes.ToArray(); } }
public override bool Equals(object obj)
{
if (!base.Equals(obj))
return false;
XmlSerializerWithExtraTypesKey other = (XmlSerializerWithExtraTypesKey)obj;
return comparer.Equals(this.extraTypes, other.extraTypes);
}
public override int GetHashCode()
{
int code = base.GetHashCode();
if (extraTypes != null)
code ^= comparer.GetHashCode(extraTypes);
return code;
}
}
public sealed class XmlSerializerIgnoringDefaultValuesKey : XmlSerializerWithExtraTypesKey
{
readonly XmlAttributeOverrides overrides;
private XmlSerializerIgnoringDefaultValuesKey(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, XmlAttributeOverrides overrides)
: base(serializerType, ignoreDefaultTypes)
{
this.overrides = overrides;
}
public static XmlSerializerIgnoringDefaultValuesKey Create(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, bool recurse)
{
XmlAttributeOverrides overrides;
Type [] typesWithOverrides;
CreateOverrideAttributes(ignoreDefaultTypes, recurse, out overrides, out typesWithOverrides);
return new XmlSerializerIgnoringDefaultValuesKey(serializerType, typesWithOverrides, overrides);
}
protected override XmlSerializer CreateSerializer()
{
var types = ExtraTypes;
if (types == null || types.Length < 1)
return new XmlSerializer(SerializedType);
return new XmlSerializer(SerializedType, overrides);
}
static void CreateOverrideAttributes(IEnumerable<Type> types, bool recurse, out XmlAttributeOverrides overrides, out Type[] typesWithOverrides)
{
HashSet<Type> visited = new HashSet<Type>();
HashSet<Type> withOverrides = new HashSet<Type>();
overrides = new XmlAttributeOverrides();
foreach (var type in types)
{
CreateOverrideAttributes(type, recurse, overrides, visited, withOverrides);
}
typesWithOverrides = withOverrides.ToArray();
}
static void AddOverride(XmlAttributeOverrides overrides, Type type, MemberInfo memberInfo)
{
var xmlElementAttr = memberInfo.GetCustomAttributes<XmlElementAttribute>();
var xmlArrayAttr = memberInfo.GetCustomAttribute<XmlArrayAttribute>();
var xmlArrayItemAttr = memberInfo.GetCustomAttributes<XmlArrayItemAttribute>();
var attrs = new XmlAttributes
{
XmlDefaultValue = null,
XmlArray = xmlArrayAttr,
};
foreach (var a in xmlElementAttr)
attrs.XmlElements.Add(a);
foreach (var a in xmlArrayItemAttr)
attrs.XmlArrayItems.Add(a);
overrides.Add(type, memberInfo.Name, attrs);
}
static void CreateOverrideAttributes(Type type, bool recurse, XmlAttributeOverrides overrides, HashSet<Type> visited, HashSet<Type> withOverrides)
{
if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string) || visited.Contains(type))
return;
foreach (var property in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, property.Name] == null) // Check to see if overrides for this base type were already set.
if (Attribute.IsDefined(property, typeof(DefaultValueAttribute), true))
{
withOverrides.Add(type);
AddOverride(overrides, type, property);
}
foreach (var field in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, field.Name] == null) // Check to see if overrides for this base type were already set.
if (Attribute.IsDefined(field, typeof(DefaultValueAttribute), true))
{
withOverrides.Add(type);
AddOverride(overrides, type, field);
}
visited.Add(type);
if (recurse)
{
var baseType = type.BaseType;
if (baseType != type)
CreateOverrideAttributes(baseType, recurse, overrides, visited, withOverrides);
}
}
}
And use it like:
var serializer = XmlSerializerIgnoringDefaultValuesKey.Create(typeof(MyType), new[] { typeof(MyType) }, true).GetSerializer();
using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
serializer.Serialize(writer, new MyType());
}
Demo fiddle here.
Related
I have a scenario where I will receive a dictionary<string, object>();
that can also be recursive though not always, but the main issue is that it contains lambdas.
as we implement a transaction system I need to clone it, which works fine until I hit the lambdas.
I tried to move these to delegates but the error changes and still gives me runtime issue.
technically I can re inject the lambdas after the cloning, but don't know how to tell the DataContractSerializer to ignore these.
I also cannot remove the lambdas prior to the cloning as the original object still needs them if I cancel the transaction.
In your Clone() method you can use the data contract surrogate functionality to replace all System.Delegate objects in your serialization graph with a serializable type, such as a lookup in a dictionary of delegates that you build as you serialize. Then, as you deserialize, you could replace the deserialized serialization surrogates with the original delegates.
The following does this:
public static class DataContractExtensions
{
public static Dictionary<string, object> Clone(this Dictionary<string, object> dictionary)
{
if (dictionary == null)
return null;
var surrogate = new DelegateSurrogateSelector();
var types = new[]
{
typeof(Dictionary<string, object>),
typeof(DelegateSurrogateId),
// Add in whatever additional known types you want.
};
var serializer = new DataContractSerializer(
typeof(Dictionary<string, object>),
types, int.MaxValue, false, false,
surrogate);
var xml = dictionary.ToContractXml(serializer, null);
return FromContractXml<Dictionary<string, object>>(xml, serializer);
}
public static string ToContractXml<T>(this T obj, DataContractSerializer serializer, XmlWriterSettings settings)
{
serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.WriteObject(xmlWriter, obj);
}
return textWriter.ToString();
}
}
public static T FromContractXml<T>(string xml, DataContractSerializer serializer)
{
using (var textReader = new StringReader(xml ?? ""))
using (var xmlReader = XmlReader.Create(textReader))
{
return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
}
}
[DataContract]
class DelegateSurrogateId
{
[DataMember]
public int Id { get; set; }
}
class DelegateSurrogateSelector : IDataContractSurrogate
{
public Dictionary<int, System.Delegate> DelegateDictionary { get; private set; }
public DelegateSurrogateSelector()
{
this.DelegateDictionary = new Dictionary<int, Delegate>();
}
#region IDataContractSurrogate Members
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public Type GetDataContractType(Type type)
{
if (typeof(Delegate).IsAssignableFrom(type))
return typeof(DelegateSurrogateId);
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
var id = obj as DelegateSurrogateId;
if (id != null)
return DelegateDictionary[id.Id];
return obj;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public object GetObjectToSerialize(object obj, Type targetType)
{
var del = obj as Delegate;
if (del != null)
{
var id = DelegateDictionary.Count;
DelegateDictionary.Add(id, del);
return new DelegateSurrogateId { Id = id };
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
#endregion
}
}
Using the above Clone() extension method, the following test will pass:
Func<bool> returnTrue = () => true;
Func<bool> returnFalse = () => false;
var dictionary = new Dictionary<string, object>()
{
{ "a", "hello"},
{ "b", 10101 },
{ "c", returnTrue },
{ "d", new Dictionary<string, object>()
{
{ "q", 101 },
{ "r", returnFalse },
}
}
};
var dictionary2 = dictionary.Clone();
Assert.AreEqual(returnTrue, dictionary2["c"]); // No failure
var inner = (Dictionary<string, object>)dictionary["d"];
var inner2 = (Dictionary<string, object>)dictionary2["d"];
Assert.AreEqual(inner["r"], inner2["r"]); // No failure
Notes:
Data contract serialization surrogates work differently in .NET Core. There, the IDataContractSurrogate interface has been replaced with ISerializationSurrogateProvider.
I want to serialize and deserialize objects, which are contained in a dictionary, implement Equals method and contain dictionaries inside. However it leads to an ArgumentException, because Equals method is called too early on deserialization.
using System.Collections.Generic;
using System.Runtime.Serialization;
using NUnit.Framework;
[Serializable]
public class SerializableObject
{
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var (k, v) in Values)
if (!other.Values.TryGetValue(k, out var otherV) || v != otherV)
return false;
return true;
}
public override int GetHashCode()
{
return 0;
}
}
[Test]
public void DeserializeDictionary()
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var formatter = new BinaryFormatter();
var dict = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
};
formatter.Serialize(stream, dict);
stream.Seek(0, SeekOrigin.Begin);
dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);
Assert.Contains(obj1, dict.Keys);
Assert.Contains(obj2, dict.Keys);
}
}
When deserializing, Equals method is called on two objects, which have empty dictionaries, and an ArgumentException happens ("An item with the same key has already been added").
Also, if the object implements [OnDeserialized] method, this method is also called when the dictionary is still empty, and the inner dictionary is filled later, before returning the entire top-level dictionary.
EDIT: In this short example GetHashCode deliberately returns 0 to force Equals to be called. In the real-world program there are just two unequal objects with the same hash code (a collision).
EDIT 2: Here is a more complex example where the objects are nested.
[Serializable]
public class SerializableObject : ISerializable
{
public SerializableObject() {}
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode() => 0;
public void GetObjectData(SerializationInfo info, StreamingContext context) =>
info.AddValue(nameof(Values), Values.ToArray());
private SerializableObject(SerializationInfo info, StreamingContext context) =>
Values = new Dictionary<string, bool>(info.Get<KeyValuePair<string, bool>[]>(nameof(Values)));
}
[Serializable]
public class SerializableObject2 : ISerializable
{
public SerializableObject2() { }
public Dictionary<SerializableObject, bool> Values = new Dictionary<SerializableObject, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject2 other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode() => 0;
public void GetObjectData(SerializationInfo info, StreamingContext context) =>
info.AddValue(nameof(Values), Values.ToArray());
private SerializableObject2(SerializationInfo info, StreamingContext context) =>
Values = new Dictionary<SerializableObject, bool>(info.Get<KeyValuePair<SerializableObject, bool>[]>(nameof(Values)));
}
[Test]
public void DeserializeDictionary()
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var sobj1 = new SerializableObject2
{
Values = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
}
};
var formatter = new BinaryFormatter();
formatter.Serialize(stream, sobj1);
stream.Seek(0, SeekOrigin.Begin);
sobj1 = (SerializableObject2)formatter.Deserialize(stream);
Assert.Contains(obj1, sobj1.Values.Keys);
Assert.Contains(obj2, sobj1.Values.Keys);
}
}
I'm not sure and am just speculating, but it doesn't look like the SerializableObject is being populated until after the Dictionary that was serialized (in this case, dict) has added them.
It does seem like you can get around the issue by having SerializableObject implement ISerializable and provide your own GetObjectData and Deserialization constructor.
[Serializable]
public class SerializableObject : ISerializable
{
public SerializableObject()
{
}
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode()
{
return 0;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(KeyValuePairsKey, Values.Select(kvp => kvp).ToArray());
}
private SerializableObject(SerializationInfo info, StreamingContext context)
{
var kvps = info.GetValue(KeyValuePairsKey, typeof(KeyValuePair<string, bool>[])) as KeyValuePair<string, bool>[];
foreach (var kvp in kvps)
{
Values.Add(kvp.Key, kvp.Value);
}
}
private const string KeyValuePairsKey = "KVPS";
}
class Program
{
static void Main(string[] args)
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var formatter = new BinaryFormatter();
var dict = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
};
formatter.Serialize(stream, dict);
stream.Seek(0, SeekOrigin.Begin);
dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);
}
}
}
You always return 0 in the method GetHashCode(). I think the dictionary uses the GetHashCode() to determine the equality of keys.
Json.Net uses the default .Net Dictionary for Serialization of JSON Dictionaries when i just use the IDictionary interface. I want to replace it against my Custom Version of a Dictionary globally.
Do I need to write a JsonConverter in order to do this or do i just miss a Setting like:
config.DefaultDictType = typeof(MyDict);
somewhere
There isn't a simple setting for this. Properties declared as IDictionary<TKey,TValue> are allocated as Dictionary<TKey, TValue> if the current value is null.
However, if you pre-allocate the custom dictionaries in the default constructors for your classes, Json.NET will use the pre-existing allocated dictionary rather than allocating a new one. (This is why JsonConverter.ReadJson has an existingValue argument.)
For instance:
public class MyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
}
public class DictionaryContainer
{
public DictionaryContainer()
{
this.Values = new MyDictionary<string, string>();
}
public IDictionary<string, string> Values { get; set; }
}
public static class TestClass
{
public static void Test()
{
var container = new DictionaryContainer();
container.Values["one"] = "first";
container.Values["two"] = "second";
container.Values["three"] = "third";
Debug.Assert(container.Values.GetType() == typeof(MyDictionary<string, string>)); // No assert
var json = JsonConvert.SerializeObject(container, Formatting.Indented);
var container2 = JsonConvert.DeserializeObject<DictionaryContainer>(json);
Debug.Assert(container.Values.GetType() == container2.Values.GetType());// No assert
}
}
If this is impractical (because of, e.g., issues with legacy code), the easiest way to achieve what you want is probably with a single global converter that, when reading, allocates dictionaries for all properties defined to return type IDictionary<TKey,TValue> for any key and value:
public abstract class CustomDictionaryTypeReaderBase : JsonConverter
{
protected abstract Type CreateDictionaryType(Type tKey, Type tValue);
bool GetIDictionaryGenericParameters(Type objectType, out Type tKey, out Type tValue)
{
tKey = tValue = null;
if (!objectType.IsGenericType)
return false;
var genericType = objectType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
var args = objectType.GetGenericArguments();
if (args.Length != 2)
tKey = tValue = null;
tKey = args[0];
tValue = args[1];
return true;
}
public override bool CanConvert(Type objectType)
{
Type tKey, tValue;
return GetIDictionaryGenericParameters(objectType, out tKey, out tValue);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (existingValue != null)
{
serializer.Populate(reader, existingValue);
return existingValue;
}
else
{
Type tKey, tValue;
bool ok = GetIDictionaryGenericParameters(objectType, out tKey, out tValue);
if (!ok)
{
return serializer.Deserialize(reader, objectType);
}
else
{
var realType = CreateDictionaryType(tKey, tValue);
Debug.Assert(objectType.IsAssignableFrom(realType));
return serializer.Deserialize(reader, realType);
}
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public sealed class MyCustomDictionaryTypeReader : CustomDictionaryTypeReaderBase
{
protected override Type CreateDictionaryType(Type tKey, Type tValue)
{
var dictType = typeof(MyDictionary<,>).MakeGenericType(new[] { tKey, tValue });
return dictType;
}
}
And then use it like:
var settings = new JsonSerializerSettings();
settings.Converters = new[] { new MyCustomDictionaryTypeReader() };
var multiContainer2 = JsonConvert.DeserializeObject<MultipleDictionaryContainer>(json2, settings);
Another way to achieve what you wanted is by modifying the DefaultContractResolver, override its CreateDictionaryContract() method, and then set it as the serializer's default ContractResolver.
Assuming your custom dictionary is defined as:
public class CustomDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
public string Custom { get { return "I'm Custom"; } }
}
You could implement the custom contract resolver as:
public class CustomDictionaryContractResolver : DefaultContractResolver
{
public CustomDictionaryContractResolver() : base(true) { }
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
// let the base class do the heavy lifting
var contract = base.CreateDictionaryContract(objectType);
// nothing todo if the created type is already our custom dictionary type
if (IsGenericDefinition(contract.CreatedType, typeof (CustomDictionary<,>)))
return contract;
if (IsGenericDefinition(contract.UnderlyingType, typeof(IDictionary<,>)) || (typeof(IDictionary).IsAssignableFrom(contract.UnderlyingType)))
{
contract.CreatedType = typeof(CustomDictionary<,>)
.MakeGenericType(contract.DictionaryKeyType ?? typeof(object), contract.DictionaryValueType ?? typeof(object));
// Set our object instantiation using the default constructor;
// You need to modify this, if your custom dictionary does not
// have a default constructor.
contract.DefaultCreator = () => Activator.CreateInstance(contract.CreatedType);
}
return contract;
}
static bool IsGenericDefinition(Type type, Type genericDefinition)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == genericDefinition;
}
}
And then use it like:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CustomDictionaryContractResolver()
};
For example:
public class MyObject
{
public string FirstName { get; set; }
public string LastName { get; set; }
public IDictionary<string, object> Properties { get; set; }
}
public static class TestClass
{
public static void Test()
{
const string json = #"{""FirstName"": ""John"",""LastName"": ""Smith"",""Properties"": {""Email"": ""john.smith#example.com"",""Level"": 42,""Admin"": true}}";
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CustomDictionaryContractResolver()
};
var myObject = JsonConvert.DeserializeObject<MyObject>(json);
Debug.WriteLineIf(myObject.FirstName == "John", "FirstName is John");
Debug.WriteLineIf(myObject.LastName == "Smith", "LastName is Smith");
Debug.WriteLineIf(myObject.Properties != null, "Properties is not NULL");
Debug.WriteLineIf(myObject.Properties.Count == 3, "Properties has 3 items");
Debug.WriteLineIf(myObject.Properties.GetType() == typeof(CustomDictionary<string, object>), "Properties is CustomDictionary<,>");
var customDictionary = myObject.Properties as CustomDictionary<string, object>;
Debug.WriteLineIf(customDictionary != null, "Properties say " + customDictionary.Custom);
}
}
If you run the TestClass.Test() method, you should see the following in the Output:
FirstName is John
LastName is Smith
Properties is not NULL
Properties has 3 items
Properties is CustomDictionary
Properties say I'm Custom
Using a C# class generated from an XSD document, I can create an object, and serialize it successfully. However, some attributes have an XmlDefaultValue defined. If any objects have the default value, then those attributes do not get created when the object is serialized.
This is expected behavior according to the documentation. But this is not how I want it to behave. I need to have all such attributes generated in the XML document.
I've checked for any code attributes that can be applied that might force it to be outputted, even if it is the default value, but I couldn't find anything like that.
Is there any way to make that work?
The last answer regarding DataContract is NOT the answer. The XSD is generated automatically and the person consuming the classes is not in control of the attributes used by the original author. The question was about auto-generated classes based on an XSD.
The other answer is problematic too because properties that have defaults defined also may not allow null values (this happens often). The only real solution is to have a serializer where you can tell it what properties to ignore with respect to serialization. This has been and always be a serious problem with current XML serializers that simply don't allow one to pass in what properties to force being serialized.
Actual scenario:
A REST service accepts XML in the body to update an object. The XML has an XSD defined by the author of the rest service. The current object stored by the rest service has a non-default value set. The users modifies the XML to change it back to the default... but the serialized version put in the body of the REST post skips the value and doesn't include it because its set to a default value.
What a quagmire... can't update the value because the logic behind not exporting default values completely ignores the idea that XML can be used to update an object, not just create new ones based on the XML. I can't believe its been this many years and nobody modified XML serializers to handle this basic scenario with ease.
You can do this for a specific set of types when serializing by constructing an XmlAttributeOverrides that specifies new XmlAttributes() { XmlDefaultValue = null } for every field or property that has DefaultValueAttribute applied, then passing this to the XmlSerializer(Type, XmlAttributeOverrides) constructor:
var overrides = new XmlAttributeOverrides();
var attrs = new XmlAttributes() { XmlDefaultValue = null };
overrides.Add(typeToSerialize, propertyNameWithDefaultToIgnore, attrs);
var serializer = new XmlSerializer(typeToSerialize, overrides);
Note, however, this important warning from the documentation:
Dynamically Generated Assemblies
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
However, the example given in the code doesn't give any suggestion of how to key the hashtable. It also isn't thread-safe. (Perhaps it dates from .Net 1.0?)
The following code creates a key scheme for xml serializers with overrides, and manufactures (via reflection) serializers for which the [DefaultValue] values (if any) of all properties and fields are overridden to be null, effectively cancelling the default value. Note, when creating a blank XmlAttributes() object all attributes are set to null. When overriding with this XmlAttributes object any attributes that are desired to stay need to be transferred into this new object:
public abstract class XmlSerializerKey
{
static class XmlSerializerHashTable
{
static Dictionary<object, XmlSerializer> dict;
static XmlSerializerHashTable()
{
dict = new Dictionary<object, XmlSerializer>();
}
public static XmlSerializer GetSerializer(XmlSerializerKey key)
{
lock (dict)
{
XmlSerializer value;
if (!dict.TryGetValue(key, out value))
dict[key] = value = key.CreateSerializer();
return value;
}
}
}
readonly Type serializedType;
protected XmlSerializerKey(Type serializedType)
{
this.serializedType = serializedType;
}
public Type SerializedType { get { return serializedType; } }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
else if (ReferenceEquals(null, obj))
return false;
if (GetType() != obj.GetType())
return false;
XmlSerializerKey other = (XmlSerializerKey)obj;
if (other.serializedType != serializedType)
return false;
return true;
}
public override int GetHashCode()
{
int code = 0;
if (serializedType != null)
code ^= serializedType.GetHashCode();
return code;
}
public override string ToString()
{
return string.Format(base.ToString() + ": for type: " + serializedType.ToString());
}
public XmlSerializer GetSerializer()
{
return XmlSerializerHashTable.GetSerializer(this);
}
protected abstract XmlSerializer CreateSerializer();
}
public abstract class XmlserializerWithExtraTypesKey : XmlSerializerKey
{
static IEqualityComparer<HashSet<Type>> comparer;
readonly HashSet<Type> extraTypes = new HashSet<Type>();
static XmlserializerWithExtraTypesKey()
{
comparer = HashSet<Type>.CreateSetComparer();
}
protected XmlserializerWithExtraTypesKey(Type serializedType, IEnumerable<Type> extraTypes)
: base(serializedType)
{
if (extraTypes != null)
foreach (var type in extraTypes)
this.extraTypes.Add(type);
}
public Type[] ExtraTypes { get { return extraTypes.ToArray(); } }
public override bool Equals(object obj)
{
if (!base.Equals(obj))
return false;
XmlserializerWithExtraTypesKey other = (XmlserializerWithExtraTypesKey)obj;
return comparer.Equals(this.extraTypes, other.extraTypes);
}
public override int GetHashCode()
{
int code = base.GetHashCode();
if (extraTypes != null)
code ^= comparer.GetHashCode(extraTypes);
return code;
}
}
public sealed class XmlSerializerIgnoringDefaultValuesKey : XmlserializerWithExtraTypesKey
{
readonly XmlAttributeOverrides overrides;
private XmlSerializerIgnoringDefaultValuesKey(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, XmlAttributeOverrides overrides)
: base(serializerType, ignoreDefaultTypes)
{
this.overrides = overrides;
}
public static XmlSerializerIgnoringDefaultValuesKey Create(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, bool recurse)
{
XmlAttributeOverrides overrides;
Type [] typesWithOverrides;
CreateOverrideAttributes(ignoreDefaultTypes, recurse, out overrides, out typesWithOverrides);
return new XmlSerializerIgnoringDefaultValuesKey(serializerType, typesWithOverrides, overrides);
}
protected override XmlSerializer CreateSerializer()
{
var types = ExtraTypes;
if (types == null || types.Length < 1)
return new XmlSerializer(SerializedType);
return new XmlSerializer(SerializedType, overrides);
}
static void CreateOverrideAttributes(IEnumerable<Type> types, bool recurse, out XmlAttributeOverrides overrides, out Type[] typesWithOverrides)
{
HashSet<Type> visited = new HashSet<Type>();
HashSet<Type> withOverrides = new HashSet<Type>();
overrides = new XmlAttributeOverrides();
foreach (var type in types)
{
CreateOverrideAttributes(type, recurse, overrides, visited, withOverrides);
}
typesWithOverrides = withOverrides.ToArray();
}
static void CreateOverrideAttributes(Type type, bool recurse, XmlAttributeOverrides overrides, HashSet<Type> visited, HashSet<Type> withOverrides)
{
if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string) || visited.Contains(type))
return;
var attrs = new XmlAttributes() { XmlDefaultValue = null };
foreach (var property in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, property.Name] == null) // Check to see if overrides for this base type were already set.
if (property.GetCustomAttributes<DefaultValueAttribute>(true).Any())
{
withOverrides.Add(type);
overrides.Add(type, property.Name, attrs);
}
foreach (var field in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, field.Name] == null) // Check to see if overrides for this base type were already set.
if (field.GetCustomAttributes<DefaultValueAttribute>(true).Any())
{
withOverrides.Add(type);
overrides.Add(type, field.Name, attrs);
}
visited.Add(type);
if (recurse)
{
var baseType = type.BaseType;
if (baseType != type)
CreateOverrideAttributes(baseType, recurse, overrides, visited, withOverrides);
}
}
}
And then you would call it like:
var serializer = XmlSerializerIgnoringDefaultValuesKey.Create(typeof(ClassToSerialize), new[] { typeof(ClassToSerialize), typeof(AdditionalClass1), typeof(AdditionalClass2), ... }, true).GetSerializer();
For example, in the following class hierarchy:
public class BaseClass
{
public BaseClass() { Index = 1; }
[DefaultValue(1)]
public int Index { get; set; }
}
public class MidClass : BaseClass
{
public MidClass() : base() { MidDouble = 1.0; }
[DefaultValue(1.0)]
public double MidDouble { get; set; }
}
public class DerivedClass : MidClass
{
public DerivedClass() : base() { DerivedString = string.Empty; }
[DefaultValue("")]
public string DerivedString { get; set; }
}
public class VeryDerivedClass : DerivedClass
{
public VeryDerivedClass() : base() { this.VeryDerivedIndex = -1; }
[DefaultValue(-1)]
public int VeryDerivedIndex { get; set; }
}
The default XmlSerializer produces:
<VeryDerivedClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
But the custom serializer produces
<?xml version="1.0" encoding="utf-16"?>
<VeryDerivedClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Index>1</Index>
<MidDouble>1</MidDouble>
<DerivedString />
<VeryDerivedIndex>-1</VeryDerivedIndex>
</VeryDerivedClass>
Finally, note that writing of null values is controlled by [XmlElement( IsNullable = true )] so writing of nulls is not affected by this serializer.
Example how to force serialize all public properties with XmlDefaultValue attribute:
[Test]
public void GenerateXMLWrapTest()
{
var xmlWrap = new XmlWrap();
using (var sw = new StringWriter())
{
var overrides = new XmlAttributeOverrides();
var attrs = new XmlAttributes { XmlDefaultValue = null };
var type = typeof(XmlWrap);
foreach (var propertyInfo in type.GetProperties())
{
if (propertyInfo.CanRead && propertyInfo.CanWrite && propertyInfo.GetCustomAttributes(true).Any(o => o is DefaultValueAttribute))
{
var propertyNameWithDefaultToIgnore = propertyInfo.Name;
overrides.Add(type, propertyNameWithDefaultToIgnore, attrs);
}
}
var serializer = new XmlSerializer(type, overrides);
serializer.Serialize(sw, xmlWrap);
sw.Flush();
var xmlString = sw.ToString();
Console.WriteLine(xmlString);
}
}
Output:
<?xml version="1.0" encoding="utf-16"?>
<ConIdTranslator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:devices-description-1.0">
<Disabled>false</Disabled>
<HostPortParams>COM1 baud=115200 parity=None data=8 stop=One</HostPortParams>
<TranslatorObjectNumber>9000</TranslatorObjectNumber>
...
Where Disabled, HostPortParams, TranslatorObjectNumber public properties of serialized class has default value attribute:
[Serializable]
[XmlRoot("ConIdTranslator", Namespace = "urn:devices-description-1.0", IsNullable = false)]
public class ConIdTranslatorXmlWrap : HardwareEntityXmlWrap
{
#region Fields
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlIgnore]
private string hostPortParams = "COM1 baud=115200 parity=None data=8 stop=One";
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlIgnore]
private bool disabled = false;
...
#endregion
#region Properties
[XmlElement]
[DefaultValue(false)]
public bool Disabled
{
get => this.disabled;
set
{
this.disabled = value;
this.OnPropertyChanged("Disabled");
}
}
[XmlElement]
[DefaultValue("COM1 baud=115200 parity=None data=8 stop=One")]
public string HostPortParams
{
get => this.hostPortParams;
set
{
this.hostPortParams = value;
this.OnPropertyChanged("HostPortParams");
}
}
...
I found the answer:
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datamemberattribute.emitdefaultvalue%28v=vs.110%29.aspx
Set the attribute in the DataContract like this: [DataMember(EmitDefaultValue=true)]
I use C# 3 on microsoft .net 3.5 (VS2008).
I have a problem with de-serialization. I use DataContract and DataMember in a hierarchy of classes that I want to be serializable.
However, I also have polymorphism in one container, so I need to pass a list of known types to the serializers. My collection is a serializable dictionary that I found on the net:
[Serializable]
[XmlRoot("dictionary")]
public class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
if (reader.IsEmptyElement)
{
return;
}
// Move past container
if (!reader.Read())
{
throw new XmlException("Error in Deserialization of Dictionary");
}
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
// for serialization/deserialization pruporses
public void SetKnownTypes(Type[] extraTypes)
{
this.extraTypes = extraTypes;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
This is the one that holds a polymorphic object tree in its TVal.
So you see I have modified the original code to add a list of known types, which works well for serialization, because I set this list in my superior classes constructors. (the classes that holds the dictionary instance).
This list of known types happens to be discovered at runtime, using this function:
static public class TypeDiscoverer
{
public enum EFilter { All, OnlyConcreteTypes }
public enum EAssemblyRange { AllAppDomain, OnlyAssemblyOfRequestedType }
public static List<Type> FindAllDerivedTypes<T>(EFilter typesFilter, EAssemblyRange assembRange)
{
HashSet< Type > founds = new HashSet<Type>();
Assembly[] searchDomain =
assembRange == EAssemblyRange.OnlyAssemblyOfRequestedType ?
new Assembly[1] { Assembly.GetAssembly(typeof(T)) }
: AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in searchDomain)
{
founds = new HashSet<Type>(founds.Concat(FindAllDerivedTypes<T>(a, typesFilter)));
}
return founds.ToList();
}
public static List<Type> FindAllDerivedTypes<T>(Assembly assembly, EFilter typesFilter)
{
var derivedType = typeof(T);
List<Type> result = assembly
.GetTypes()
.Where(t =>
t != derivedType &&
derivedType.IsAssignableFrom(t)
).ToList();
if (typesFilter == EFilter.OnlyConcreteTypes)
result = result.Where(x => !x.IsAbstract).ToList();
return result;
}
}
This dynamic system allows me to discover the known types by just knowing the base class. Which is something I always wondered why do the framework does not provide this feature... but well..
So my issue is that, my serializable dictionary, is an utility class, I can not specialize it to hardcode the list of known types, even less so because it is discovered at run time.
Deserialization works on uninitialized object, and therefore I can not provide the list of known types to the dictionary de-serializer.
Of course, for the moment, I will workaround that problem by discovering the list of known types using my FindAllDerivedTypes functions on TVal directly in the dictionary.
But as it is less scalable than an exeternally-provided type list, I'd like to know if anyone can provide me with a real fix.
thanks a lot.
You can use custom XmlReader (or pass the types in some static / thread-local-storage variable).
IdeOne example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace DynaXmlSer {
public class KnownTypesXmlReader: XmlTextReader {
public KnownTypesXmlReader(Stream ios): base(ios) {}
public Type[] ExtraTypes = null;
}
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
public void SetKnownTypes(Type[] extraTypes) {
this.extraTypes = extraTypes;
valueSerializer = null;
keySerializer = null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) {
if (reader.IsEmptyElement)
return;
if (!reader.Read())
throw new XmlException("Error in Deserialization of Dictionary");
//HERE IS THE TRICK
if (reader is KnownTypesXmlReader)
SetKnownTypes(((KnownTypesXmlReader)reader).ExtraTypes);
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
}
public class BasicElement {
private string name;
public string Name {
get { return name; }
set { name = value; } }
}
public class ElementOne: BasicElement {
private string one;
public string One {
get { return one; }
set { one = value; }
}
}
public class ElementTwo: BasicElement {
private string two;
public string Two {
get { return two; }
set { two = value; }
}
}
public class Program {
static void Main(string[] args) {
Type[] extraTypes = new Type[] { typeof(ElementOne), typeof(ElementTwo) };
SerializableSortedDictionary<string, BasicElement> dict = new SerializableSortedDictionary<string,BasicElement>();
dict.SetKnownTypes(extraTypes);
dict["foo"] = new ElementOne() { Name = "foo", One = "FOO" };
dict["bar"] = new ElementTwo() { Name = "bar", Two = "BAR" };
XmlSerializer ser = new XmlSerializer(typeof(SerializableSortedDictionary<string, BasicElement>));
MemoryStream mem = new MemoryStream();
ser.Serialize(mem, dict);
Console.WriteLine(Encoding.UTF8.GetString(mem.ToArray()));
mem.Position = 0;
using(XmlReader rd = new KnownTypesXmlReader(mem) { ExtraTypes = extraTypes })
dict = (SerializableSortedDictionary<string, BasicElement>)ser.Deserialize(rd);
foreach(KeyValuePair<string, BasicElement> e in dict) {
Console.Write("Key = {0}, Name = {1}", e.Key, e.Value.Name);
if(e.Value is ElementOne) Console.Write(", One = {0}", ((ElementOne)e.Value).One);
else if(e.Value is ElementTwo) Console.Write(", Two = {0}", ((ElementTwo)e.Value).Two);
Console.WriteLine(", Type = {0}", e.Value.GetType().Name);
}
}
}
[Serializable]
[XmlRoot("dictionary")]
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
}
Output:
Key = bar, Name = bar, Two = BAR, Type = ElementTwo
Key = foo, Name = foo, One = FOO, Type = ElementOne
You can comment out the passing of the types and deserialization fails: using(XmlReader rd = new KnownTypesXmlReader(mem) /* { ExtraTypes = extraTypes } */)
ADDED COMMENTS:
I was searching for the solution in this order:
Use what is already there ([XmlInclude]) if you can. (Your runtime constraint disallows this.)
Customize some class involved - the problem was in void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) thus XmlReader was perfect candidate. I'd suggest using iterface for final solution (e.g. interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); })
If above fails (if you were not in control of the deserializer) use static variable (or thread-local-storage for use with multiple threads, or static synchronized(weak)dictionary) for additional configuration. (Not perfect and it seems that you can use option 2.)
First of all XmlSerializer ignores [Serializable], [NonSerialized], [DataContract] and [DataMember] attributes - it is controlled either by IXmlSerializable interface (which completely changes behavior of object serialization) or by default it serializes all public members/properties of an object, and you can give hints to XmlSerializer via attributes like [XmlRoot], [XmlAttribute], [XmlIgnore] [XmlArray], [XmlElement], [XmlArrayItem], [XmlInclude], [XmlText], etc.
The functionality you're after is already included in those attributes. Lets assume you have SerializableSortedDictionary<string, Car> where Car is class with subclasses Volvo and Audi.
[XmlInclude(typeof(Audi))]
[XmlInclude(typeof(Volvo))]
public class Car {
private string m_Name = "Car";
public virtual string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Audi : Car {
private string m_Name = "Audi";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Volvo : Car {
private string m_Name = "Volvo";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
All you need is to decorate the base class with all possible sub classes via the XmlInclude
var dic = new SerializableSortedDictionary<string, Car>();
dic.Add("0", new Car());
dic.Add("1", new Audi());
dic.Add("2", new Volvo());
var serializer = new XmlSerializer(typeof(SerializableSortedDictionary<string, Car>));
var builder = new StringBuilder();
using(var writer = new StringWriter(builder)) {
serializer.Serialize(writer, dic);
}
Deserialization works as well. You may notice xsi:type attribute in resulting xml - that how xml serializer persist information about types. It essentially impossible to "guess" type from serialized object without specifying it. It won't work for generic types, that weren't specified via XmlInclude - that's a security feature (if an attacker can make you make instance of any object he like when you're parsing xml feed you may run into serious troubles).