Why can I no longer deserialize this data? - c#

I am using the following code to serialize some data and save it to file:
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer((typeof(Item)));
Item item = ((Item)list.SelectedItems[0].Tag);
serializer.WriteObject(stream, item);
var filepath = Program.appDataPath + list.SelectedItems[0].Group.Name + ".group";
stream.Position = 0;
using (FileStream fileStream = new FileStream(filepath, FileMode.Create))
{
stream.WriteTo(fileStream);
}
And later on, I'm trying to read back that data from file and insert it into ListView:
private void OpenFiles()
{
// DEBUG ONLY:
// Read into memorystream and filestream.
Console.WriteLine("Attempeting to open note.");
bool canLoad = false;
foreach (string file in Directory.GetFiles(Program.appDataPath))
{
if (file.EndsWith(".group"))
{
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(
typeof(
List<Item>
)
);
using (FileStream fileStream =
new FileStream(
file,
FileMode.Open)
)
{
fileStream.CopyTo(stream);
}
stream.Position = 0;
//List<Withdrawal> tempWithList = new List<Withdrawal>();
foreach (Item item in (List<Item>)serializer.ReadObject(stream))
{
Console.WriteLine(item.Title + " " + item.Group.Name);
Item.Items.Add(item);
}
//Console.WriteLine("Got file \{file}");
//if (file.EndsWith(".group"))
//{
// Console.WriteLine("File is a group.");
// MemoryStream stream = new MemoryStream();
// DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<Item>));
// using (FileStream fileStream = new FileStream(file, FileMode.Open))
// {
// fileStream.CopyTo(stream);
// }
// Console.WriteLine("Got stream");
// stream.Position = 0;
// try
// {
// Item.Items = (List<Item>)serializer.ReadObject(stream);
// Console.WriteLine("WTF?");
// }
// catch(Exception exception)
// {
// Console.WriteLine(exception.Message);
// }
// Console.WriteLine(Item.Items.Count);
// canLoad = true;
//}
//else Console.WriteLine("File is not a group.");
}
if (canLoad)
{
//list.Items.Clear();
foreach (Item item in Item.Items)
{
ListViewGroup group = new ListViewGroup(item.Group.Name);
list.Groups.Add(group);
list.Items.Add(
new ListViewItem(
item.Title,
group)
);
Console.WriteLine(item.Title + " " + item.Group.Name);
}
}
}
}
Now, the above exact code works in an older program (few months old), but it's not working in a new program. I have no idea why. I have set breakpoints EVERYWHERE and it has proven to be kind of pointless in this case.
One thing I did learn from setting a breakpoint is that even though the stream contains the data expected, the very next second, when it gets added to list, it is NULL. There is nothing in the list. I've run out of ideas, and Google wasn't of much help.
Group.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Notes
{
[DataContract]
public class Group
{
[DataMember]
public string Name { get; set; }
}
}
Item.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Notes
{
[DataContract]
[Serializable]
public class Item : Note
{
[DataMember]
public static List<Item> Items = new List<Item>();
[DataContract]
public enum ItemType
{
Group,
Note
}
[DataMember]
public ItemType Type { get; set; }
[DataMember]
public int Index { get; set; }
}
}
Note.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Notes
{
[DataContract]
public class Note
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Content { get; set; }
[DataMember]
public Group Group;
[DataContract]
public enum NoteImportance
{
Important,
Neutral,
NotImportant
}
[DataMember]
public NoteImportance Importance { get; set; }
[DataMember]
public bool Protected { get; set; }
}
}
How can I deserialize these objects/read from file and get them into a List or ListView? I've already done this, but for some reason it's not working anymore.
Any help would be appreciated.

When you create a .group file, you serialize a single Item:
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Item));
// And later
serializer.WriteObject(stream, item);
But when you deserialize the contents of a .group file, you try to deserialize a List<Item>:
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<Item>));
// And later
foreach (Item item in (List<Item>)serializer.ReadObject(stream))
{
Item.Items.Add(item);
}
Those types don't match. But in order to deserialize the data you previously serialized, they need to match - or at least, the deserialized type cannot be a collection if the serialized type was, because collections are serialized as JSON arrays while other classes are serialized as JSON objects (name/value pairs).
Since it looks like each .group file has a single item, and there are many .group files in the directory you are scanning, you probably just want to do
var serializer = new DataContractJsonSerializer(typeof(Item));
// And later
var item = (Item)serializer.ReadObject(stream);
if (item != null)
Item.Items.Add(item);

Related

How to serialize different objects to the same XML file and then deserialize

