I am trying to dynamically serialize a List to Xml.
I am able to do so, as long I do not have a ICollection as a property of T.
I would like to dynamically overwrite the ICollection type into List before I write it to Xml.
This is what I have so far.
List<-XmlElementAttribute-> attrToConvertList = new List<-XmlElementAttribute->();
foreach (var propertyInfo in typeof(T).GetProperties())
{
if (propertyInfo.PropertyType.Name == "ICollection`1")
{
XmlElementAttribute attrToConvert = new XmlElementAttribute();
attrToConvert.ElementName = propertyInfo.Name;
attrToConvert.Type = typeof(List<>);
attrToConvert.Type = attrToConvert.Type.MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()[0]);
attrToConvertList.Add(attrToConvert);
}
}
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attributesToConvert = new XmlAttributes();
foreach (var xmlElementAttribute in attrToConvertList)
attributesToConvert.XmlElements.Add(xmlElementAttribute);
overrides.Add(typeof(T), attributesToConvert);
XmlSerializer serializer = new XmlSerializer(typeof(List<T>), overrides);
I get the error that I cannot serialize the type ICollection because it is an interface.
I was under the impression that what I was doing with the XmlAttributeOverrides was supposed to overwrite the ICollection to the type List.
XML serialization doesn't handle interfaces, and apparently XmlAttributeOverride doesn't allow you to bypass that behavior. You can change the type of your property, or make a type, for serialization only, where the property is a List<T>.
Example:
class RealClass
{
ICollection<int> SomeInts { get; set; }
}
class MySerializationClass
{
private readonly RealClass _wrappedObject;
public SerializationClass() : this(new RealClass()) { }
public SerializationClass(RealClass wrappedObject)
{
_wrappedObject = wrappedObject;
}
public List<T> SomeInts
{
get { return new List<T>(_wrappedObject.SomeInts); }
set { _wrappedObject.SomeInts = value; }
}
}
You could also do this with explicit interface member implementation, and use the interface in most of your code:
interface IHaveSomeInts
{
ICollection<int> SomeInts { get; set; }
}
class TheClass : IHaveSomeInts
{
public List<T> SomeInts { get; set; }
ICollection<T> IHaveSomeInts.SomeInts
{
get { return SomeInts; }
set { SomeInts = new List<T>(value); }
}
}
When assigning an ICollection<T> to an IList<T>, I would probably use as to see if I can just cast the object rather than creating a new one, to avoid creating lists needlessly.
I solved my original issue by using Newton.Json to serialize the object.
Related
I need to return a genericList templateFields as below from a generic list with code as below:
public interface TestData
{
string field { get; set; }
string fieldName { get; set; }
string type { get; set; }
}
private static IList<T> GETCG<T>(string test, string type) where T : Program.TestData
{
XmlNodeList extractNode = xdoc.SelectNodes(
#".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
var templateFields = nodees.Cast<XmlNode>().Select(x => new
{
field = (String)x.Attributes["userName"].Value,
fieldName = (String)x.Attributes["name"].Value
.Substring(0, x.Attributes["name"].Value.IndexOf(':')),
type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
.IndexOf(':') + 1, 4)
}).ToList();
}
return (T)Convert.ChangeType(templateFields, typeof(T));
I get the following error, on the return:
Object must implement Iconvertible.
I do understand templateFields doesnot implement IConvertible to use ChangeType. What's the best way of returning templateFields
Add new() contraint to T and use the following codee
private static IList<T> GETCG<T>(string test, string type) where T : TestData, new()
{
XmlNodeList extractNode = xdoc.SelectNodes(#".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
var templateFields = nodees.Cast<XmlNode>().Select(x => new T() //not anonymous type but T object
{
field = x.Attributes["userName"].Value,
fieldName = (string)x.Attributes["name"].Value.Substring(0, x.Attributes["name"].Value.IndexOf(':')),
type = x.Attributes["name"].Value.Substring(x.Attributes["name"].Value.IndexOf(':') + 1, 4)
}).ToList();
return templateFields;
}
I think the problem here is that you're selecting an anonymous type when you do select new { ... }, and then the Convert.ChangeType fails because anonymous types only include public read-only properties, and don't implement IConvertible. Instead, we want to select a new T. But in order to do this, we also have to include a new() constraint on T, which means that T must have a default constructor (so we can create an instance of it).
By doing this, we don't need to convert anything, as we have a List<T> as a result of the Select.
You can also reduce some code by selecting an IEnumerable<XmlNode> in one line, rather than creating a second variable and doing a cast on the first one.
Something like this should work:
private static IList<T> GETCG<T>(string test, string type) where T : TestData, new()
{
IEnumerable<XmlNode> templateFieldNodes = xdoc
.SelectNodes(".//mediaInstances/mediaInstance/properties/templateFields/templateField",
manager)
.Cast<XmlNode>();
return templateFieldNodes.Select(x => new T
{
field = (String)x.Attributes["userName"].Value,
fieldName = (String)x.Attributes["name"].Value
.Substring(0, x.Attributes["name"].Value.IndexOf(':')),
type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
.IndexOf(':') + 1, 4)
}).ToList();
}
You declared an interface TestData but didn't declare any type implementing it. You cannot cast any type that just happens to have the same properties by accident to this interface. You must create a class or struct implementing it. Also, with the usual .NET naming conventions interface names start with an upper case I and property names have PascalCase.
With these declarations ...
public interface ITestData
{
string Field { get; set; }
string FieldName { get; set; }
string Type { get; set; }
}
public class TestData : ITestData
{
public string Field { get; set; }
public string FieldName { get; set; }
public string Type { get; set; }
}
You can write
private static IList<ITestData> GETCG(string test, string type)
{
XmlNodeList extractNode = xdoc.SelectNodes(
#".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
var templateFields = nodees.Cast<XmlNode>().Select(x => (ITestData)new TestData {
Field = (String)x.Attributes["userName"].Value,
FieldName = (String)x.Attributes["name"].Value
.Substring(0, x.Attributes["name"].Value.IndexOf(':')),
Type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
.IndexOf(':') + 1, 4)
}).ToList();
return templateFields;
}
Note that the method is not generic. To make .ToList() create a IList<ITestData>, the new data must be casted to the interface (ITestData)new TestData { ... }.
The question is whether you still need the interface, or if you prefer to use the class directly.
If you still want the method to be generic, you must tell it that T must have a default constructor with the new() constraint. And you must call the method with a concrete type. I.e., you cannot call it with the interface, since this one does not have a constructor.
private static IList<T> GETCG<T>(string test, string type)
where T : ITestData, new()
{
...
var templateFields = nodees.Cast<XmlNode>().Select(x => new T {
...
}).ToList();
return templateFields;
}
and call with
IList<TestData> var result = GETCG<TestData>("hello", "world");
Say I have the following classes:
[DataContract]
class Entry<T>
{
[DataMember]
public T Data { get; set; }
}
[DataContract]
class DataList<T>
{
[DataMember]
public IList<T> Collection { get; set; }
}
[DataContract]
[KnownType(typeof(User))]
class ExtendedDataList : DataList<object>
{
[DataMember]
public string SomeExtraParameter { get; set; }
}
[DataContract]
class User
{
[DataMember]
public string Name { get; set; }
}
I've applied KnownTypeAttribute to ExtendedDataList since that class extends the base class of general type object, and it will store different kinds of objects in the list. In this example, I've marked a known type of User since I know it'll contain User objects.
Here's the serialization code:
var user = new User { Name = "Bob" };
var users = new ExtendedDataList { Collection = new List<object> { user } };
serialize(users);
with
static void serialize<T>(T obj)
{
var entry = new Entry<T>();
entry.Data = obj;
var stream = new MemoryStream();
var serializer = new DataContractJsonSerializer(typeof(Entry<T>));
serializer.WriteObject(stream, entry);
stream.Seek(0, SeekOrigin.Begin);
var r = new StreamReader(stream);
var s = r.ReadToEnd();
System.Diagnostics.Debug.WriteLine(s);
}
the line serializer.WriteObject(stream, entry); throws a SerializationException saying that the type User was not expected and that I should use KnownTypeAttribute to specify it. But I did (indirectly)!
How can I make this work? I cannot move KnownType to the Entry<T> class, because it needs to be general. Why can't DataContractJsonSerializer see that ExtendedDataList specifies the User as a known type?
I figured out a way to get it to work, but it still doesn't explain why DataContractJsonSerializer is ignoring the KnownType attribute I've applied to ExtendedDataList.
It seems that DataContractJsonSerializer isn't smart enough to notice the KnownType attributes that I've specified on the ExtendedDataList class; apparently it only honors the known types that I've attached to the Entry<T> class since typeof(Entry<T>) is the type that I give to the constructor of DataContractJsonSerializer
I want Entry<T> to remain general, and I do not want to litter it with all sorts of KnownType attributes. So instead, I use the following class definition:
[DataContract]
[KnownType("KnownTypes")]
class Entry<T>
{
[DataMember]
public T Data { get; set; }
public static IEnumerable<Type> KnownTypes()
{
var attrs = typeof(T).GetTypeInfo().GetCustomAttributes<KnownTypeAttribute>();
return attrs.Select(attr => attr.Type);
}
}
This uses reflection to get the KnownType attributes attached to the inner type T and then passes these types to whatever data contract serializer that serializes Entry<T>.
Note: requires using System.Linq and using System.Reflection.
I am using EF5 database first. In order to serialize a generated POCO class, I need to use the XMLIgnore attribute as follows
public partial class Demographics
{
public string KeyTract { get; set; }
public string CollectionYear { get; set; }
public string MSAFIPS { get; set; }
...
...
...
[XmlIgnore()]
public virtual ICollection<LoanApp> LoanApps { get; set; }
}
In order to keep from adding this each time I have EF re-create the model from the database, I added a "buddy class"
[MetadataType(typeof(Demographic_Metadata))]
public partial class Demographics
{
}
public class Demographic_Metadata
{
[XmlIgnore()]
public virtual ICollection<LoanApp> LoanApps { get; set; }
}
But when I attempt to serialize the Demographics object using XmlSerializer I get "There was an error reflecting type ...Demographics".
In researching SO it appears that XMLSerializer ignores the buddy class. But has anyone found a workaround to avoid adding the XMLIgnore attribute each time the POCO class is regenerated?
You can do this by using the XmlSerializer overload where you pass in the XmlAttributeOverrides. All we need to do is populate it via TypeDescriptor and make an ICustomAttributeProvider.
first the ICustomAttributeProvider is the simplest one, mainly because I'm just ignoring the inhert flag and always returning back all the attributes. The idea is that we will just pass in the attributes we want the XmlSerializer to know about.
public class CustomAttributeProvider : ICustomAttributeProvider
{
private readonly object[] _attributes;
public CustomAttributeProvider(params Attribute[] attributes)
{
_attributes = attributes;
}
public CustomAttributeProvider(AttributeCollection attributeCollection)
: this(attributeCollection.OfType<Attribute>().ToArray())
{
}
public object[] GetCustomAttributes(bool inherit)
{
return _attributes;
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return _attributes.Where(attributeType.IsInstanceOfType).ToArray();
}
public bool IsDefined(Type attributeType, bool inherit)
{
return _attributes.Any(attributeType.IsInstanceOfType);
}
}
Now I'm going to create a factory method to create the XMLAttributeOverrides.
We need to tell TypeDescriptor about the buddy class and that's what adding AssociatedMetadataTypeTypeDescriptionProvider to the provider list does.
From that we use TypeDescriptor to read the class attributes and the properties. Since buddy classes don't allow fields and TypeDescriptor doesn't read the fields either, so I use reflection for fields.
public static class BuddyClass
{
public static XmlAttributeOverrides CreateXmlAttributeOverrides(Type type)
{
// Tell TypeDescriptor about the buddy class
TypeDescriptor.AddProvider(new AssociatedMetadataTypeTypeDescriptionProvider(type), type);
var xmlAttributeOverrides = new XmlAttributeOverrides();
xmlAttributeOverrides.Add(type, new XmlAttributes(new CustomAttributeProvider(TypeDescriptor.GetAttributes(type))));
foreach (PropertyDescriptor props in TypeDescriptor.GetProperties(type))
{
if (props.Attributes.Count > 0)
{
xmlAttributeOverrides.Add(type, props.Name, new XmlAttributes(new CustomAttributeProvider(props.Attributes)));
}
}
foreach (var field in type.GetFields())
{
var attributes = field.GetCustomAttributes(true).OfType<Attribute>().ToArray();
if (attributes.Any())
{
xmlAttributeOverrides.Add(type, field.Name, new XmlAttributes(new CustomAttributeProvider(attributes)));
}
}
return xmlAttributeOverrides;
}
}
You would call it like
var serializer = new XmlSerializer(typeof(Test), BuddyClass.CreateXmlAttributeOverrides(typeof(Test)));
Then user serializer like you normally would. This doesn't handle the attributes on parameters or return values.
I've been struggling with this for a while now.
Here is my situation - one of the properties of the class that I'm trying to serialize contains an IEnumerable (which can't be serialized since it's an interface), I can't go and change the property (I can't add attributes to it either).
Here is the code I've got so far:
// Ignore the byte array containing the Data for the serialization
xmlAttrOverrides.Add(typeof (FileContent), "Data", xmlIgnoreAttr);
using (var writer = xmlDocument.CreateWriter())
{
var serializableTypes = GetSerializableSubTypes();
var serializer = new XmlSerializer(documentPackage.GetType(), xmlAttrOverrides, serializableTypes, null, null);
serializer.Serialize(writer, documentPackage);
}
Is there a way to setup the XmlSerializer so that every time it sees an IList or an IEnumerable to "convert" them to a List (or Array).
Any help would be highly appreciated.
Since you can't change the class, create a new class, inheriting from the class you want to serialize and from IXmlSerializable. In addition, you can override the base Colors array with the new keyword.
Try this one:
public class Something
{
public int Id { get; set; }
public string Text { get; set; }
public IEnumerable<string> Colors { get; set; }
}
public class MySerializableSomething : Something, IXmlSerializable
{
public new List<string> Colors { get; set; }
public MySerializableSomething()
{
Colors = new List<string>();
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
while (reader.Read())
{
switch (reader.LocalName)
{
case "Id": Id = reader.ReadElementContentAsInt(); break;
case "Text": Text = reader.ReadElementContentAsString(); break;
case "Color": Colors.Add(reader.ReadElementContentAsString()); break;
}
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("Id", Id.ToString());
writer.WriteElementString("Text", Text);
writer.WriteStartElement("Colors");
foreach (var color in Colors)
{
writer.WriteElementString("Color", color);
}
writer.WriteEndElement();
}
}
Here you can see I have an IEnumerable<string>. Normally wouldn't work.. so I wrap it and then serialize the wrapped one. If you need to, you can then convert it to the normal base class.. not the greatest solution, but since you said you can't change the base class, then there's not a lot of options here...
Write your own custom XmlSerializer class by implementing IXmlSerializable, refer msdn for its usage
When I try to serialize this collection, the name property is not serialized.
public class BCollection<T> : List<T> where T : B_Button
{
public string Name { get; set; }
}
BCollection<BB_Button> bc = new BCollection<B_Button>();
bc.Name = "Name";// Not Serialized!
bc.Add(new BB_Button { ID = "id1", Text = "sometext" });
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(bc);
Only if I create a new class (without List<t> inheritance), and define there string Name property and List<B_Button> bc = new List<B_Button>(); property I get the right result.
In many serializers (and data-binding, in fact), an object is either an entity or (exclusive) a list; having properties on a list is not commonly supported. I would refactor to encapsulate the list:
public class Foo<T> {
public string Name {get;set;}
private readonly List<T> items = new List<T>();
public List<T> Items { get { return items; } }
}
Also; how would you plan on representing that in JSON? IIRC the JSON array syntax doesn't allow for extra properties either.