Search code examples
c#json.netfilestream

Can't overwrite after calling read when serializing DataTable in JSON.NET


I am writing a class to serialize/deserialize a class which includes a DataTable to a JSON file using JSON.net using C# .NET CORE v5 Visual Studio 2019. When the following conditions occur, the write operation doesn't overwrite, instead it appends the DataTable to the file.

  1. A read operation occurs before a write.
  2. The Class being serialized must include a DataTable.

Note: I've stripped out all the error handling and support functions to reduce the size of the code and then placed into a console application.

What I've tried...

  1. I discovered that commenting out the DataTable seems to resolve the problem.
  2. If removed the line "fs.Seek(0, SeekOrigin.Begin);" in the read operation. But as expected, this has no influence on the write operation.
  3. In the CloseFileStream() function, I added a call to GC.Collect() to ensure that GC is occuring, but no luck.
  4. The original function WriteJSONFile() used the fileStream. I changed it to a different method but the problem remains. See comments in that function.

When I run the code below, I expect a JSON file with the following contents.

{
    "Name": "Test Name",
    "dt": [
        {
            "Name": "Buckaroo Banzai",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Hoban Washburne",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Dr. Morbius",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        }
    ]
}

Instead I get this file, note that the DataTable is duplicated twice.

{
    "Name": "Test Name",
    "dt": [
        {
            "Name": "Buckaroo Banzai",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Hoban Washburne",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Dr. Morbius",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Buckaroo Banzai",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Hoban Washburne",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        },
        {
            "Name": "Dr. Morbius",
            "Num": 1,
            "Test,typeof(string)": "abcd"
        }
    ]
}
 public static class FileOperations
    { 

        private static FileStream fs = null;
        private static string currentPath = null;

        private static bool OpenFileStream(string path)
        {
            fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
            currentPath = path;
            return true;
        }


        public static bool ReadJSONFile<T>(string path, out T returnedInstanceOfClassFromJSONFile)
        {

            returnedInstanceOfClassFromJSONFile = default(T); //set to null

            bool bSuccessOpenFileStream = true;

            if (path != currentPath)
            {
                CloseFileStream();
                bSuccessOpenFileStream = OpenFileStream(path);
            }

            if (bSuccessOpenFileStream)
            {
                using (StreamReader reader = new StreamReader(fs, Encoding.UTF8, true, 4096, leaveOpen: true))
                using (JsonReader jreader = new JsonTextReader(reader))
                {
                    fs.Seek(0, SeekOrigin.Begin);
                    JsonSerializer serializer = new JsonSerializer();
                    returnedInstanceOfClassFromJSONFile = (T)serializer.Deserialize(jreader, typeof(T));
                }
                return true;
            }
            return false;

        }



        public static bool WriteJSONFile(string path, object objectToSerialize)
        {

            CloseFileStream();

            //I first tried using the streamwrite, but it has the same problem for example
            //using (StreamWriter sw = new StreamWriter(fs, ....)
            //using (JsonWriter writer = new JsonTextWriter(sw))
            //{
            //    serializer.Serialize(writer, objectToSerialize);
            //}
            File.WriteAllText(path, JsonConvert.SerializeObject(objectToSerialize));

            bool bSuccessOpenFileStream = OpenFileStream(path);

            return bSuccessOpenFileStream;

        }


        private static void CloseFileStream()
        {
            if (fs != null)
            {
                fs.Close();  
                fs = null;

                //shouldn't be requried, trying out of desperation
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
    }

Using the following code. Note that if you comment out the ReadJSONFile, the problem doesn't occur.

 static void Main(string[] args)
        {
            Simple simple = new Simple();

            string path = @"c:\somepath";
            FileOperations.WriteJSONFile(path, simple);
            FileOperations.ReadJSONFile(path, out simple); //comment this out and it works as expected
            FileOperations.WriteJSONFile(path, simple);
            FileOperations.WriteJSONFile(path, simple);
            FileOperations.WriteJSONFile(path, simple);
            FileOperations.WriteJSONFile(path, simple);
        }
    }


    public class Simple
    {
        public string Name { get; set; }
        public DataTable dt { get; set; }


        public Simple()
        {
            Name = "Test Name";

            dt = new DataTable();
            dt.Columns.Add("Name", typeof(string));
            dt.Columns.Add("Num", typeof(long));
            dt.Columns.Add("Test,typeof(string)");

            dt.Rows.Add("Buckaroo Banzai", 1, "abcd");
            dt.Rows.Add("Hoban Washburne", 1, "abcd");
            dt.Rows.Add("Dr. Morbius", 1, "abcd");
        }
    }



Solution

  • I think the situation happens on this line:

    FileOperations.ReadJSONFile(path, out simple);
    

    This line instantiates a Simple object

    This calls the constructor

    The constructor fills the datatable with 3 rows

    Then the ReadJSONFile method fills the object with data from the file - adding another 3 rows

    enter image description here