When a user uses my program, it will generate many thousands of objects over the course of a couple of hours. These cannot accumulate in RAM, so I want to write them to a single file as they are generated. Then, in a different program, the objects must all be deserialized.
When I try to serialize different objects of the same class to the same XML file and then try to deserialize later, I get:
System.InvalidOperationException: 'There is an error in XML document (1, 206).'
Inner Exception
XmlException: There are multiple root elements. Line 1, position 206.
Here is an example of a .NET 6.0 console app that recapitulates this problem:
using System.Xml.Serialization;
using System.IO;
using System.Xml;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
// Serialize a person
using (FileStream stream = new FileStream("people.xml", FileMode.Create))
{
Person jacob = new Person { Name = "Jacob", Age = 33, Alive = true };
XmlSerializer serializer = new XmlSerializer(typeof(Person));
serializer.Serialize(stream, jacob);
}
// Serialize another person to the same file using the "clean XML" method
// https://stackoverflow.com/questions/1772004/how-can-i-make-the-xmlserializer-only-serialize-plain-xml
using (StreamWriter stream = new StreamWriter("people.xml", true))
{
Person rebecca = new Person { Name = "Rebecca", Age = 45, Alive = true };
stream.Write(SerializeToString(rebecca));
}
// Deserialize the people
List<Person> people = new List<Person>();
using (FileStream stream = new FileStream("people.xml", FileMode.Open))
{
XmlSerializer deserializer = new XmlSerializer(typeof(Person));
while (stream.Position < stream.Length)
{
people.Add((Person)deserializer.Deserialize(stream));
}
}
// See the people
foreach (Person person in people)
Console.WriteLine($"Hello. I am {person.Name}. I am {person.Age} and it is {person.Alive} that I am alive.");
}
// Serialize To Clean XML
public static string SerializeToString<T>(T value)
{
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var serializer = new XmlSerializer(value.GetType());
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, value, emptyNamespaces);
return stream.ToString();
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public bool Alive { get; set; }
public Person()
{
Name = "";
}
}
}
Probably not the perfect answer, but could you de-serialize to a list, append your new person, and then re-serialize?
public static List<Person> ReadPeople(string file)
{
var people = new List<Person>();
if (File.Exists(file))
{
var serializer = new XmlSerializer(typeof(List<Person>));
using (var stream = new FileStream(file, FileMode.Open))
{
people = (List<Person>)serializer.Deserialize(stream);
}
}
return people;
}
public static void SavePerson(string file, Person person)
{
var people = ReadPeople(file);
people.Add(person);
var serializer = new XmlSerializer(typeof(List<Person>));
using (var stream = new FileStream(file, FileMode.Create))
{
serializer.Serialize(stream, people);
}
}
Additionally, on the "FileMode.Create", is the "FileMode.Append" option. The problem with that is it will append lists next to each other, rather than nesting the "people"
NB - I've split this into two functions to give the flexibility of being able to load the file easily

Serializing Arrays in C#

Is there a way to Serialize an entire array in C# such as:
[Serializable()]
public class Data
{
public short Some_Short_Data = new short[100,100];
public string Some_String_Data = new string[100,100];
}
Then writting the file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
Data Write_Data = new Data();
XmlSerializer Writer = new XmlSerializer(typeof(Data));
using (FileStream file = File.OpenWrite("Myfile.xml"))
{
Writer.Serialize(file, Write_Data); //Writes data to the file
}
When I try this, it fails on:
XmlSerializer Writer = new XmlSerializer(typeof(Data));
Saying: "There was an error reflecting type 'Data' "
I am particularly new to both Stack and Xml/Xml Serialization so any help would be appreciated.
Multi-dimensional arrays are not supported by XmlSerialzier. But you can make a workaround by using a temp class something like this
public class Array100<T>
{
public T[] Data = new T[100];
}
public class Data
{
public Array100<short>[] Some_Short_Data = new Array100<short>[100];
public Array100<string>[] Some_String_Data = new Array100<string>[100];
}
BTW: No need for Serializable attribute. It is not used by XmlSerialzier
You cannot serialze a int[,] but you can serialize a int[][]. Before serializing your 2D array, just convert it into a jagged array like that :
var my2dArray = new int[2,5];
var myJaggedArray = new int [2][];
for(int i = 0 ; i < my2DArray.GetLength(0) ; i ++)
{
myJaggedArray[i] = new int[my2DArray.GetLength(1)];
for(int j = 0 ; j < my2DArray.GetLength(1) ; j ++)
myJaggedArray[i][j] = my2DArray[i,j];
}
I ran into this issue when attempting to serialize a multi-dimensional array that was one of the values in a dictionary of objects. The approach I took was to wrap all of the arrays in a class that is serializable before serialization and then unwrap them all afterwards.
If you don't care what the data looks like then you can serialize the object using the binary formatter, which can serialize anything marked as serializable, and then persist the binary data as a base 64 encoded string.
The wrapper implementation is below.
[Serializable]
public class UnserializableValueWrapper: IXmlSerializable
{
private static readonly BinaryFormatter Formatter = new BinaryFormatter();
public UnserializableValueWrapper([NotNull] object value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.Value = value;
}
public UnserializableValueWrapper()
{
}
public object Value { get; private set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
var length = int.Parse(reader.GetAttribute("length"));
reader.ReadStartElement("Data");
var buffer = new byte[length];
reader.ReadContentAsBase64(buffer, 0, length);
using (var stream = new MemoryStream(buffer))
{
this.Value = Formatter.Deserialize(stream);
}
reader.ReadEndElement();
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Data");
using (var stream = new MemoryStream())
{
Formatter.Serialize(stream, this.Value);
var buffer = stream.ToArray();
writer.WriteAttributeString("length", buffer.Length.ToString());
writer.WriteBase64(buffer, 0, buffer.Length);
}
writer.WriteEndElement();
}
}

