Protobuf-net enum serialization behaviour changed in ver. 2.3.0 - c#

Any serialized object before 2.3.0 will not be correctly deserialized in 2.3.0 or later if it contains enum values and is using DataMember with InferTagFromNameDefault instead of ProtoMember.
[DataContract]
public class ClassWithEnum
{
[DataMember]
public MyEnum Enum { get; set; }
}
public enum MyEnum
{
FirstValue,
SecondValue
}
I have this class. Serializing it using this code.
{
RuntimeTypeModel.Default.InferTagFromNameDefault = true;
var v = new ClassWithEnum { Enum = MyEnum.SecondValue };
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, v);
var bytes = memoryStream.ToArray();
}
}
Before 2.3.0 this will result in a byte[] { 8, 2 }
and after 2.3.0 it will result in byte [] { 8, 1 }
Is there any way I can make later versions serialize the same way as the earlier ones?

This is ... unanticipated. I don't yet understand what happened here, but you're right that there was a change. It looks like historically there was an offset of 1 on the enum values when using InferTagFromNameDefault, and the "enum passthru" check fails to account for this (with "enum passthru" now evaluating to true here, where it would have been false previously).
The following seems to fix this at runtime:
RuntimeTypeModel.Default.Add(typeof(MyEnum), true).EnumPassthru = false;
or via attributes:
[ProtoContract(EnumPassthru = false)]
public enum MyEnum
{...}
I will investigate what happened here.

Related

Newtonsoft.Json Serializer strange behaviour with dynamic and decimals

I'm trying to cast dynamic structure which contains a decimal to a concrete class. I haven't been able to find a reasonable solution to that so I'm using "hacky" way by serializing the dynamic structure to JSON and then by deserializing it back to a concrete class. My first question is - Is there a better way to do that?
Actually I've noticed something really strange for me. After doing what I just explained I was trying to get string values out of those decimal values - a.ToString(CultureInfo.InvariantCulture);
To my surprise those strings were different! One was 10 and another 10.0. Can you explain why this happens? During debugging the valus both seem to be the same...
This is my code so you can easily check this:
using System.Globalization;
using Newtonsoft.Json;
using NUnit.Framework;
namespace SimpleFx.UnitTests.UnitTest
{
public class DecimalStruct
{
public DecimalStruct(decimal a)
{
A = a;
}
public decimal A { get; set; }
}
public class DynamicDecimalTest
{
/// <summary>
/// "Hacky" way of casting dynamic object to a concrete class
/// </summary>
public static T Convert<T>(dynamic obj) where T : class
{
var serialized = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject<T>(serialized);
}
[Test]
public void CastTest()
{
decimal a = 10;
dynamic s1 = new DecimalStruct(a);
var s2 = Convert<DecimalStruct>(s1);
Assert.AreEqual(a, s1.A);
Assert.AreEqual(a, s2.A);
Assert.AreEqual(a.ToString(CultureInfo.InvariantCulture), s1.A.ToString(CultureInfo.InvariantCulture));
Assert.AreEqual(a.ToString(CultureInfo.InvariantCulture), s2.A.ToString(CultureInfo.InvariantCulture)); // this fails because "10.0" is not equal "10"
}
}
}
Please consider the following example to convert dynamic to concrete
var typeName = "YourNamespace.ExampleObj";
object obj = new
{
A = 5,
B = "xx"
};
var props = TypeDescriptor.GetProperties(obj);
Type type = Type.GetType(typeName);
ExampleObj instance = (ExampleObj)Activator.CreateInstance(type);
instance.A = (int)props["A"].GetValue(obj);
instance.B = (string)props["B"].GetValue(obj);
//serialize the instance now...

protobuf-net: Deserializing onto an instance; setting property to null

I'm using protobuf-net's serialization library and my team came across an interesting use case. In our application, we're deserializing a byte array onto an existing instance. The existing instance has properties, some are nullable and some aren't. The issue that I'm facing is that if a nullable property on the existing instance has a value, but the serialized instance just received over the wire doesn't have a value for that property. Since Protocol Buffers doesn't have a concept of null, null isn't serialized into the byte array and thus the deserialize call doesn't overwrite the nullable property with null.
In case you couldn't follow that, I wrote a quick unit test to illustrate my point/question:
[TestFixture]
public class WhenSerializingNullableProperties
{
private RuntimeTypeModel _runtimeTypeModel;
[TestFixtureSetUp]
public void FixtureSetup()
{
_runtimeTypeModel = TypeModel.Create();
_runtimeTypeModel.Add(typeof (Foo), false).Add("Id", "Name");
}
internal class Foo
{
public string Name { get; set; }
public int? Id { get; set; }
}
[Test]
public void ShouldSetNullablePropertiesToNull()
{
var foo = new Foo { Name = "name", Id = null };
byte[] serialized;
using (var stream = new MemoryStream())
{
_runtimeTypeModel.Serialize(stream, security);
serialized = stream.ToArray();
}
foo.Id = 18;
foo.Name = "other";
using (var stream = new MemoryStream(serialized))
{
_runtimeTypeModel.Deserialize(stream, foo, typeof(Foo));
}
foo.Name.Should().Be("name"); // this PASSES
foo.Id.Should().NotHaveValue(); // this FAILS
}
}
I understand (and am grateful for) protobuf's compactness with excluding nulls. I don't want to throw in a default value and have my streams be larger. Instead, I'm looking for protobuf-net to set a nullable property to null if it's not included in the stream being deserialized.
Thanks in advance for any and all help provided.

