user94614
user94614

Reputation: 511

How to serialize object with stream in C#

I'm looking for a way to serialize an object with a stream to JSON to POST to an API. I continue to receive this error:

Newtonsoft.Json.JsonSerializationException: 'Error getting value from 'ReadTimeout' on 'System.IO.FileStream'.'

I understand what the error is saying but I don't understand what I need to implement to resolve it. Does it need to be converted to something else before JSON?

Here is my code:

Model:

[Serializable]
public class Document
{
    public string Number { get; set; }
    public string Revision { get; set; }
    public string FileName { get; set; }
    public Stream File { get; set; }
}

Building request:

public Document BuildRequest(string pdfFile, string txtFile)
{
    Document document = new Document();

    try
    {
        string data = File.ReadAllText(txtFile);
        string[] array = data.Split('|');

        FileStream fs = new FileStream(pdfFile, FileMode.Open);

        document.Number = array[0];
        document.Revision = array[1];
        document.FileName = file;
        document.File = fs;
    }
        // rest of code...
}

Post:

public void Post(Document document)
{
    var json = JsonConvert.SerializeObject(document);
    // rest of code in method...
}

Upvotes: 3

Views: 13918

Answers (2)

ColinM
ColinM

Reputation: 2681

After posting this I saw that this was answered already by Lasse in the comments, therefore this answer will serve as an alternative to achieving this

Here is an example which implements a custom JsonConverter that converts the File property to and from a Base64 string so it can be transferred over the network.

Some important points:

  1. You'll need to test this code in scenarios where you have a large PDF file
  2. You'll have to refactor this code to handle certain edge cases, should you identify any
  3. I have written this purely to answer your question of "can it be done", I have not considered any exceptions, edge cases or even network latency depending on how large the Base64 string will become - you will experience issues and limitations depending on the content size of the HTTP request.
  4. The API needs to know how to process this request, thus reading the Base 64 text as a stream.

Starting off, I created a StreamStringConverter

/// <summary>
/// Handles the (de)serialization of <see cref="Stream"/>.
/// </summary>
/// <remarks>
/// The <see cref="Stream"/> will be written as a Base64 encoded string, on the inverse it will be converted from a Base64 string to a <see cref="MemoryStream"/>.
/// </remarks>
public class StreamStringConverter : JsonConverter
{
    private static Type AllowedType = typeof(Stream);

    public override bool CanConvert(Type objectType)
        => objectType == AllowedType;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var objectContents = (string)reader.Value;
        var base64Decoded = Convert.FromBase64String(objectContents);

        var memoryStream = new MemoryStream(base64Decoded);

        return memoryStream;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var valueStream = (FileStream)value;
        var fileBytes = new byte[valueStream.Length];

        valueStream.Read(fileBytes, 0, (int)valueStream.Length);

        var bytesAsString = Convert.ToBase64String(fileBytes);

        writer.WriteValue(bytesAsString);
    }
}

You can decorate the appropriate members in your Document class to use this custom StreamStringConverter

public class Document
{
    public string Number { get; set; }
    public string Revision { get; set; }
    public string FileName { get; set; }

    // Specify a custom JsonConverter for our StreamJsonConverter
    [JsonConverter(typeof(StreamStringConverter))]
    public Stream File { get; set; }
}

Your model is now ready to begin serializing and deserializing, I have updated some of the code to use string in place of an actual file handle for txtFile, for simplicity.

static void Main(string[] args)
{
    Document document = new Document();

    const string file = "file";
    const string txtFileContents = "1|1.0";
    const string pdfFile = "myPdfFile.pdf";

    try
    {
        string[] array = txtFileContents.Split('|');

        FileStream fs = new FileStream(pdfFile, FileMode.Open);

        document.Number = array[0];
        document.Revision = array[1];
        document.FileName = file;
        document.File = fs;
    }
    catch (Exception exception)
    {
    }

    // Serialize the Document object
    // File, in the JSON contents, will be a Base64 encoded string
    var serializedContents = JsonConvert.SerializeObject(document);

    // Deserialize the contents
    // File will be a Stream
    var deserializedContents = JsonConvert.DeserializeObject<Document>(serializedContents);

    // For demo purposes, this will write the Document.File object back to a new PDF file for comparison
    using (var fileStream = File.Create("myDeserializedPdfFile.pdf"))
    {
        var fileAsMemoryStream = (MemoryStream)deserializedContents.File;
        fileAsMemoryStream.WriteTo(fileStream);
    }
}

Again I reiterate that I have not written this code to be production ready, that's up to you, this is simply to guide you in the right direction.

Upvotes: 4

Raziel
Raziel

Reputation: 11

Like the good fellows Lasse and Colin said in the comments: You can't write a filestream into Json. If you want to send the file into json you'll need to change it to some type of string or byte array.

Upvotes: 0

Related Questions