0

I am attempting to save an object as serialized string and then I need to deserialize it back to an object at a later stage. I want to avoid using the file system.

The serializing is being done with this function:

  public string SerializeObject<T>(T objectToSerialize)
  {
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream memStr = new MemoryStream();
    try
    {
      bf.Serialize(memStr, objectToSerialize);
      memStr.Position = 0;
      return Convert.ToBase64String(memStr.ToArray());
    }
    finally
    {
      memStr.Close();
    }
  }

Which I got from here.

This appears to be working. However, when I attempt to deserialize to an object using the following functions, I am getting an error.

  public Call deSerialize(string Serialized)
  {
    Stream Fs = GenerateStreamFromString(Serialized);
    BinaryFormatter F = new BinaryFormatter();
    Call s1 = (Call)F.Deserialize(Fs);
    Fs.Close();
    return s1;
  }

  public Stream GenerateStreamFromString(string s)
  {
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(Convert.FromBase64String(s));
    writer.Flush();
    stream.Position = 0;
    return stream;
  }

The error I am getting is:

End of Stream encountered before parsing was completed.

And it is occurring on the line reading:

Call s1 = (Call)F.Deserialize(Fs);

Is there a way around this, or perhaps a better way to deserialize a string without using the file system?

Osprey
  • 1,493
  • 7
  • 27
  • 41
  • BTW, the `memStr.Position = 0;` in `SerializeObject` [is not required](https://docs.microsoft.com/en-us/dotnet/api/system.io.memorystream.toarray?view=net-5.0#System_IO_MemoryStream_ToArray) – MickyD May 26 '21 at 08:51
  • Does this answer your question? [Serialize an object to string](https://stackoverflow.com/questions/2434534/serialize-an-object-to-string) – Sinatr May 26 '21 at 09:23

2 Answers2

2

Disclaimer: I don't have anything against the use of Newtonsoft Json in general


The problem is that you are using StreamWriter to write the re-constructed bytes from Convert.FromBase64String to a MemoryStream.

MSDN: (my empasis)

Implements a TextWriter for writing characters to a stream in a particular encoding.

In your case this results in a much smaller buffer in MemoryStream thus leading to an exception later in Deserialize.

We can see the differences in sizes below:

MemStreamTest.exe Information: 0 : Position: 206 after save
MemStreamTest.exe Information: 0 : ToArray returned: 206 bytes
MemStreamTest.exe Information: 0 : Position: 13 after reconstruction

Change this:

public Stream GenerateStreamFromString(string s)
  {
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(Convert.FromBase64String(s));
    writer.Flush();
    stream.Position = 0;
    return stream;
  }

...to:

public Stream GenerateStreamFromString(string s)
{
    MemoryStream stream = new MemoryStream();
    var bytes = Convert.FromBase64String(s);
    stream.Write(bytes, 0, bytes.Length);
    Trace.TraceInformation($"Position: {stream.Position} after reconstruction");
    stream.Position = 0;
    return stream;
}


The following example of Call results in 206 bytes in each MemoryStream.

var call = new Call {PhoneNumber = "0812345678", Duration = TimeSpan.FromMinutes(5)};

[Serializable]
public class Call
{
    public TimeSpan Duration { get; set; }
    public string PhoneNumber { get; set; }
}

Results:

MemStreamTest.exe Information: 0 : Position: 206 after save
MemStreamTest.exe Information: 0 : ToArray returned: 206 bytes
MemStreamTest.exe Information: 0 : Position: 206 after reconstruction

Complete listing

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace MemStreamTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }

        private void Run()
        {
            var call = new Call {PhoneNumber = "0812345678", Duration = TimeSpan.FromMinutes(5)};
            var contents = SerializeObject(call);

            var other = deSerialize(contents);
        }

        public string SerializeObject<T>(T objectToSerialize)
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream memStr = new MemoryStream();
            try
            {
                bf.Serialize(memStr, objectToSerialize); // serialise as binary
                Trace.TraceInformation($"Position: {memStr.Position} after save");
                memStr.Position = 0;
                var bytes = memStr.ToArray();
                Trace.TraceInformation($"ToArray returned: {bytes.Length} bytes");
                
                return Convert.ToBase64String(bytes); // binary to Base64
            }
            finally
            {
                memStr.Close();
            }
        }

        public Call deSerialize(string Serialized)
        {
            Stream Fs = GenerateStreamFromString(Serialized);
            BinaryFormatter F = new BinaryFormatter();
            Call s1 = (Call)F.Deserialize(Fs);
            Fs.Close();
            return s1;
        }

        public Stream GenerateStreamFromString(string s)
        {
            MemoryStream stream = new MemoryStream();
            var bytes = Convert.FromBase64String(s);
            stream.Write(bytes, 0, bytes.Length);
            Trace.TraceInformation($"Position: {stream.Position} after reconstruction");
            stream.Position = 0;
            return stream;
        }
    }

    [Serializable]
    public class Call
    {
        public TimeSpan Duration { get; set; }
        public string PhoneNumber { get; set; }
    }
}

Final thoughts

Unless you really want a Base64 payload you would be advised to use Newtonsoft Json.

MickyD
  • 14,343
  • 6
  • 43
  • 67
  • Thanks @MickyD for the elaborate answer and for actually explaining what was wrong with my approach. I really appreciate it. However, Poiter 's solution does what I needed in two lines of code. – Osprey May 26 '21 at 09:47
  • 1
    @Osprey not a problem. I would recommend NewtonsoftJson too. However it's always good to know _what we were doing wrong else we never learn_. In this case that `StreamWriter` is for _text_ not binary – MickyD May 26 '21 at 10:37
0

You can use Newtonsoft Jsonconvert to do this out of the box:

var stringValue = JsonConvert.SerializeObject(entity);
var object = JsonConvert.DeserializeObject<T>(stringValue);
Poiter
  • 348
  • 1
  • 7
  • Works like a charm. Thanks! Two lines of code are way better than the functions I was attempting to use. – Osprey May 26 '21 at 09:14
  • Whilst answers can be of the form _['don’t do that...try this instead.](https://stackoverflow.com/help/how-to-answer)_ you have also changed the underlying format from Base64 to JSON – MickyD May 26 '21 at 10:52