How to serialize the base class with derived classes - c#

.
Hello,
I have this sample code :
public class Vehicule
{
public string Name { get; set; }
public Brand Brand { get; set; }
}
public class Car : Vehicule
{
public string Matriculation { get; set; }
}
public class Brand
{
public string Name { get; set; }
}
public class Renault : Brand
{
public string Information { get; set; }
}
If I create this instance :
var car = new Car { Name = "Clio", Matriculation = "XXX-XXX", Brand = new Renault { Name = "Renault", Information = "Contact Infos" } };
When I serialize this object like that :
var serializer = new XmlSerializer(typeof(Car), new Type[] { typeof(Renault)});
serializer.Serialize(wr, car);
I obtain this :
<?xml version="1.0" encoding="utf-8"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Clio</Name>
<Brand xsi:type="Renault">
<Name>Renault</Name>
<Information>Contact Infos</Information>
</Brand>
<Matriculation>XXX-XXX</Matriculation>
</Car>
But, in my project, I don't have to have informations on derived classes, I would like only elements of base classes from this instance like this :
var serializer = new XmlSerializer(typeof(Vehicule));
serializer.Serialize(wr, car);
The Xml :
<?xml version="1.0" encoding="utf-8"?>
<Vehicule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Clio</Name>
<Brand>
<Name>Renault</Name>
</Brand>
</Vehicule>
Can you please, help me to obtain the good Xml (only with base type Vehicule and Brand) ?
Many thanks

You can't magically serialize a derived class as it's base because
"...Serialization checks type of instance by calling Object.getType()
method. This method always returns the exact type of object."
http://bytes.com/topic/net/answers/809946-how-force-serialize-base-type
The solution here, if you really need to only serialize the base class is to implement the IXmlSerializable interface and create your own custom serializer.
IXmlSerializable:
http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx
One more thought. If you can work around the limitation of outputting the extra XML elements, you are able to serialize the derived class using only the base object by either 1) using XmlIncludeAttributes on the base class to tell it which types to expect or 2) using the XmlSerializer constructor overload that takes a list of types.
Edit:
After thinking about this a little more, a workaround would be that you would add a Clone() method onto your base object, then serialize the clone of the base.
LinqPad code:
public class Vehicule
{
public string Name { get; set; }
public Brand Brand { get; set; }
public Vehicule Clone()
{
return new Vehicule { Name = this.Name, Brand = new Brand { Name = this.Brand.Name } };
}
}
public class Car : Vehicule
{
public string Matriculation { get; set; }
}
public class Brand
{
public string Name { get; set; }
}
public class Renault : Brand
{
public string Information { get; set; }
}
void Main()
{
var car = new Car { Name = "Clio", Matriculation = "XXX-XXX", Brand = new Renault { Name = "Renault", Information = "Contact Infos" } };
var vehicle = car as Vehicule;
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(Vehicule));
XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = new UnicodeEncoding(false, false),
Indent = false,
OmitXmlDeclaration = false
};
using(StringWriter textWriter = new StringWriter())
using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
serializer.Serialize(xmlWriter, vehicle.Clone());
textWriter.ToString().Dump();
}
}

This is one of the issues with inheritance, and another reason to favor composition imho.
I ran into the same issue on a mobile app where I had a Contact class that derives from ContactSummary. The repository returns Contact instances, but in lots of cases I only wanted the ContactSummary going over the wire to save on message sizes and data usage etc. The default Xml and Json serialisers would only work when the derived class was attributed with the [KnownType()] of the base class, but this still meant all those extra properties going over the wire.
Using inheritance it is problematic to achieve a viable solution, and I didn't want to resort to custom serialisers, and if the solution is to pollute the DTO with copy constructors and clone properties, then why not change the DTO to use composition instead?
If you have control over your DTOs, then restructuring them to use composition rather than inheritance may be the answer. In my example it was fairly simple...
public class ContactSummary
{
public string Name { get; set;}
public string Phone { get; set; }
}
public class Contact
{
public ContactSummary Summary { get; set; }
// ... other properties here
}
In your example, Car would need to contain a reference to Vehicle not inherit from it - something like...
[KnowTypes(typeof(Renault))]
public class Vehicle
{
public string Name { get; set; }
public Brand Brand { get; set; }
}
public class Car
{
public Vehicle Vehicle { get; set; }
public string Matriculation { get; set; }
}
Then when you want the 'base' type in your example, simply serialise Car.Vehicle.

