I need to use this method :
public T DeserializeFromXmlString<T>(string xmlString)
{
var serializer = new XmlSerializer(typeof(T));
using (TextReader reader = new StringReader(xmlString))
{
return (T)serializer.Deserialize(reader);
}
}
And to use it, I need to know the generic type T. Imagine that I have a class "Apple". I will use it like this:
var myapple= DeserializeFromXmlString<Apple>(someXmlString);
But I need to be able to remove this <Apple> and replacing it by something else considering that I have the string "Apple".The goal is to convert a string to be able to use it in this method as a generic type T.
Re-design your API to support non-generic cases:
public object DeserializeFromXmlString(Type targetType, string xmlString)
{
var serializer = new XmlSerializer(targetType);
using (TextReader reader = new StringReader(xmlString))
{
return serializer.Deserialize(reader);
}
}
public T DeserializeFromXmlString<T>(string xmlString)
{
return (T)DeserializeFromXmlString(typeof(T), xmlString);
}
Load type from string and use non-generic API:
var targetType = Type.GetType("YourTypeName", true);
var deserializedObj = DeserializeFromXmlString(targetType, yourXmlString);
Related
I have to load and deserialize an Xml file into an object. I can read the xml, get to the point where the object is described and parse the xml only from that part which is great, but there is a namespace declared in the root of the xml.
I don't understand why but when reading the xml, even though I read it from a given node, the xmlns attribute gets added to it, resulting in my program not being able to deserialize that into an object, due to the unexpected member.
My code:
public static SomeClass GetObjectFromXml (string path)
{
XmlReader reader = XmlReader.Create(path);
string wantedNodeContents = string.Empty;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "IWantThis")
{
wantedNodeContents = reader.ReadOuterXml();
break;
}
}
XmlSerializer xmlSerializer = new XmlSerializer(typeof(SomeClass));
System.IO.StringReader stringReader = new System.IO.StringReader(wantedNodeContents);
SomeClass loadedSomeClassXml = xmlSerializer.Deserialize(stringReader) as SomeClass;
return loadedSomeClassXml;
}
How could I get rid of the xmlns and deserialize the xml into an object?
You have a few issues here:
The default namespace attribute is added to the string returned by ReadOuterXml() because ReadOuterXml() is designed not to change the semantics of the returned XML. Apparently in your XML there is a default namespace applied to some parent node of <IWantThis> -- which, being a default namespace, recursively applies to <IWantThis> itself. To retain this namespace membership, ReadOuterXml() must emit a default namespace as it writes out the nested XML.
If you really want to completely ignore namespaces on XML, you need to create a custom XmlReader, e.g. as shown in
this answer to Can I make XmlSerializer ignore the namespace on deserialization? by Cheeso.
this answer to How do I create a XmlTextReader that ignores Namespaces and does not check characters by Alterant.
You need to construct an XmlSerializer for SomeClass whose expected root node is <IWantThis>. You can do this using the XmlSerializer(Type, XmlRootAttribute) constructor, however, if you do, you must statically cache and reuse the serializer to avoid a severe memory leak, as explained in Memory Leak using StreamReader and XmlSerializer.
You are creating a local copy wantedNodeContents of the element you want to deserialize, then re-parsing that local copy. There is no need to do this, you can use XmlReader.ReadSubtree() to deserialize just a portion of the XML.
Putting all these issues together, your GetObjectFromXml() could look like:
public static partial class XmlExtensions
{
public static T GetObjectFromXml<T>(string path, string localName, string namespaceURI, bool ignoreNamespaces = false)
{
using (var textReader = new StreamReader(path))
return GetObjectFromXml<T>(textReader, localName, namespaceURI);
}
public static T GetObjectFromXml<T>(TextReader textReader, string localName, string namespaceURI, bool ignoreNamespaces = false)
{
using (var xmlReader = ignoreNamespaces ? new NamespaceIgnorantXmlTextReader(textReader) : XmlReader.Create(textReader))
return GetObjectFromXml<T>(xmlReader, localName, namespaceURI);
}
public static T GetObjectFromXml<T>(XmlReader reader, string localName, string namespaceURI)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "IWantThis" && reader.NamespaceURI == namespaceURI)
{
var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceURI);
using (var subReader = reader.ReadSubtree())
return (T)serializer.Deserialize(subReader);
}
}
// Or throw an exception?
return default(T);
}
}
// This class copied from this answer https://stackoverflow.com/a/873281/3744182
// To https://stackoverflow.com/questions/870293/can-i-make-xmlserializer-ignore-the-namespace-on-deserialization
// By https://stackoverflow.com/users/48082/cheeso
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader): base(reader) { }
public override string NamespaceURI { get { return ""; } }
}
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;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
{
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
}
return serializer;
}
}
}
Demo fiddle here.
XDocument provides you a bit of more flexibility at time of deserialize any XML. I had a similiar problem and it was resolve using the next snippet code:
///Type T must have a default constructor
private T XMLToObject (string pathXML)
{
T myObjectParsedFromXML= default(T);
LoadOptions loadOpt = LoadOptions.SetLineInfo;
XDocument xmlDocument = XDocument.Load(pathXML , loadOpt);
string namespaceXML = xmlDocument.Root.Name.Namespace.NamespaceName;
XmlSerializer serializer = new XmlSerializer(typeof(T), defaultNamespace: namespaceXML);
XmlReader XMLreader = xmlDocument.CreateReader();
myObjectParsedFromXML= (T)serializer.Deserialize(XMLreader);
return myObjectParsedFromXML;
}
In addition, XmlSerializer provides you a set of events for register any issue or error during serialization process:
XmlSerializer serializer = new XmlSerializer(typeof(T), defaultNamespace: namespaceXML);
serializer.UnknownAttribute += new XmlAttributeEventHandler((sender, args) =>
{
//Your code for manage the errors during serialization
});
serializer.UnknownElement += new XmlElementEventHandler((sender, args) =>
{
//Your code for manage the errors during serialization
});
I am using XmlSerializer to serialize C# objects to XML. I have DefaultValueAttribute on some of the properties of the classes I am trying to serialize, when I try to serialize them it seems that XmlSerializer does not include value in xml if it equals default value.
Look at this example:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
public class Person
{
[System.Xml.Serialization.XmlAttribute()]
[System.ComponentModel.DefaultValue("John")]
public string Name { get; set; }
}
public static class Test
{
public static void Main()
{
var serializer = new XmlSerializer(typeof(Person));
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
}
}
}
It will produce the following xml (notice name attribute is not available):
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
I cannot modify the source code of the classes so I CANNOT remove DefaultValueAttribute. Is there a way to make XmlSerializer serialize this properties without changing the source code?
You can do it by passing in an XmlAttributeOverrides instance to the XmlSerializer when you create it as below. You can either change the default value to something else using this, or set it to null to effectively remove it.
XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();
var attributes = new XmlAttributes()
{
XmlDefaultValue = null,
XmlAttribute = new XmlAttributeAttribute()
};
attributeOverrides.Add(typeof(Person), "Name", attributes);
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
Update: the above means you have to provide other unrelated attributes again on each property you are changing. This is a bit of a chore if you have a lot of properties and just want to remove default for all of them. The class below can be used to preserve other custom attributes while only removing one type of attribute. It can be further extended if required to only do it for certain properties etc.
public class XmlAttributeOverrideGenerator<T>
{
private static XmlAttributeOverrides _overrides;
private static Type[] _ignoreAttributes = new Type[] { typeof(DefaultValueAttribute) };
static XmlAttributeOverrideGenerator()
{
_overrides = Generate();
}
public static XmlAttributeOverrides Get()
{
return _overrides;
}
private static XmlAttributeOverrides Generate()
{
var xmlAttributeOverrides = new XmlAttributeOverrides();
Type targetType = typeof(T);
foreach (var property in targetType.GetProperties())
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
xmlAttributeOverrides.Add(targetType, property.Name, propertyAttributes);
}
return xmlAttributeOverrides;
}
public class CustomAttribProvider : ICustomAttributeProvider
{
private PropertyInfo _prop = null;
private Type[] _ignoreTypes = null;
public CustomAttribProvider(PropertyInfo property, params Type[] ignoreTypes)
{
_ignoreTypes = ignoreTypes;
_prop = property;
}
public object[] GetCustomAttributes(bool inherit)
{
var attribs = _prop.GetCustomAttributes(inherit);
if (_ignoreTypes == null) return attribs;
return attribs.Where(attrib => IsAllowedType(attrib)).ToArray();
}
private bool IsAllowedType(object attribute)
{
if (_ignoreTypes == null) return true;
foreach (Type type in _ignoreTypes)
if (attribute.GetType() == type)
return false;
return true;
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
public bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
}
}
Usage:
XmlAttributeOverrides attributeOverrides = XmlAttributeOverrideGenerator<Person>.Get();
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
I dont have enough reputation to comment on steve16351's answer. but I feel I improved upon his code a little bit.
My use case involved an XML schema file generated via XSD.exe provided by Microsoft, and a class was then generated from the schema file. So the class had all the DefaultValue tags on it from the schema. There was also many nested types created. So to make my code succinct, I modified the 'Generate' method to get the overrides for the top-level class, and all the ones beneath it. Then everything is returned in a single XmlAttributesOverrides object.
static XmlAttributeOverrideGenerator()
{
_overrides = Generate(typeof(T), new XmlAttributeOverrides());
}
private static XmlAttributeOverrides Generate(Type targetType, XmlAttributeOverrides Overrides)
{
foreach (var property in targetType.GetProperties())
{
if (property.CustomAttributes.Any( CA => CA.AttributeType == typeof(DefaultValueAttribute)))
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
Overrides.Add(targetType, property.Name, propertyAttributes);
}
else
{
Type propType = property.PropertyType;
if (propType.IsClass && !propType.IsValueType && !propType.IsEnum && !propType.IsPrimitive && !propType.IsSealed) // Check if this is a custom class
{
//If this is a nested class or other class type, generate its overrides as well
Generate(propType, Overrides);
}
}
}
return Overrides;
}
I have an extension method for deserialization of an XDocument.
I use CarConfiguration as variable in this method, but I have another class configurations:
public static CarConfiguration Deserialize(this XDocument xdoc)
{
XmlSerializer serializer = new XmlSerializer(typeof(CarConfiguration));
using (StringReader reader = new StringReader(xdoc.ToString()))
{
CarConfiguration cfg = (CarConfiguration) serializer.Deserialize(reader);
return cfg;
}
}
class CarConfiguration
{
//car
}
class BikeConfiguration
{
//bike
}
So, there are 2 questions here:
Can I use class name as parameter for this method? Something like this:
static CarConfiguration Deserialize(this XDocument xdoc, class classname) {
var serializer = new XmlSerializer(typeof(classname));
Can I make this method generic for all required types(CarConfiguration, BikeConfiguration etc.)? I mean for example dependency of return type on input class:
static <classname> Deserialize(this XDocument xdoc, class classname) {
The key word in your question is generic, so yes you can do this by utilising C# generics. For example:
public static T Deserialize<T>(this XDocument xdoc)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringReader reader = new StringReader(xdoc.ToString()))
{
T cfg = (T) serializer.Deserialize(reader);
return cfg;
}
}
And now you call it like this:
CarConfiguration x = xmlDocument.Deserialize<CarConfiguration>();
I have a function that receive a dynamic object and a type as string.
I would like to cast the object to the type I have in my string.
public void PostAutomaticRule(dynamic automaticRuleObject, string ruleType)
{
switch (ruleType)
{
case "Increase_budget":
ConvertToAutomaticRule(typeof(IncreaseBudgetRule), ref automaticRuleObject);
break;
}
}
private void ConvertToAutomaticRule<T>(Type type, ref dynamic ruleObject)
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var json = serializer.Serialize(ruleObject);
var c = serializer.Deserialize<type>(json);
}
The class I am trying to convert to:
public class IncreaseBudgetRule
{
public string automaticRuleName { get; set; }
public string givePrioity { get; set; }
}
I have many rules types so I want that function to receive a type and a object and will return an object of the type I sent in the function.
How can I accomplish that?
You don't need the type in your ConvertToAutomaticRule-Method. You defined there a generic paramter which you can use as the type of the outcome. As the Deserialize-Method also accepts a generic Argument you can rewrite your methods like this:
public void PostAutomaticRule(dynamic automaticRuleObject, string ruleType)
{
switch (ruleType)
{
case "Increase_budget":
ConvertToAutomaticRule<IncreaseBudgetRule>(ref automaticRuleObject);
break;
}
}
private void ConvertToAutomaticRule<T>(ref dynamic ruleObject)
{
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(ruleObject);
var c = serializer.Deserialize<T>(json);
}
Edit (returning instead of using ref):
You can also use the generic parameter to set is as return type.
public void PostAutomaticRule(dynamic automaticRuleObject, string ruleType)
{
switch (ruleType)
{
case "Increase_budget":
var increasedBudgetRule = ConvertToAutomaticRule<IncreaseBudgetRule>(automaticRuleObject);
break;
}
}
private T ConvertToAutomaticRule<T>(dynamic ruleObject)
{
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(ruleObject);
return serializer.Deserialize<T>(json);
}
try to change your generics to interfaces and that way you can do something like:
var JsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var deserializedObject = JsonConvert.DeserializeObject<IYourAutoSerializedObject>(automaticRuleObject.ToString(), JsonSerializerSettings);
Error : 'object' does not contain a definition for 'lp' and no extension method >'lp' accepting a first argument of type 'object' could be found (are you missing >a using directive or an assembly reference?
If I try and get a value form my object I get this...
But When I run without trying to get the value, I can clearly see my object does contain lp...
Full code for Deserialize...
public object Deserialize(Object obj, string path)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType());
StreamReader reader = new StreamReader(path);
obj = serializer.Deserialize(reader);
reader.Close();
return obj;
}
Person class...
public class Person
{
public string name { get; set; }
public int age { get; set; }
}
PersonList class...
public class PersonList
{
public List<Person> lp = new List<Person>();
public void AddPerson(Person p)
{
lp.Add(p);
}
}
It's an instance of the Person List I am sending in to public class PersonList
{
public List<Person> lp = new List<Person>();
public void AddPerson(Person p)
{
lp.Add(p);
}
}.
UPDATE: I was doing casting before but I will be passing in loads of different types of objects so wanted a generic Deserialize function. is there a way to do this? –
You updated your question with a request for a generic cast and Generics is exactly what you're looking for.
public T Deserialize<T>(T obj, string path)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StreamReader reader = new StreamReader(path);
obj = (T)serializer.Deserialize(reader);
reader.Close();
return obj;
}
Your obj variable is declared as object instance. Do cast in Deserialize:
var obj = (PersonList)serializer.Deserialize(reader);
Your obj have type of object, so you can use only general methods of object class. You should cast obj to the type you need. For example:
obj = (PersonList)serializer.Deserialize(reader);
obj = serializer.Deserialize(reader) as PersonLis;
You can also use is operator to check if your obj belongs to PersonList class
You could write an extension method for the XmlSerializer to use generic deserialization:
public static class Extension
{
public static T Deserialize<T>(this XmlSerializer serializer, StreamReader streamReader)
{
try
{
return (T) serializer.Deserialize(streamReader);
}
catch (InvalidCastException)
{
return default(T);
}
}
}
Then call it by:
XmlSerializer serializer = new XmlSerializer(obj.GetType());
StreamReader reader = new StreamReader(path);
var deserialized = serializer.Deserialize<PersonList>(reader);
reader.Close();