CsvHelper not writing anything to memory stream

I have the following method:
public byte[] WriteCsvWithHeaderToMemory<T>(IEnumerable<T> records) where T : class
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords<T>(records);
return memoryStream.ToArray();
}
}
Which is being called with a list of objects - eventually from a database, but since something is not working I'm just populating a static collection. The objects being passed are as follows:
using CsvHelper.Configuration;
namespace Application.Models.ViewModels
{
public class Model
{
[CsvField(Name = "Field 1", Ignore = false)]
public string Field1 { get; set; }
[CsvField(Name = "Statistic 1", Ignore = false)]
public int Stat1{ get; set; }
[CsvField(Name = "Statistic 2", Ignore = false)]
public int Stat2{ get; set; }
[CsvField(Name = "Statistic 3", Ignore = false)]
public int Stat3{ get; set; }
[CsvField(Name = "Statistic 4", Ignore = false)]
public int Stat4{ get; set; }
}
}
What I'm trying to do is write a collection to a csv for download in an MVC application. Every time I try to write to the method though, the MemoryStream is coming back with zero length and nothing being passed to it. I've used this before, but for some reason it's just not working - I'm somewhat confused. Can anyone point out to me what I've done wrong here?
Cheers
You already have a using block which is great. That will flush your writer for you. You can just change your code slightly for it to work.
using (var memoryStream = new MemoryStream())
{
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords<T>(records);
} // StreamWriter gets flushed here.
return memoryStream.ToArray();
}
If you turn AutoFlush on, you need to be careful. This will flush after every write. If your stream is a network stream and over the wire, it will be very slow.
Put csvWriter.Flush(); before you return to flush the writer/stream.
EDIT: Per Jack's response. It should be the stream that gets flushed, not the csvWriter. streamWriter.Flush();. Leaving original solution, but adding this correction.
EDIT 2: My preferred answer is: https://stackoverflow.com/a/22997765/1795053 Let the using statements do the heavy lifting for you
Putting all these together (and the comments for corrections), including resetting the memory stream position, the final solution for me was;
using (MemoryStream ms = new MemoryStream())
{
using (TextWriter tw = new StreamWriter(ms))
using (CsvWriter csv = new CsvWriter(tw, CultureInfo.InvariantCulture))
{
csv.WriteRecords(errors); // Converts error records to CSV
tw.Flush(); // flush the buffered text to stream
ms.Seek(0, SeekOrigin.Begin); // reset stream position
Attachment a = new Attachment(ms, "errors.csv"); // Create attachment from the stream
// I sent an email here with the csv attached.
}
}
In case the helps someone else!
There is no flush in csvWriter, the flush is in the streamWriter. When called
csvWriter.Dispose();
it will flush the stream.
Another approach is to set
streamWriter.AutoFlush = true;
which will automatically flush the stream every time.
Here is working example:
void Main()
{
var records = new List<dynamic>{
new { Id = 1, Name = "one" },
new { Id = 2, Name = "two" },
};
Console.WriteLine(records.ToCsv());
}
public static class Extensions {
public static string ToCsv<T>(this IEnumerable<T> collection)
{
using (var memoryStream = new MemoryStream())
{
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(collection);
} // StreamWriter gets flushed here.
return Encoding.ASCII.GetString(memoryStream.ToArray());
}
}
}
Based on this answer.
using CsvHelper;
public class TwentyFoursStock
{
[Name("sellerSku")]
public string ProductSellerSku { get; set; }
[Name("shippingPoint")]
public string ProductShippingPoint { get; set; }
}
using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(TwentyFoursStock);
}

Serialize/Deserilize array of objects/structs