I had the same problem and I got around it by re-mapping the inheriting class into the base class using AutoMapper:
MapperConfiguration config = new MapperConfiguration(cfg => cfg.CreateMap<Inheriting, Base>());
IMapper mapper = config.CreateMapper();
var baseObj = mapper.Map<Base>(InheritingObj);
There is not much you can customize on XmlSerializer out-of-the-box options.

Related

Using JsonDerivedType attribute to serialize and deserialize Polymorphic objects in .NET 7

JSON.NET (by Newtonsoft) has great support for serializing and deserializing complex objects.
I am curious about using System.Text.Json instead of JSON.NET and I am not able to find a good tutorial on the internet.
The .NET 7 preview has support for deserializing polymorphic objects. Here is an example using .NET 7 preview and C# 11:
// _To run it you will need net7 preview and c# 11_
using System.Text.Json.Serialization;
var testPolymorphism = new TestPolymorphysm()
{
Animals = new List<Animal>()
{
new Fish() {
Id = "fish1",
Name = "GoldFish",
Action = new ActionSwim() { DistanceSwam = 10 }
},
new Dog() {
Id = "dog1",
Name = "Tom",
Action = new ActionRun() { DistanceRan = 50 }
}
}
};
// serialize
var jsonSerialized = System.Text.Json.JsonSerializer.Serialize(testPolymorphism);
Console.WriteLine(jsonSerialized);
// deserialize
var clonePolymorphysm = System.Text.Json.JsonSerializer.Deserialize<TestPolymorphysm>(jsonSerialized);
Console.WriteLine(clonePolymorphysm);
// === MODELS ===
class TestPolymorphysm
{
public List<Animal> Animals { get; set; } = new();
}
[JsonDerivedType(derivedType: typeof(Dog), typeDiscriminator: "foo1")]
[JsonDerivedType(derivedType: typeof(Fish), typeDiscriminator: "foo2")]
abstract class Animal
{
public required string Id { get; set; }
public required string Name { get; set; }
}
class Dog : Animal
{
public required IAction Action { get; set; }
public AnimalType ExtensionType => AnimalType.Dog;
}
class Fish : Animal
{
public required IAction Action { get; set; }
public AnimalType ExtensionType => AnimalType.Fish;
}
[JsonDerivedType(derivedType: typeof(ActionSwim), typeDiscriminator: "foo3")]
[JsonDerivedType(derivedType: typeof(ActionRun), typeDiscriminator: "foo4")]
interface IAction { }
class ActionSwim : IAction
{
public required int DistanceSwam { get; set; }
}
class ActionRun : IAction
{
public required int DistanceRan { get; set; }
}
public enum AnimalType
{
Fish,
Dog
}
Anyways this code works thanks to JsonDerivedType attributes but I am not sure why it works. Why is it that if I remove the typeDiscriminators foo1, foo2, foo3 and foo4 it does not work? I want to make sure I understand how it works before I use it.
The process is described in the documentation: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0#polymorphic-type-discriminators
To enable polymorphic deserialization, you must specify a type discriminator for the derived class
... With the added metadata, specifically, the type discriminator, the serializer can serialize and deserialize the payload
... Serialization will emit JSON along with the type discriminator metadata
Essentially, the JsonDerivedTypeAttribute identifies supported derived types and adds the type discriminator $type as JSON metadata, which in turn instructs the serializer.
Sorry I was not paying attention to the serialized object. It contains: "$type": "foo1",, "$type": "foo2", etc..
That's why the deserializer knows how to deserialize the object.

Deserializing an object with protobuf-net with derived type for some field

