Protobuf-net deserializing after defining inheritance - c#

I'm using protobuf-net version 2.0.0.640 to serialize some data as shown below.
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public interface ITestMessage
{
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrder : ITestMessage
{
public int Amount { get; set; }
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrderWrapper
{
public MyOrder Order { get; set; }
}
[TestMethod]
public void TestOrderSerialize()
{
var order = new MyOrder() {Amount = 10};
var orderWrapper = new MyOrderWrapper() { Order = order };
using (var file = File.Create("C:\\temp\\order.bin"))
{
Serializer.Serialize<MyOrderWrapper>(file, orderWrapper);
}
}
Now, If I declare an inheritance dependency between 'ITestMessage' & 'MyOrder' via code using:
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
I get the following error when trying to deserialize my prevously saved data.
"No parameterless constructor found for ITestMessage".
[TestMethod]
public void TestOrderDeserialize()
{
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
MyOrderWrapper orderWrapper;
using (var file = File.OpenRead("C:\\temp\\order.bin"))
{
orderWrapper = Serializer.Deserialize<MyOrderWrapper>(file);
}
}
Can someone please explain why this would happen when 'MyOrderWrapper' does not reference anything higher than 'MyOrder' in the inheritance hirarchy.
Also, why it works when I explictly include '[ProtoInclude(2, typeof(MyOrder))]' on 'ITestMessage'
Thanks

Basically, that is a breaking change as far as the serializer is concerned - at the wire layer, neither "classes" nor "interfaces" exist, so in terms of storage, this is akin to changing the base-type of the class; when serializing, the root type was MyOrder - and during deserialization the root type is ITestMessage. This will not make it happy.
Basically: you can't do that.

Related

How to work with 'generic xml root'

Some (external) genius decided to provide us with XML:
<message_X>
<header>
<foo>Foo</foo>
<bar>Bar</bar>
</header>
<body>
<blah>
<yadda1 />
<yadda2 />
<yadda3 />
<contentX>
<!-- message_X specific content -->
</contentX>
</blah>
</body>
</message_X>
However, there are also other messages (say, message_Y and message_Z). These all have the exact same basic structure besides what is in the content node and, the reason for this question, the differing root-nodes:
<message_Y>
<header>
<foo>Foo</foo>
<bar>Bar</bar>
</header>
<body>
<blah>
<yadda1 />
<yadda2 />
<yadda3 />
<contentY>
<!-- message_X specific content -->
</contentY>
</blah>
</body>
</message_Y>
Why the root node isn't just named <message>, as I would've done it baffles me. Who comes up with this?
I have created an abstract class Message accordingly:
public abstract class Message {
public Header Header { get; set; }
public Body Body { get; set; }
}
public class Header {
public string Foo { get; set; }
public string Bar { get; set; }
}
// Etc...
I was hoping I could then do this:
[XmlInclude(typeof(XMessage))]
[XmlInclude(typeof(YMessage))]
public abstract class Message {
// ...
}
[XmlRoot("message_X")]
public class XMessage : Message {
// ...
}
[XmlRoot("message_Y")]
public class YMessage : Message {
// ...
}
But that doesn't work: InvalidOperationException: <message_X xmlns=''> was not expected.. To deserialize I use:
var ser = new XmlSerializer(typeof(Message));
using (var sr = new StringReader(xmlString))
return (Message)ser.Deserialize(sr);
I have no control over the XML and I'm not looking forward to implement this message again and again for each X, Y and Z.
I'll sort the Content part out by probably making Message into Message<T> and inheriting with specifying T etc. but that'll be of later concern.
I have also tried specifying Message as Type and XMessage and YMessage as ExtraTypes of the XmlSerializer Constructor but that didn't help either. I have also tried to go the DataContractSerializer route with DataContract, KnownType etc. annotations but this didn't work either.
I would appreciate tips / pointers on how to solve this in a clean fashion.
With #steve16351's** ideas I wrote the following deserializer (I'm not interested in serializing, only deserializing):
public class XmlDeserializer<T>
where T : class
{
// "Globally" caches T => Dictionary<xmlelementnames, type>
private static readonly ConcurrentDictionary<Type, IDictionary<string, Type>> _typecache = new ConcurrentDictionary<Type, IDictionary<string, Type>>();
// We store instances of serializers per type T in this pool so we need not create a new one each time
private static readonly ConcurrentDictionary<Type, XmlSerializer> _serializers = new ConcurrentDictionary<Type, XmlSerializer>();
// And all serializers get the same instance of XmlReaderSettings which, again saves creating objects / garbage collecting.
private static readonly XmlReaderSettings _readersettings = new XmlReaderSettings() { IgnoreWhitespace = true };
// Lookup for current T, with this we keep a reference for the current T in the global cache so we need one less dictionary lookup
private readonly IDictionary<string, Type> _thistypedict;
public XmlDeserializer()
{
// Enumerate T's XmlInclude attributes
var includes = ((IEnumerable<XmlIncludeAttribute>)typeof(T).GetCustomAttributes(typeof(XmlIncludeAttribute), true));
// Get all the mappings
var mappings = includes.Select(a => new
{
a.Type,
((XmlRootAttribute)a.Type.GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault())?.ElementName
}).Where(m => !string.IsNullOrEmpty(m.ElementName));
// Store all mappings in our current instance and at the same time store the mappings for T in our "global cache"
_thistypedict = _typecache.GetOrAdd(typeof(T), mappings.ToDictionary(v => v.ElementName, v => v.Type));
}
public T Deserialize(string input)
{
// Read our input
using (var stringReader = new StringReader(input))
using (var xmlReader = XmlReader.Create(stringReader, _readersettings))
{
xmlReader.MoveToContent();
// Make sure we know how to deserialize this element
if (!_thistypedict.TryGetValue(xmlReader.Name, out var type))
throw new InvalidOperationException($"Unable to deserialize type '{xmlReader.Name}'");
// Grab serializer from pool or create one if required
var serializer = _serializers.GetOrAdd(type, (t) => new XmlSerializer(t, new XmlRootAttribute(xmlReader.Name)));
// Finally, now deserialize...
return (T)serializer.Deserialize(xmlReader);
}
}
}
This deserializer uses the XmlInclude attributes to figure out which classes are to be mapped with what element-name using the XmlRoot attributes. Usage couldn't be simpler:
var ser = new XmlDeserializer<Message>();
ser.Deserialize("<message_X>...");
It does some internal 'caching' and 'pooling' of objects to keep it memory/GC-friendly and quite performant. So this solves my root-node-different-name-for-each-type problem. Now I need to figure out how I'm going to handle the different content-nodes...
** Who has since deleted his answer for some unknown reason...
The second part of the question, the 'generic content' was easily solved:
public class Blah {
public Yadda1 Yadda1 { get; set; }
public Yadda2 Yadda2 { get; set; }
public Yadda3 Yadda3 { get; set; }
[XmlElement("contentX", typeof(ContentX))]
[XmlElement("contentY", typeof(ContentY))]
[XmlChoiceIdentifier(nameof(PayloadType))]
public Payload Payload { get; set; }
public PayloadType PayloadType { get; set; }
}
public abstract class Payload { ... }
public class ContentX : Payload { ... }
public class ContentY : Payload { ... }
[XmlType]
public enum PayloadType
{
[XmlEnum("contentX")]
ContentX,
[XmlEnum("contentY")]
ContentY,
}
This only leaves me wondering if I could apply the same idea on the root node...

Error in creating xml from an list of mock objects

I wanted to generate a sample xml so I write a unit test in which I have created an object using moq. I tried to serialize it like this:
private AssetDescription GetAssetDescription(string description, string type, string name, string iconUrl)
{
var asstDesp = new Mock<AssetDescription>(type);
asstDesp.Setup(m => m.Description).Returns(description);
asstDesp.Setup(m => m.Type).Returns(type);
asstDesp.Setup(m => m.Name).Returns(name);
asstDesp.Setup(m => m.IconUrl).Returns(iconUrl);
return asstDesp.Object;
}
Note: here AssetDescription is a class like this:
[DataContract]
public class AssetDescription
{
[DataMember]
public virtual string Type { get; set; }
[DataMember]
public virtual string Name { get; set; }
[DataMember]
public virtual string Description { get; set; }
[DataMember]
public virtual string IconUrl { get; set; }
public AssetDescription(string type)
{
Type = type;
}
public AssetDescription()
{
// I have added a parameter less constructor to xml serialization.
}
}
XML serialization method:
public string SerializeObject(object obj)
{
var xmlDoc = new XmlDocument();
var serializer = new XmlSerializer(obj.GetType());
using (var ms = new MemoryStream())
{
serializer.Serialize(ms, obj);
ms.Position = 0;
xmlDoc.Load(ms);
return xmlDoc.InnerXml;
}
}
Now I can serialize the AssetDescription successfully like this :
var ds = GetAssetDescription("Description1", "type1", "name1", "iconurl1");
var dsxml = SerializeObject(ds);
Problem: AssetDescription is a part of a list and that list is part of some other object I have created that object using moq. I have break down to this after some testing:-
I am not able to serialize a list of AssetDescription it is throwing error.
Here is my list creating method:
private List<AssetDescription> GetListAssetDescriptions()
{
var lst = new List<AssetDescription>
{
GetAssetDescription("Description1", "type1", "name1", "iconurl1"),
GetAssetDescription("Description2", "type2", "name2", "iconurl2"),
GetAssetDescription("Description3", "type3", "name3", "iconurl3"),
GetAssetDescription("Description4", "type4", "name4", "iconurl4")
};
return lst;
}
I tried to serialize it like this:
var fgh = GetListAssetDescriptions();
var fghd = SerializeObject(fgh);
but this error occurs:
There was an error generating the XML document
Questions:
can I generate xml from mock objects?
if yes, then does anybody know how to solve this error?
Not an answer, but I'm puzzled with this question. Let me ask you a few questions:
Why do you need mock there? AssetDescription is a POCO, not an interface. As far as your code goes, this class does not do anything. Why can't you just create a real object, not a mock?
If you need mock there, what is the purpose of serialisation to xml? what do you do with it later?
Mocks are for testing only. One must think really hard before adding a Moq reference to non-testing project. Mocks have quite complex internal structure - they are designed to pretend to be something they are not. XML serialisation was not part of design for these guys, so no surprise you can't serialise mock into XML. And I'll go that far and say that there is no work-around for this. Because mocks must not be serialised.

Serialize list of interface types with ServiceStack.Text

I'm looking at ways to introduce something other than BinaryFormatter serialization into my app to eventually work with Redis. ServiceStack JSON is what I would like to use, but can it do what I need with interfaces?
It can serialize (by inserting custom __type attribute)
public IAsset Content;
but not
public List<IAsset> Contents;
- the list comes up empty in serialized data. Is there any way to do this - serialize a list of interface types?
The app is big and old and the shape of objects it uses is probably not going to be allowed to change.
Thanks
Quoting from http://www.servicestack.net/docs/framework/release-notes
You probably don't have to do much :)
The JSON and JSV Text serializers now support serializing and
deserializing DTOs with Interface / Abstract or object types. Amongst
other things, this allows you to have an IInterface property which
when serialized will include its concrete type information in a __type
property field (similar to other JSON serializers) which when
serialized populates an instance of that concrete type (provided it
exists).
[...]
Note: This feature is automatically added to all
Abstract/Interface/Object types, i.e. you don't need to include any
[KnownType] attributes to take advantage of it.
By not much:
public interface IAsset
{
string Bling { get; set; }
}
public class AAsset : IAsset
{
public string Bling { get; set; }
public override string ToString()
{
return "A" + Bling;
}
}
public class BAsset : IAsset
{
public string Bling { get; set; }
public override string ToString()
{
return "B" + Bling;
}
}
public class AssetBag
{
[JsonProperty(TypeNameHandling = TypeNameHandling.None)]
public List<IAsset> Assets { get; set; }
}
class Program
{
static void Main(string[] args)
{
try
{
var bag = new AssetBag
{
Assets = new List<IAsset> {new AAsset {Bling = "Oho"}, new BAsset() {Bling = "Aha"}}
};
string json = JsonConvert.SerializeObject(bag, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto
});
var anotherBag = JsonConvert.DeserializeObject<AssetBag>(json, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto
});

Using Web API to deserialize into class with abstract property

I'm attempting to write a set of classes to represent a particularly complex object, and in one of those classes, I have a property that is set as the base (abstract) class of three possible derived classes. I'm setting up an ASP.NET Web API to handle the serialization and deserialization, which means that, by default, it uses Json.NET for JSON. How can I get the Web API to properly deserialize JSON sent via POST or PUT into the proper derived class?
The class with the abstract member looks like this (I'm including the Xml decorators for clarity and because they work perfectly well for deserializing xml using the XmlSerializer)
[Serializable]
public class FormulaStructure {
[XmlElement("column", typeof(ColumnStructure))]
[XmlElement("function", typeof(FunctionStructure))]
[XmlElement("operand", typeof(OperandStructure))]
public AFormulaItemStructure FormulaItem;
}
The abstract class is pretty basic:
[Serializable]
public abstract class AFormulaItemStructure { }
And there are three derivatives of the abstract class:
[Serializable]
public class ColumnStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("field")]
public string Field;
[XmlAttribute("display")]
public string Display;
}
[Serializable]
public class FunctionStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlAttribute("name")]
public string Name;
[XmlElement("parameters")]
public string Parameters;
}
[Serializable]
public class OperandStructure: AFormulaItemStructure {
[XmlAttribute("type")]
public string Type;
[XmlElement("left")]
public string Left;
[XmlElement("right")]
public string Right;
}
At present, using [DataContract] attributes, the Json.NET formatter fails to populate the derived class, leaving the property null.
Questions
Can I mix XmlSerializer attributes with DataContractSerializer attributes on the same class? I use the XmlSerializer because I use xml attributes in the xml I designed, but that can be changed if necessary since I am developing the xml schema myself.
What is the equivalent in Json.NET to [KnownType()] ? Json.NET doesn't appear to respect the DataContractSerializer version of KnownType. Will I need to roll my own JsonConverter to determine the proper type?
How would I decorate the classes so that DataContractSerializer or DataContractJsonSerializer will properly deserialize the objects in both Xml and Json? My goal is to put this into an ASP.NET Web API, so I want the flexibility to generate Xml or Json, as appropriate to the requested type. Is there an alternative formatter that I need to use to work with this complex class, if Json.NET won't work?
I need the ability to generate an object on the client side without necessarily including the .NET class names into the object.
Testing and Refinement
In my testing of the Web API, the default serialization sends down to the client:
{"FormulaItem":{"type":"int","field":"my_field","display":"My Field"}}
which is ideal for my purposes. Getting this to go back to the API and deserialize into the proper derived types, though, isn't working (it's generating null for the property).
Testing Tommy Grovnes answer below, the DataContractSerializer he used for testing generates:
{"FormulaItem":{"__type":"column:#ExpressionStructureExperimentation.Models","display":"My Field","field":"my_field","type":"int"}}
which doesn't work for me, or for code maintainability (refactoring becomes a PITA if I hard-code the entire namespace into the JavaScript for generating these objects).
You can mix as mentioned already but I don't think you need to, haven't used WEB api myself but WCF Rest produces xml and json from DataContracts (without Xml.. tags), tag your classes like this:
[DataContract]
public class FormulaStructure
{
[DataMember]
public AFormulaItemStructure FormulaItem;
}
[DataContract]
[KnownType(typeof(ColumnStructure))]
[KnownType(typeof(FunctionStructure))]
[KnownType(typeof(OperandStructure))]
public abstract class AFormulaItemStructure { }
[DataContract(Name="column")]
public class ColumnStructure : AFormulaItemStructure
{
[DataMember(Name="type")]
public string Type;
[DataMember(Name = "field")]
public string Field;
[DataMember(Name = "display")]
public string Display;
}
[DataContract(Name="function")]
public class FunctionStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "name")]
public string Name;
[DataMember(Name = "parameters")]
public string Parameters;
}
[DataContract(Name = "operand")]
public class OperandStructure : AFormulaItemStructure
{
[DataMember(Name = "type")]
public string Type;
[DataMember(Name = "left")]
public string Left;
[DataMember(Name = "right")]
public string Right;
}
If you need more control over the XML/JSON generated you might have to tweak this further. I used this code to test:
public static string Serialize(FormulaStructure structure)
{
using (MemoryStream memoryStream = new MemoryStream())
using (StreamReader reader = new StreamReader(memoryStream))
{
var serializer = new DataContractSerializer(typeof(FormulaStructure));
serializer.WriteObject(memoryStream, structure);
memoryStream.Position = 0;
return reader.ReadToEnd();
}
}
public static FormulaStructure Deserialize(string xml)
{
using (Stream stream = new MemoryStream())
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
stream.Write(data, 0, data.Length);
stream.Position = 0;
var deserializer = new DataContractSerializer(typeof(FormulaStructure));
return (FormulaStructure)deserializer.ReadObject(stream);
}
}
After we ran into some issues much further down the line with my previous answer, I discovered the SerializationBinder class that JSON can use for serializing/deserializing namespaces.
Code First
I generated a class to inherit the SerializationBinder:
public class KnownTypesBinder : System.Runtime.Serialization.SerializationBinder {
public KnownTypesBinder() {
KnownTypes = new List<Type>();
AliasedTypes = new Dictionary<string, Type>();
}
public IList<Type> KnownTypes { get; set; }
public IDictionary<string, Type> AliasedTypes { get; set; }
public override Type BindToType(string assemblyName, string typeName) {
if (AliasedTypes.ContainsKey(typeName)) { return AliasedTypes[typeName]; }
var type = KnownTypes.SingleOrDefault(t => t.Name == typeName);
if (type == null) {
type = Type.GetType(Assembly.CreateQualifiedName(assemblyName, typeName));
if (type == null) {
throw new InvalidCastException("Unknown type encountered while deserializing JSON. This can happen if class names have changed but the database or the JavaScript references the old class name.");
}
}
return type;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName) {
assemblyName = null;
typeName = serializedType.Name;
}
}
How it works
Let's say I have a set of classes defined thus:
public class Class1 {
public string Text { get; set; }
}
public class Class2 {
public int Value { get; set; }
}
public class MyClass {
public Class1 Text { get; set; }
public Class2 Value { get; set; }
}
Aliased Types
What this does is allows me to generate my own names for classes that will be serialized/deserialized. In my global.asax file, I apply the binder as such:
KnownTypesBinder binder = new KnownTypesBinder()
binder.AliasedTypes["Class1"] = typeof(Project1.Class1);
binder.AliasedTypes["WhateverStringIWant"] = typeof(Project1.Class2);
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Binder = binder;
Now, whenever I serialize, say, MyClass as JSON, I get the following:
{
item: {
$type: "Project1.MyClass",
Text: {
$type: "Class1",
Text: "some value"
},
Value: {
$type: "WhateverStringIWant",
Value: 88
}
}
}
Known Types
I can also choose to strip off the assembly information and strictly use the class name by adding information to the KnownTypesBinder:
KnownTypesBinder binder = new KnownTypesBinder()
binder.KnownTypes.Add(typeof(Project1.Class1));
binder.KnownTypes.Add(typeof(Project1.Class1));
In the two examples given, Class1 is referenced the same way. However, if I refactor Class1 to, say, NewClass1, then this second example will start sending a different name. That may or may not be a big deal, depending on whether you are using the types or not.
Final Thoughts
The advantage of the AliasedTypes is that I can give it any string that I want, and it doesn't matter how much I refactor the code, the communication between the .NET and the JavaScript (or whatever consumer is out there) is unbroken.
Be careful not to mix AliasedTypes and KnownTypes that have the exact same class name, because the code is written that the AliasType will win out over KnownType. When the binder doesn't recognize a type (aliased or known), it will provide the full assembly name of the type.
In the end, I broke down and added the .NET class information to the module in string variables to make refactoring easier.
module.net = {};
module.net.classes = {};
module.net.classes['column'] = "ColumnStructure";
module.net.classes['function'] = "FunctionStructure";
module.net.classes['operand'] = "OperandStructure";
module.net.getAssembly = function (className) {
return "MyNamespace.Models." + module.net.classes[className] + ", MyAssembly";
}
and generated the JSON as
{
"FormulaItem": {
"$type": module.net.getAssembly('column'),
"type": "int",
"field": "my_field",
"display": "My Field"
}
}

