Changing types during binary deserialization in C#
Asked Answered
M

2

5

One of the solutions in our company consumes a 3rd party service. Communication is done through XML messaging. On our end, we generate classes to be used based on XML schemas that they provide us, and at some point we serialize some of those types into a binary blob in our database for later use.

The problem comes in where that 3rd party company has changed one of the fields from a boolean to an integer type. Now when we try to deserialize the data that's already there we predictably get a type conversion exception (can't convert from boolean to integer).

My question is - how do we go about deserializing the existing data in our database with the old boolean type to convert it to the new integer type?

I've tried a number of things - among which were reflection and implementing ISerializable, but nothing's panned out so far. The ideal solution would be to implement ISerializable, but I get a "Member not found" error when trying to deserialize the existing data because it was already serialized using only the Serializable attribute.

Any suggestions are welcome!

Edit: Adding some code to clearly demonstrate my problem.

namespace ClassLibrary
{
    [Serializable]
    public class Foo //: ISerializable
    {
        public bool Bar { get; set; }

        public Foo() { }

        //[OnDeserializing()]
        //internal void OnDeserializingMethod(StreamingContext context)
        //{
        //    Bar = 10;
        //}

        //public Foo(SerializationInfo info, StreamingContext context)
        //{
        //    Bar = (int)info.GetValue("Bar", typeof(int));
        //}

        //public void GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //    info.AddValue("Bar", Bar);
        //}
    }
}

namespace ConsoleApplication2
{
    static class Program
    {
        static void Main(string[] args)
        {
            Foo foo;

            // Run #1, where Foo.Bar is a boolean

            foo = new Foo();
            foo.Bar = true;
            SerializeObject(foo);
            byte[] data = File.ReadAllBytes(@".\Test.bin");
            foo = DeserializeObject(data) as Foo;

            // Now change Foo.Bar to an integer type, comment the lines above, and uncomment the two lines below
            //byte[] newData = File.ReadAllBytes(@".\Test.bin");
            //foo = DeserializeObject(newData) as Foo;

            Console.WriteLine(foo.Bar);
            Console.ReadLine();
        }

        private static Object DeserializeObject(byte[] buff)
        {
            if (buff == null) return null;
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Binder = new CustomSerializationBinder();
            MemoryStream ms = new MemoryStream(buff);
            return formatter.Deserialize(ms);
        }

        private static void SerializeObject(Object obj)
        {
            if (obj == null) return;
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream ms = new FileStream(@".\Test.bin", FileMode.Create))
            {
                formatter.Serialize(ms, obj);
            }
        }
    }
Margarite answered 4/5, 2015 at 0:42 Comment(8)
Do you have access to the previous version of the DLL?Doan
You dont have access to your end?Rescind
Did the name of the field change? Or did they keep the same field name, but just changed the type? This matters, because from a serialization perspective, it would be a breaking change if they changed the type and kept the same field name. Ideally, they would have just added a new "int" field and left the old "bool" field alone and just somehow marked it as deprecated. If they did this as a breaking change, then I think your best bet is to keep two copies of the classes around ... one to deserialize old data, and one to deserialize new data.Pilau
Which serializer are you using -- XmlSerializer, DataContractSerializer, or something else?Eellike
well as you cannot touch the bin. serialization if something is on your DB right now you should deserialize the XML into another class - even if you cannot get your tools/frameworks to do this for you, you can always to it the hard way (using System.Xml and inspecting the nodes manually) - next time better either include a Version field into your binary serialized class or don't serialize into Binary (with the BinaryFormatter or what it's called)Blau
Re-reading your question, maybe you're using two different serializers: BinaryFormatter for storing to the database, and XmlSerializer for communications. Is that right?Eellike
Yes, we have access to the previous version of the dll (since we generate the code for it). The field name stayed the same, only the type changed. We're using a binaryformatter for serialization into the database. dbc that's exactly right. XmlSerializer is only for the communication layer. We store data in our db with the binaryformatter.Margarite
And serializing to xml isn't an option for us. For our application we need to preserve object references, and guids, etc. That's why we used binary in the first place. I realize we would've been better off including some versioning code originally, but I'm afraid I didn't write this bit of the app. I'm just the guy stuck dealing with it now.Margarite
E
8

You can handle this situation with ISerializable, however you will need to loop through the deserialized properties using SerializationInfo.GetEnumerator to determine what type of data was actually read from the stream. In addition, you need to be aware that BinaryFormatter serializes fields not properties, so if your class used Auto-Implemented Properties then the name previously stored in the binary stream will be the name of the backing field not the name of the property.

For instance, say this is your original class:

[Serializable]
public class Foo
{
    public bool Bar { get; set; }

    public Foo() { }
}

Now you want to change Bar to an integer. To serialize or deserialize both old and new BinaryFormatter streams, use the following implementation of ISerializable:

[Serializable]
public class Foo : ISerializable
{
    public int Bar { get; set; }

    public Foo() { }

    public Foo(SerializationInfo info, StreamingContext context)
    {
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;
            Debug.WriteLine(string.Format("{0} of type {1}: {2}", current.Name, current.ObjectType, current.Value));
            if (current.Name == "Bar" && current.ObjectType == typeof(int))
            {
                Bar = (int)current.Value;
            }
            else if (current.Name == "<Bar>k__BackingField" && current.ObjectType == typeof(bool))
            {
                var old = (bool)current.Value;
                Bar = (old ? 1 : 0); // Or whatever.
            }
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Bar", Bar);
    }
}
Eellike answered 4/5, 2015 at 21:35 Comment(3)
That's fantastic! I didn't realize SerializationInfo had an enumerator to get the serialized fields. I'll try an implementation tomorrow. BIG thanks if it works, I've been tearing my hair out for 3 days on this!Margarite
Yep this is exactly what I needed. SerializationInfo.GetEnumerator() was the missing link in the puzzle for me. Thank you SO much for taking the time dbc. You're good people.Margarite
This is nearly exactly what I need, thank you. Struggling to understand the documentation and how the deserialization process works though. Dumb question, I know, but this ignores any other properties besides Bar...how would one include other, non-type-converted props?Paraffin
S
0

If we convert boolean to integer, that would be either 0 or 1. Can you please try:

int val=int.Tryparse(myBooleanValue);

Or

int val= myBooleanValue?1:0;
Stig answered 4/5, 2015 at 2:7 Comment(5)
I think you misunderstood the question. I understand how to convert one to the other. The issue is how do I do that conversion with data that is already serialized in the database with a new dll that has the different type?Margarite
Do you want to change the data type in the serialized data which is saved in your databaseStig
It is hard to maintain if we change the data types in json or serialized stored data. You better write a console application , retrieve the data from DB and deserialize , convert from boolean to int and store it back to DB as serialized how it was before. I think it may be bad idea.Stig
Yes, what you're describing is EXACTLY what I want to do.. The question is - how? If I use the dll with the integer type, while the DB has the boolean type I can no longer deserialize the data.Margarite
Write a Job ( console application) retrieve the all the serialized data from DB and deserialize it. Now change the data type from boolean to integer and serialize it and store it back to the DB.Stig

© 2022 - 2024 — McMap. All rights reserved.