There is some logic that stores data of type DataContainer<BaseClass> using protobuf-net to some repository. Lets say I need to create new DerivedClass, so all new instances of DataContainer<DerivedClass> would be serialized properly, but for purposes of back-compatibility I want to have option to deserialize some previously saved data as DerivedClass without changing any data in repository.
class Program
{
static void Main(string[] args)
{
var baseObject = new BaseClass()
{
Name = "some name"
};
using (var stream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(stream, new DataContainer<BaseClass>{ Data = baseObject, DataBase64 = "base class Base64"});
stream.Seek(0, SeekOrigin.Begin);
//System.InvalidCastException: 'Unable to cast object of type 'BaseClass' to type 'DerivedClass'.'
var result = ProtoBuf.Serializer.Deserialize<DataContainer<DerivedClass>>(stream);
}
}
}
public interface IDataInterface
{
}
[ProtoBuf.ProtoContract]
[ProtoInclude(1001, typeof(DerivedClass))]
public class BaseClass: IDataInterface
{
[ProtoBuf.ProtoMember(1)]
public string Name { get; set; }
}
[ProtoBuf.ProtoContract]
public class DerivedClass : BaseClass
{
[ProtoBuf.ProtoMember(1)]
public int Index { get; set; }
}
public interface IContainerInterface
{
IDataInterface Content { get; }
}
[ProtoContract]
public class DataContainer<T> : IContainerInterface where T: IDataInterface
{
[ProtoMember(1)]
public T Data { get; set; }
[ProtoMember(2)]
public string DataBase64 { get; set; }
public IDataInterface Content => this.Data;
}
Is there any way to deserialize DataContainer<DerivedClass> out of serialized DataContainer<BaseClass>? The other way is to use some higher-level converter after deserializing DataContainer<BaseClass> that would simply turn it to DataContainer<DerivedClass>, but it involves some property-coping, which I would like to avoid as much as possible
If the item you're serializing *was a DerivedClass, then I would expect this to already work. However, if you need to deserialize existing data that only knew about BaseClass, then you would need to deserialize a DataContainer<BaseClass> and then test to find that Data is actually a DerivedClass.

C# XML Serialization removing wrapper element

