Search code examples
c#fileserializationbinarydeserialization

Is there any way to save and load a complex data structure to binary file in C#?


The problem

I am writing a program in C# which is use for save and load a list of object in specific structure like this:

SIGNATURE\r\n
SIZE\r\n
OBJECT_DATA\r\n
SIZE\r\n
OBJECT_DATA\r\n
\r\n

Signature is a primitive string that contain a private key to separate my data file with others file.

SIZE is an integer value present the number of bytes that OBJECT_DATA use.

OBJECT_DATA is a class that contain my data.

What I've tried

I've found many solution in Stackoverflow and almost all of them use BinaryFormatter which is insecure and should not be use.

I also took a look at protobuf-net but I still confused to apply it for my problem.

My Idea

I think the serialize and deserialize stage for the solution will look like this:

Serialize (assume that the input/output file is a binary file):

1. Open output file and write the signature.
2. Loop over the source object list
    - With each item, calculate the size of item and write it to output file.
    - Write object data to binary file. (I'm still confusing in this step)
    - Write "\r\n" to mark this is the end of the item.

Deserialize

1. Open input file and read the Signature
2. If the Signature fit with my secret key:
    - Loop until end of file
        - Read the SIZE value.
        - Read object and create instance of it's class.

Update

The OBJECT_DATA I mentioned is an instance of a class:

public abstract class IShape
{
    public State Configuration { get; set; }
    public abstract string Name { get; }
    public List<Point> Points { get; set; } = [];
    public abstract UIElement Draw();
    public abstract IShape Clone();
    public BitmapImage Preview { get; set; }
}

The structure of State Configuration:

public class State
{
    public DoubleCollection StrokeDashArray { get; set; }
    public SolidColorBrush Stroke { get; set; }
    public SolidColorBrush Fill { get; set; }
    public double StrokeThickness { get; set; }
}

I think Protobuf-net is a good way to serialize an object to Stream but my class contains some value from System.Windows.Media and I'm not sure if I can serialize it with using Protobuf-net.


Solution

  • I solve my own problem by combining IShape and State into a POCO class.

    public class ShapeProtocol
    {
        public string Name { get; set; }
        public string StartX { get; set; }
        public string StartY { get; set; }
        public string EndX { get; set; }
        public string EndY { get; set; }
        public string StrokeThickness { get; set; }
        public string Stroke { get; set; }
        public string? StrokeDashArray { get; set; }
        public string? Fill { get; set; }
    
        public ShapeProtocol() { }
    
        public ShapeProtocol(IShape shape)
        { /* Constructor */ }
    
        public static IShape ConvertBack(ShapeProtocol protocol)
        { /*...*/ }
    }
    

    Then I implement a ShapeSerializer:

    public static class ShapeSerializer
    {
        public static string Serialize(IShape shape)
        {
            var shapeProtocol = new ShapeProtocol(shape);
    
            var properties = from p in shapeProtocol.GetType().GetProperties()
                             where p.GetValue(shapeProtocol, null) != null
                             select p.Name + "=" + p.GetValue(shapeProtocol, null);
    
            // this string will be the OBJECT_DATA I mentioned. 
            // the result will look like this: Name=Ellipse;StartX=240;StartY=48.2;EndX=411.3;EndY=140;StrokeThickness=1;Stroke=#FFDC143C
            return String.Join(";", properties.ToArray());
        }
    
        public static IShape Deserialize(string rawData)
        {
            var _raw = rawData.Split(';').ToDictionary(p => p.Split("=")[0], p => p.Split("=")[1]);
            var deserialized = new ShapeProtocol();
    
            var _properties = typeof(ShapeProtocol).GetProperties();
            foreach (var property in _properties)
            {
                try
                {
                    var value = _raw[property.Name];
                    property.SetValue(deserialized, value, null);
                }
                catch { /* assume that value is null */ }
            }
    
            return ShapeProtocol.ConvertBack(deserialized);
        }
    }
    

    The workflow will like this: Workflow of Serialize/Deserialize

    Then I just use FileStream to store/load the data with the file structure in the very fist of this problem.

    Expressing my gratitude to @JonSkeet and @MarcGravell. Both of you have been a great source of inspiration in addressing this issue.