Is it possible to serialize a custom struct as an xml attribute?

Is is possible to serialize a custom struct as an xml attribute?
Sample code:
public class Dummy<T>
{
private Dummy() { }
public Dummy(T item1)
{
Item1 = item1;
Item2 = item1;
}
public T Item1 { get; set; }
[XmlAttribute]
public T Item2 { get; set; }
}
public struct Meh
{
public int Prop { get; set; }
}
[Test]
public void XmlMehTest()
{
var meh = new Meh{Prop = 1};
var dummy = new Dummy<Meh>(meh);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
// System.InvalidOperationException : Cannot serialize member 'Meh2' of type Meh.
// XmlAttribute/XmlText cannot be used to encode complex types.
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
[Test]
public void XmlDateTimeTest()
{
var dummy = new Dummy<DateTime>(DateTime.Now);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
Please ignore that the struct is mutable, wrote it like that for a compact sample.
This is truly a first-world-developer-problem but I'm still curious :)
The documentation says:
You can assign the XmlAttributeAttribute only to public fields or public properties that return a value (or array of values) that can be mapped to one of the XML Schema definition language (XSD) simple types (including all built-in datatypes derived from the XSD anySimpleType type). The possible types include any that can be mapped to the XSD simple types, including Guid, Char, and enumerations.
So to do this, we should be able to create our own type definition for XSD,I guess we can do that.Because this documentation contains full explanation about it.But what we can't do is, we can't include our definition to this list.Initially XML Serializer uses these types to figure out your type's XSD type definition.You can use this attribute with DateTime because it's definition creating with this method and storing in a HashTable:
AddPrimitive(typeof(DateTime), "dateTime", "DateTime",
TypeFlags.XmlEncodingNotRequired |
TypeFlags.HasCustomFormatter |
TypeFlags.CanBeElementValue | **TypeFlags.CanBeAttributeValue**);
AddPrimitive method:
private static void AddPrimitive(Type type, string dataTypeName, string formatterName, TypeFlags flags)
{
XmlSchemaSimpleType dataType = new XmlSchemaSimpleType {
Name = dataTypeName
};
TypeDesc desc = new TypeDesc(type, true, dataType, formatterName, flags);
if (primitiveTypes[type] == null)
{
primitiveTypes.Add(type, desc);
}
primitiveDataTypes.Add(dataType, desc);
primitiveNames.Add(dataTypeName, "http://www.w3.org/2001/XMLSchema", desc);
}
And this definition calling from XmlReflectionImporter like this (which is generating the exception according to StackTrace):
this.GetTypeDesc(name, ns, TypeFlags.CanBeElementValue | TypeFlags.CanBeTextValue | TypeFlags.CanBeAttributeValue);
I guess most important thing is here TypeFlags.CanBeAttributeValue and I think it's specify that this type can be attibute value.So as a result maybe we can serialize custom structs as an XmlAttirube but we can't do it with standart XmlSerializer.Because as I said it's using this list to figure out XSD type definition.And it's an initial list and it's impossible to add new element to that list.
P.S. You might want take a look at here http://msdn.microsoft.com/en-us/library/8w07bk3h(v=vs.80).aspx

XmlSerializer - How can I set a default when deserializing an enum?

I have a class that looks like this (heavily simplified):
public class Foo
{
public enum Value
{
ValueOne,
ValueTwo
}
[XmlAttribute]
public Value Bar { get; set; }
}
I'm receiving an XML file from an external source. Their documentation states that the Foo element will only ever have "ValueOne" or "ValueTwo" in the Bar attribute (they don't supply an XSD).
So, I deserialize it like this:
var serializer = new XmlSerializer(typeof(Foo));
var xml = "<Foo Bar=\"ValueTwo\" />";
var reader = new StringReader(xml);
var foo = (Foo)serializer.Deserialize(reader);
... and that all works.
However, last night, they sent me some XML looking like this instead, and my deserialization failed (as it should):<Foo Bar="" />
Is there a good way to defensively code around this? Ideally I'd like to say something like "default to ValueOne, if something goes wrong here". I don't want to throw away the whole XML file, because a single attribute was mangled.
you can set you enum like this , so this will set "" value of enum to Unknown = 0 somewhat like default value
public enum Foo
{
[XmlEnum(Name = "")]
Unknown =0,
[XmlEnum(Name = "Single")]
One,
[XmlEnum(Name = "Double")]
Two
}
more detail check : XmlEnumAttribute Class
You can manually parse enum value by creating another property with the same XmlAttribute name:
public enum Value
{
// Default for unknown value
Unknown,
ValueOne,
ValueTwo
}
[XmlIgnore]
public Value Bar { get; set; }
[XmlAttribute("Bar")]
public string _Bar
{
get { return this.Bar.ToString(); }
set { this.Bar = Enum.TryParse(value, out Value enumValue) ? enumValue : Value.Unknown; }
}
Usage the same as your before
var serializer = new XmlSerializer(typeof(Foo));
var xml = "<Foo Bar=\"invalid value\" />";
var reader = new StringReader(xml);
var foo = (Foo)serializer.Deserialize(reader);
Shoot down XmlSerializer..
Use LINQ2XML for this simple task
XElement doc=XElement.Load("yourStreamXML");
List<Foo> yourList=doc.Descendants("Foo")
.Select(x=>
new Foo
{
Bar=(Enum.GetNames(typeof(Value)).Contains(x.Attribute("Bar").Value))?((this.Value)x.Attribute("Bar")):(this.Value.ValueOne);
}
).ToList<Foo>();
So,I am basically doing this
if(Enum.GetNames(typeof(Value)).Contains(x.Attribute("Bar").Value))
//if the xml bar value is a valid enum
Bar=(this.Value)x.Attribute("Bar");
else//xml bar value is not a valid enum..so use the default enum i.eValueOne
Bar=this.Value.ValueOne;

Serialization of enum fails

I use .NET 3.5 for this.
I have an enum:
[System.SerializableAttribute()]
public enum MyEnum
{
[System.Xml.Serialization.XmlEnumAttribute("035")]
Item1,
Item2
}
I use this enum in a class:
[System.SerializableAttribute()]
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public MyEnum MyEnum { get; set; }
}
Now I want to create a new Eplomyee-instance, set the MyEnum-property by casting it from a string.
Then serialize it and save it in a file.
Employee bob = new Employee() {Id = 1, Name = "Bob"};
bob.MyEnum = (MijnEnum)Enum.Parse(typeof(MyEnum), string.Format(CultureInfo.InvariantCulture, "{0}", "035"));
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
FileInfo fi = new FileInfo(#"C:\myfile.xml");
using (FileStream stream = fi.OpenWrite())
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings { Encoding = Encoding.UTF8, OmitXmlDeclaration = true, Indent = true };
using (XmlWriter writer = XmlWriter.Create(stream, xmlWriterSettings))
{
serializer.Serialize(writer, bob); // this is place where it goes wrong
}
}
If I debug this, I see that the value of bob.MyEnum is 35
When I try to serialize, I get an exception:
There was an error generating the XML document.
Instance validation error: '35' is not a valid value for
WindowsFormsApplication.MyEnum.
What is going wrong and how can I solve this?
Let's start:
[System.SerializableAttribute()] // useless, valuetype is implicitly so
public enum MyEnum
{
[System.Xml.Serialization.XmlEnumAttribute("035")]
Item1,
Item2
}
Now the XmlEnumAttribute controls how that value is serialized and deserialized in XML.
IT HAS NOTHING TO WITH THE REST OF YOUR CODE! (sorry for the caps, but no-one else seems to get this).
So when a value of MyEnum.Item1 get serialized, "035" will be emitted.
Now the problem is how you want to assign this.
It is simple. Just assign like you would normally do. None these attributes change semantics of normal code, everything stays the same.
Example:
Employee bob = new Employee() {Id = 1, Name = "Bob", MyEnum = MyEnum.Item1};
There is abolutely no reason why Enum.Parse should even be considered here. The enum type and value is statically known.
If you did want to use Enum.Parse, use it like normal, example:
Enum.Parse(typeof(MyEnum), "Item1")
This is happening because, Enums are internally store as int. Hence your statement bob.MyEnum = (MijnEnum)Enum.Parse(typeof(MyEnum), string.Format(CultureInfo.InvariantCulture, "{0}", "035")); is running without issue. If you debug, value of bob.MyEnum is 35. When you deserialize this, deserializer searches for matching enum with int value 35, which is not there, as you are specifying Item1 and Item2. Hence you get an error.
This will work
bob.MyEnum = (MyEnum)Enum.Parse(typeof(MyEnum), "35");
public enum MyEnum {
Item1 = 35,
Item2
}
Ideally you should be doing this
bob.MyEnum = (MyEnum)Enum.Parse(typeof(MyEnum), "Single");
public enum MyEnum {
[System.Xml.Serialization.XmlEnumAttribute("Single")]
Item1,
Item2
}
Hope this helps you.
I changed the enum-parsing.
I used reflection to parse the string to the enum, as described in this article: http://www.codeguru.com/csharp/csharp/cs_syntax/enumerations/article.php/c5869
And now it works.

Categories

Resources