How to serialize a class with a property of type object filled with an array

After searching 99% of the net I am still stuck on the following matter. I have a web service which must comply to a wsdl that a partner company supplied. Calling a method of this service results in a (complex) class. Unfortunately a serialization error is raised when the service is called.
I have pinpointed the issue but cannot think of (and find) a solution to it. Because I'm dependant on the wsdl which was supplied, I cannot change the complex class structure. Hope anyone knows what I am missing. Here is example code to reproduce my issue:
[System.SerializableAttribute()]
public class MyObject
{
public int Id { get; set; }
public object Item { get; set; } // <---- Note type *object* here
}
[System.SerializableAttribute()]
public class MyItem
{
public int Id { get; set; }
public string Name { get; set; }
}
[TestClass]
public class SerializationTest
{
[TestMethod]
public void Serializing()
{
MyObject myObject = new MyObject { Id = 1 };
myObject.Item = new MyItem[] { new MyItem { Id = 1, Name = "Test" } };
string serializedString = SerializeObjectToXmlString(myObject, new []{ typeof(MyItem)});
Assert.IsFalse(String.IsNullOrWhiteSpace(serializedString));
}
/// <summary>
/// This method serializes objects to an XML string using the XmlSerializer
/// </summary>
private static string SerializeObjectToXmlString(object theObject, Type[] types)
{
using (var oStream = new System.IO.MemoryStream())
{
var oSerializer = new System.Xml.Serialization.XmlSerializer(theObject.GetType(), types);
oSerializer.Serialize(oStream, theObject); // <- Here the error is raised
return System.Text.Encoding.Default.GetString(oStream.ToArray());
}
}
}
In the Try/Catch an error is raised after calling method Serialize(). Details of this error are:
InvalidOperationException was unhandled by user code
- There was an error generating the XML document.
The type MyItem[] may not be used in this context.
My development context consists of Visual Studio 2010, .Net Framework 3.5.
Edit #1: Added Serialization attributes but the error remaines
It appears that you cannot add an array of types to an object and serialize it. The solution was to create a container class which - like the name says - contains the array. This way you can assign the container class to the object and serialize it all.
In addition to my case, I was mislead by the object model created by the wsdl.exe utility, since the container class is only a technical solution to add an array to an object. This container class was also created so everything was already there to use. Only after trying out my custom container class I noticed the already created container class. Lost a lot of time on this matter unfortunately...
You should mark you classes as
[Serializable]
public class MyObject
{
public int Id { get; set; }
public MyItem[] Item { get; set; } // <---- Note type *object* here
}
[Serializable]
public class MyItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Serialize uknown object (Item of MyObject class) you will need to do manually by implementing proper interfaces:
ISerializable and IDeserializationCallback, botha added to MyObject class.
This is an old question, but I had the same problem and found a different solution, so I thought I'd share in case it helps someone else.
I found that I could add attributes to allow arrays of specific types. For the problem above, the MyObject class could be edited as below:
[System.SerializableAttribute()]
public class MyObject
{
public int Id { get; set; }
[XmlElement(Type = typeof(object), ElementName = "Item"), //added
XmlElement(Type = typeof(MyItem[]), ElementName = "Item_asArrayOfMyItem")] //added
public object Item { get; set; } // <---- Note type *object* here
}
Anything that serialized before will still look the same, but now MyObject can be serialized even when Item has type MyItem[], as in the question's test case.

Categories