Invalid output with inherit class
Asked Answered
W

4

7

I have 2 classes

[DataContract, KnownType(typeof(B))]
public class A
{
    [DataMember]
    public string prop1 { get; set; }
    [DataMember]
    public string prop2 { get; set; }
    [DataMember]
    public string prop3 { get; set; }
}

[DataContract]
public class B : A
{
    [DataMember]
    public string prop4 { get; set; }
}

and the following method:

List<B> BList = new List<B>();
BList = new List<B>() { new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
List<A> AList = BList.Cast<A>().ToList();
DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));
FileStream fs = new FileStream(@"C:\temp\AResult.xml", FileMode.Create);
using (fs)
{
    ser.WriteObject(fs, AList);
}

which writes this to the outcoming XML file:

<ArrayOfProgram.A xmlns="http://schemas.datacontract.org/2004/07/foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Program.A i:type="Program.B">
<prop1>1</prop1>
<prop2>2</prop2>
<prop3>3</prop3>
<prop4>4</prop4>
</Program.A></ArrayOfProgram.A>

How could it happen, that prop4 is within the result and how can I avoid this? prop4 is not part of List<A> which is being serialized.

Whey answered 21/9, 2016 at 10:27 Comment(1)
prop4 is not a part of class A, but List<A> can store objects of type B, so when you serialize the elements of List<A> then the serializer will take the actual types stored (not just the element type of the list).Siliculose
I
5

That happens because you are storing in AList the pointer to the B object instance. When you did "new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" }" you created an B object instance. When the serializer reflects the object stored in AList, it finds an actual B object instance, because you didint change the B object instance, you only stored it in the AList. The compiler allowed you to do that because the inheritance chain permits it but the B object instance was not changed, then it is a B object instance no matters where you store it.

Instead of doing:

List<A> AList = BList.Cast<A>().ToList();

Do:

List<A> AList = BList.Select(b => new A() 
{ prop1 = b.prop1, prop2 = b.prop2, prop3 = b.prop3 })
.ToList();

That will create a new A instance for each B instance in BList

Indignity answered 21/9, 2016 at 10:52 Comment(2)
this works but there are a lot of properties - isn't there a easier way?Whey
For doing this property mapping easier, you could consider of using automapper. automapper.orgIndignity
H
1

If you haven't done it yourself yet as mentioned in one of the comments, here is a little code I fiddled. I haven't worked with AutoMapper for a while, so I couldn't remember and work out how to map List<T> types. Anyways, here is the fiddle:

var list = new List<B> { new B { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
Mapper.Initialize(i => i.CreateMap<B, A>());
using (var stream = new FileStream(@"output.xml", FileMode.Create))
{
    var serializer = new DataContractSerializer(typeof(List<A>));
    serializer.WriteObject(stream, list.Select(i => Mapper.Map<A>(i)).ToList());
}
Hofer answered 29/9, 2016 at 11:23 Comment(0)
K
1

The DataContractResolver allows you to customize how the DataContract can be resolved. In this case, you simply want the sub type to be resolved as the base type.

The following code is from this blog post.

https://blogs.msdn.microsoft.com/youssefm/2009/06/05/configuring-known-types-dynamically-introducing-the-datacontractresolver/

public class DeserializeAsBaseResolver : DataContractResolver
{
    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
    }

    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }
}

Depending on the framework, pass the above class into the datacontract serializer and it should give you the results you need.

DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));), null, Int32.MaxValue, false, false, null, new DeserializeAsBaseResolver ());
Krieg answered 29/9, 2016 at 19:16 Comment(0)
P
1

A simple way to downcast in C# is to serialize the child and then deserialize it into the parent.

List<B> BList = new List<B>();
BList = new List<B>() { new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
var serializedChildList = JsonConvert.SerializeObject(BList);
List<A> AList = JsonConvert.DeserializeObject<List<A>>(serializedChildList);
DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));
FileStream fs = new FileStream(@"C:\temp\AResult.xml", FileMode.Create);
using (fs)
{
    ser.WriteObject(fs, AList);
}

sample output:

<ArrayOfA xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <A>
    <prop1>1</prop1>
    <prop2>2</prop2>
    <prop3>3</prop3>
  </A>
</ArrayOfA>
Prompter answered 2/10, 2016 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.