I have read many codes on this but none happened to solve the problem. first the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Serialization
{
class Program
{
static void Main(string[] args)
{
using (MoveSaver objSaver = new MoveSaver(#"C:\1.bin"))
{
MoveAndTime mv1, mv2;
mv1.MoveStruc = "1";
mv1.timeHLd = DateTime.Now;
objSaver.SaveToFile(mv1);
mv2.MoveStruc = "2";
mv2.timeHLd = DateTime.Now;
objSaver.SaveToFile(mv2);
}
using (MoveSaver svrObj = new MoveSaver())
{
MoveAndTime[] MVTobjs = svrObj.DeSerializeObject(#"C:\1.bin");
foreach (MoveAndTime item in MVTobjs)
{
//Do Something
}
}
}
}
public class MoveSaver:IDisposable
{
public void Dispose()
{
if (fs != null)
{
fs.Close();
}
}
FileStream fs;
StreamWriter sw;
public string filename { get; set; }
public MoveSaver(string FileName)
{
this.filename = FileName;
fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
public MoveSaver()
{
}
~MoveSaver()
{
if (fs != null)
{
fs.Close();
}
}
public MoveAndTime[] DeSerializeObject(string filename)
{
MoveAndTime[] objectToSerialize;
Stream stream = File.Open(filename, FileMode.Open);
BinaryFormatter bFormatter = new BinaryFormatter();
objectToSerialize = (MoveAndTime[])bFormatter.Deserialize(stream);
stream.Close();
return objectToSerialize;
}
public bool SaveToFile(MoveAndTime moveTime)
{
try
{
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Serialize(fs, moveTime);
return true;
}
catch (Exception)
{
return false;
}
}
}
[Serializable]
public struct MoveAndTime
{
public string MoveStruc;
public DateTime timeHLd;
}
}
The code mimics a need for saving all actions of user on the program. to be later shown on that program (say you play cards or so and you want to review :D what has happened). The problem is when DeSerializeObject function is called the line objectToSerialize = (MoveAndTime[])bFormatter.Deserialize(stream); throws an exception (definitely in runtime) that the cast from a single object to array is not valid:
Unable to cast object of type
'Serialization.MoveAndTime' to type
'Serialization.MoveAndTime[]'.
Any idea? Any improvement or total change of approach is appreciated.
You're saving a single MoveAndTime instance to the file, but you're trying to read an array of them.
Please modify your main block like this. I think it achieves what you want.
static void Main(string[] args)
{
using (MoveSaver objSaver = new MoveSaver(#"C:\1.bin"))
{
MoveAndTime[] MVobjects = new MoveAndTime[2];
MoveAndTime mv1, mv2;
mv2 = new MoveAndTime();
mv1 = new MoveAndTime();
mv1.MoveStruc = "1";
mv1.timeHLd = DateTime.Now;
mv2.MoveStruc = "2";
mv2.timeHLd = DateTime.Now;
MVobjects[0] = new MoveAndTime();
MVobjects[0] = mv1;
MVobjects[1] = new MoveAndTime();
MVobjects[1] = mv2;
objSaver.SaveToFile(MVobjects);
}
using (MoveSaver svrObj = new MoveSaver())
{
MoveAndTime[] MVTobjs = svrObj.DeSerializeObject(#"C:\1.bin");
foreach (MoveAndTime item in MVTobjs)
{
//Do Something
Console.WriteLine(item.MoveStruc);
Console.WriteLine(item.timeHLd);
}
}
}
Thanks

Mapping a class and xml file using serialization

If I create a class in C#, how can I serialize/deserialize it to a file? Is this somethat that can be done using built in functionality or is it custom code?
XmlSerializer; note that the exact xml names can be controlled through various attributes, but all you really need is:
a public type
with a default constructor
and public read/write members (ideally properties)
Example:
using System;
using System.Xml;
using System.Xml.Serialization;
public class Person {
public string Name { get; set; }
}
static class Program {
static void Main() {
Person person = new Person { Name = "Fred"};
XmlSerializer ser = new XmlSerializer(typeof(Person));
// write
using (XmlWriter xw = XmlWriter.Create("file.xml")) {
ser.Serialize(xw, person);
}
// read
using (XmlReader xr = XmlReader.Create("file.xml")) {
Person clone = (Person) ser.Deserialize(xr);
Console.WriteLine(clone.Name);
}
}
}
You need to use class XmlSerializer. Main methods are Serialize and Deserialize. They accept streams, text readers\writers and other classes.
Code sample:
public class Program
{
public class MyClass
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var myObj = new MyClass { Name = "My name" };
var fileName = "data.xml";
var serializer = new XmlSerializer(typeof(MyClass));
using (var output = new XmlTextWriter(fileName, Encoding.UTF8))
serializer.Serialize(output, myObj);
using (var input = new StreamReader(fileName))
{
var deserialized = (MyClass)serializer.Deserialize(input);
Console.WriteLine(deserialized.Name);
}
Console.WriteLine("Press ENTER to finish");
Console.ReadLine();
}
}

Categories

Resources