I wrote because I have problem with XmlSerializer. I want XML in the following format:
<?xml version="1.0" encoding="utf-8"?>
<RootXML>
<e-Invoice>
<Version>1.03</Version>
</e-Invoice>
<TradeInvoice>
<Id>1</Id>
<Value>100</Value>
</TradeInvoice>
<e-Invoice>
<Version>1.03</Version>
</e-Invoice>
<TradeInvoice>
<Id>2</Id>
<Value>200</Value>
</TradeInvoice>
<e-Invoice>
<Version>1.03</Version>
</e-Invoice>
<TradeInvoice>
<Id>3</Id>
<Value>300</Value>
</TradeInvoice>
</RootXML>
So I created the following classes.
[XmlRoot("RootXML")]
public class Root
{
public Root()
{
RootBodies = new List<RootBody>();
}
[XmlElement("e-Invoice")]
public List<RootBody> RootBodies { get; set; }
}
public class RootBody
{
public RootBody()
{
TradeInvoice = new TradeInvoice();
EInvoiceInfo = new Version(); ;
}
[XmlElement("e-Invoice")]
public Version EInvoiceInfo { get; set; }
[XmlElement("TradeInvoice")]
public TradeInvoice TradeInvoice { get; set; }
}
public class Version
{
[XmlElement("Version")]
public string Version { get; set; }
}
public class TradeInvoice
{
[XmlElement("Id")]
public int Id { get; set; }
[XmlElement("Value")]
public int Value { get; set; }
}
I have problem with removing wraper Elements (remove RootBody). I read similar topic like this link. But it does not solve my problem.
Before the actual explanation, let me point out a couple of very important things:
This XML is not very well designed and will cause a lot of other problems along the line (I'm guessing it is actually a lot more complicated than this).
The naming convention is inconsistent (e-Invoice and TradeInvoce)
The version number looks out of place, make sure this is really what they (whoever told you to do this) want before investing additional time.
This XML defines no namespaces (and probably doesn't have an XSD or DTD either)
Take a look at Google's XML design guidelines. You'll realize there is a lot of room for improvement.
There are a lot of different ways to do this, this is just one of them. I recommend you instead take it up with whoever is responsible for this design and try to change their mind.
Since you want to serialize e-Invoice and TradeInvoce without a wrapper element but still keep the order of elements (because they belong together) you need to make sure they have a common base class so they can be serialized from the same collection.
This abstract Invoice class simply tells the serializer which classes should be included during serialization via the XmlInclude attribute.
[XmlInclude(typeof(EInvoice))]
[XmlInclude(typeof(TradeInvoice))]
public abstract class Invoice
{
}
Your actual classes will be mostly unchanged
[XmlRoot("e-Invoice")]
public class EInvoice : Invoice
{
[XmlElement("Version")]
public string Version { get; set; }
}
[XmlRoot("TradeInvoice")]
public class TradeInvoice : Invoice
{
[XmlElement("Id")]
public int Id { get; set; }
[XmlElement("Value")]
public int Value { get; set; }
}
Your data class doesn't need any XML-related Attributes anymore because as it is now, it can't be serialized into that format directly.
public class InvoicePair
{
public InvoicePair(EInvoice eInvoice, TradeInvoice tradeInvoice)
{
TradeInvoice = tradeInvoice;
EInvoiceInfo = eInvoice;
}
public EInvoice EInvoiceInfo { get; set; }
public TradeInvoice TradeInvoice { get; set; }
}
Your Root class has to be simplified a bit. Since we want EInvoce and TradeInvoice Elements on the same level but mixed together, we need to put them in the same collection. The XmlElement attributes will tell the serializer how to handle elements of different types without relying on the xsi:type attribute.
[XmlRoot("RootXML")]
public class Root
{
public Root()
{
RootBodies = new List<Invoice>();
}
[XmlElement(Type = typeof(EInvoice), ElementName = "e-Invoice")]
[XmlElement(Type = typeof(TradeInvoice), ElementName = "TradeInvoice")]
public List<Invoice> RootBodies { get; set; }
}
Serialization is quite straight-forward at this point. Simply add all elements to the collection of elements one after another:
public static void Serialize(IEnumerable<InvoicePair> invoices, Stream stream)
{
Root root = new Root();
foreach (var invoice in invoices)
{
root.RootBodies.Add(invoice.EInvoiceInfo);
root.RootBodies.Add(invoice.TradeInvoice);
}
XmlSerializer serializer = new XmlSerializer(typeof(Root));
serializer.Serialize(stream, root);
}
Deserialization isn't very hard either, but very prone to erros and makes a lot of assumptions:
public static IEnumerable<InvoicePair> Deserialize(Stream stream)
{
XmlSerializer serializer = new XmlSerializer(typeof(Root));
Root root = serializer.Deserialize(stream) as Root;
for (int i = 0; i < root.RootBodies.Count; i += 2)
{
yield return new InvoicePair(
(EInvoice) root.RootBodies[i],
(TradeInvoice) root.RootBodies[i+1]
);
}
}
Here is a working Demo Fiddle, that outputs the XML

Mongo C#Driver:Deserialize BsonArray

I have a document in mongodb that is structured similar to this:
{
"_id":xxxxx,
"business":[{
"subBusiness":[{
"subBusinessName":"Abusiness",
"a":"aaaa"
},{
"subBusinessName":"Bbusiness",
"b":"bbbbb",
"c":"ccccc"
}]
}]
}
how to make a mapping class to serialize this document?
I also have a class defined to represent dimensions (the sub document from above)
class STObject{
[BsonId]
public ObjectId id{get;set;}
[BsonElement("business")]
public List<Business> BusinessList{get;set;}
}
class Business {
[BsonElement("subBusiness")]
public List<SubBusiness> SubBuiness { get; set; }
}
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(CSSubBusiness),typeof(ApproSubBusiness))]
public class SubBusiness {
[BsonElement("subBusinessName")]
public string SubBusinessName{get;set;}
}
public class AsubBusiness:SubBusiness{
[BsonElement("a")]
public string A{get;set;}
}
public class BsubBusiness:SubBusiness{
[BsonElement("b")]
public string B{get;set;}
[BsonElement("c")]
public string C{get;set;}
}
how to query element "b" in class STObject?
In order to deserialize class hierarchy, document should contain type discriminator field, which tells which type of subclass should be instantiated. By default this field has name _t. But if you already have documents with schema as above and can't change it, then you should override discriminator convention which is used by Mongo.
Looks like you can use subBusinessName field as type discriminator for sub business types. In order to do that, you should remove this field from base type:
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(AsubBusiness), typeof(BsubBusiness))] // btw check types
public class SubBusiness
{
}
And you should provide discriminator values for subtypes:
[BsonDiscriminator("Abusiness")] // provide discriminator value here
public class AsubBusiness : SubBusiness
{
[BsonElement("a")]
public string A { get; set; }
}
[BsonDiscriminator("Bbusiness")]
public class BsubBusiness : SubBusiness
{
[BsonElement("b")]
public string B { get; set; }
[BsonElement("c")]
public string C { get; set; }
}
And final step - create custom convention to make mongo look on this discriminator field for instantiating correct sub class type:
public class SubBusinessDiscriminatorConvention : IDiscriminatorConvention
{
public string ElementName
{
get { return "subBusinessName"; }
}
public Type GetActualType(BsonReader bsonReader, Type nominalType)
{
var bookmark = bsonReader.GetBookmark();
bsonReader.ReadStartDocument();
var actualType = nominalType;
if (bsonReader.FindElement(ElementName))
{
var discriminator = (BsonValue)BsonValueSerializer.Instance.Deserialize(bsonReader, typeof(BsonValue), null);
actualType = BsonSerializer.LookupActualType(nominalType, discriminator);
}
bsonReader.ReturnToBookmark(bookmark);
return actualType;
}
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
{
var classMap = BsonClassMap.LookupClassMap(actualType);
return classMap.Discriminator;
}
}
Now set this convention for your base type serialization:
BsonSerializer.RegisterDiscriminatorConvention(typeof(SubBusiness),
new SubBusinessDiscriminatorConvention());
And you can serialize and deserialize documents in your exact format.
UPDATE: Querying:
var collection = test.GetCollection<STObject>("collectionName");
var sto = collection.FindOne(Query.EQ("_id", new ObjectId("xxxxx")));
var businessList = sto.BusinessList.FirstOrDefault();
var bsub = businessList.SubBuiness.OfType<BsubBusiness>().FirstOrDefault();
var b = bsub.B; // returns bbbbb

XMLSerialization of base class not derived class

I have a base class that implements a number of properties I want to store, and a derived class that contains additional properties that I don't want to store (they can be quite large). I have a container class that contains a list of the base class, which in turn contains a mixture of instances of the base class and its derived class. I create an XMLSerializer for the container class, but on serialization it complains that the derived class isn't included.
Is there any way of forcing the serializer to output base class XML only, irrespective of the instance type?
Note, I don't want to use XMLInclude, as I specifically don't want any of the properties in the derived class to be stored.
(Simplified example of the code)
public class MyBase {
public String Title { get; set; }
}
public class MyDerived : MyBase {
public String Details { get; set; }
}
public class Container {
private static XmlSerializer sSerializer = new XmlSerializer(typeof(Container));
public List<MyBase> mBases { get; set; }
public void MyProblem() {
mBases = new List<MyBase>();
mBases.Add(new MyBase { Title = "One" });
mBases.Add(new MyDerived { Title = "Two", Details = "An incredibly long string" });
using (var lWriter = XmlWriter.Create("C:\\Temp\\output.xml")) {
sSerializer.Serialize(lWriter, this);
}
}
}

Categories