Serializing a custom class to a file - c#

I'm writing a save method for my Player class, I can serialize most fields using a binary writer however my custom class fields are not able to be serialized (i have a Race field for custom races)
Since I am going to have a lot of different custom classes - like Race - I really need a good reliable way of saving this information.
So, am I using the wrong kind of formatter? And if so, what is the one I should be using?
Alternatively, how do I write the method to serialize it? I have used the [Serializable()] attribute and implemented ISerializable interface where needed already.
Here is the code:
public void Save()
{
Stream stream = File.Open(SaveSlot, FileMode.Create);
BinaryWriter writer = new BinaryWriter(stream);
PlayerData saveData = new PlayerData();
writer.Write(saveData.Name = PlayerData.player.Name);
writer.Write(saveData.Race = PlayerData.player.Race);
writer.Write(saveData.PlayClass = PlayerData.player.PlayClass);
writer.Write(saveData.Level = PlayerData.player.Level);
writer.Write(saveData.Experience = PlayerData.player.Experience);
writer.Write(saveData.Strength = PlayerData.player.Strength);
writer.Write(saveData.Dexterity = PlayerData.player.Dexterity);
writer.Write(saveData.Constitution = PlayerData.player.Constitution);
writer.Write(saveData.Intelligence = PlayerData.player.Intelligence);
writer.Write(saveData.Wisdom = PlayerData.player.Wisdom);
writer.Write(saveData.Charisma = PlayerData.player.Charisma);
writer.Write(saveData.Wounds = PlayerData.player.Wounds);
writer.Write(saveData.MaxWounds = PlayerData.player.MaxWounds);
writer.Write(saveData.Attackbonus = PlayerData.player.Attackbonus);
writer.Write(saveData.Defensebonus = PlayerData.player.Defensebonus);
writer.Close();
Console.WriteLine("Data Saved Successfully");
}

Like I said in my comment you can use the json format for this.
Sample class
public class MyObject
{
public string MyProperty { get; set; }
}
Sample data
List<MyObject> objects = new List<MyObject>
{
new MyObject{MyProperty = "test 1"},
new MyObject{MyProperty = "test 2"},
new MyObject{MyProperty = "test 3"}
};
Sample code
Then you can use this code for serialize your data (using Newtonsoft).
string json = JsonConvert.SerializeObject(objects);
The content of json will be
[{"MyProperty":"test 1"},{"MyProperty":"test 2"},{"MyProperty":"test
3"}]
If you want to deserialize the string use this.
List<MyObject> deserialzed = JsonConvert.DeserializeObject<List<MyObject>>(json);
More info
You can save the json string to a file and load and deserialize it when you need it. This works with every type of a property including list and custom objects.
Have a read at the JSON format for more information.

Related

Writing data to JSON file in the correct category (C# Newtonsoft JSON)

I'm trying to make a game within my Discord Bot where it is wholly based on Dokkan Battle, the user creates their own passive and the command can view some stats based on what they inputted.
What I'm trying to do is take this data and store it in a JSON file but the way I have it laid out I want it to write it all in one category like this so then it'll be easier to search for specific passives to display
{
"userPassiveStorage": "Storage for user created passives using /passivecreator",
"members": [
{
"passiveName": "",
"leaderName": "",
"unitHP":,
"unitATK":,
"unitDEF":,
"leaderValue":,
"passiveATK":,
"passiveDEF":,
"supportBuff":,
"links": "",
}
]
}
The way I have it done its not writing to the JSON file in the correct format, but instead stores it all as one line
public void StoreUserPassives(DokkanUserPassiveBuilder classObj)
{
using (StreamWriter sw = new StreamWriter("UserPassivesStorage.json"))
{
JsonSerializer serializer = new JsonSerializer();
List<DokkanUserPassiveBuilder> data = new List<DokkanUserPassiveBuilder>();
data.Add(classObj);
string json = JsonConvert.SerializeObject(data.ToArray(), Formatting.Indented);
serializer.Serialize(sw, json);
}
}
So I just need to know how can I add write another member to this category with the same properties as I showed in that JSON file
If there is an easier way rather than using categories I would like to know if that is an easier way since I just need a way to store each passive information and it needs to be easy to search
you are serializing twice. Try this code
var path = #"UserPassivesStorage.json";
var json = File.ReadAllText(path);
var jsonObj = JObject.Parse(json);
var members = jsonObj["members"].ToObject<List<DokkanUserPassiveBuilder>>();
members.Add(classObj);
jsonObj["members"] = JArray.FromObject(members);
File.WriteAllText(path, jsonObj.ToString());

Issues with Save/Load System in a Text Based Adventure game made with ScriptableObjects in Unity

I am almost done with my Text Based Adventure Game and am trying to setup a Save/Load system that works. I can't seem to accomplish this.
I have viewed multiple tutorials and I feel that Json may be what I need. Unfortunately I can't seem to get Json code correct for the 'Load' part. I am also concerned that the amount of data I am trying to save is too much for a simple Serialization process OR I am over-complicating this and there is a simpler solution.
This is the data I am tryin to Save/Load. As you can see I have Dictionaries and Lists, as well as a 'Room' ScriptableObject that I am trying to save, namely, the currentRoom the Player is in when the game is saved. The currentRoom is tracked in the GameController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class SaveData
{
public Room saveRoomNavigation;
public List<string> saveNounsInInventory = new List<string>();
public List<InteractableObject> saveUseableItemList = new
List<InteractableObject>();
public Dictionary<string, string> saveExamineDictionary = new
Dictionary<string, string>();
public Dictionary<string, string> saveTakeDictionary = new
Dictionary<string, string>();
public List<string> saveNounsInRoom = new List<string>();
public Dictionary<string, ActionResponse> saveUseDictionary = new
Dictionary<string, ActionResponse>();
public SaveData (GameController controller, InteractableItems
interactableItems)
{
saveRoomNavigation = controller.roomNavigation.currentRoom;
saveNounsInInventory = interactableItems.nounsInInventory;
saveUseableItemList = interactableItems.useableItemList;
saveExamineDictionary = interactableItems.examineDictionary;
saveTakeDictionary = interactableItems.takeDictionary;
saveNounsInRoom = interactableItems.nounsInRoom;
saveUseDictionary = interactableItems.useDictionary;
}
}
Then this is my Save System code where I am trying to implement the Serialization with Json and binary.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public static class SaveSystem
{
public static void SaveGame (GameController controller,
InteractableItems interactableItems)
{
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "/savegame.rta";
FileStream stream = new FileStream(path, FileMode.Create);
SaveData data = new SaveData(controller, interactableItems);
var json = JsonUtility.ToJson(data);
formatter.Serialize(stream, json);
stream.Close();
}
public static SaveData LoadGame()
{
string path = Application.persistentDataPath + "/savegame.rta";
if (File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
SaveData data =
JsonUtility.FromJsonOverwrite((string)formatter.Deserialize(stream,
????));
//SaveData data = formatter.Deserialize(stream) as
SaveData;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
Where I put the '????' above is where I can't seem to get it to accept the Object I am trying to overwrite, which I thought was 'SaveData' since that is where I pull the information to put into the game when the Player clicks the 'Load' button. It tell me "'SaveData' is a type, which is not valid in the given context"
Here is the code I call when the 'Save' and 'Load' buttons are used, it is within my GameController Class where most everything is tracked during Game Play.
public void SaveGame()
{
SaveSystem.SaveGame(this, interactableItems);
}
public void LoadGame()
{
SaveData data = SaveSystem.LoadGame();
roomNavigation.currentRoom = data.saveRoomNavigation;
interactableItems.nounsInInventory = data.saveNounsInInventory;
interactableItems.useDictionary = data.saveUseDictionary;
interactableItems.takeDictionary = data.saveTakeDictionary;
interactableItems.examineDictionary =
data.saveExamineDictionary;
interactableItems.useableItemList = data.saveUseableItemList;
interactableItems.nounsInRoom = data.saveNounsInRoom;
DisplayRoomText();
DisplayLoggedText();
}
When used in game, I get "SerializationException" errors, so that is when I attempted to try Json. I read where it can serialize a lot more than the basic Unity Serializer. The errors went away and it seems to save OK with Json, but now I am unable to get the Load syntax correct.
I am a novice so maybe I am making this harder then it has to be... I also looked into Userprefs, but I don't think I can convert the Scriptable Object, Room, into a type String.
Thanks in advance!
[UPDATE] Here is what I am able to have it save in .txt format. This proves that it is saving, just not sure "instanceIDs" will give me the Load that I need.
ˇˇˇˇö{"saveRoomNavigation":
{"instanceID":11496},"saveNounsInInventory": ["sword"],"saveUseableItemList":[{"instanceID":11212}, {"instanceID":11588},{"instanceID":10710},{"instanceID":11578}, {"instanceID":10718},{"instanceID":11388}, {"instanceID":11276}],"saveNounsInRoom":["rocks","hole"]}
Take a look at the documentation for Unity's built-in JsonUtility:
https://docs.unity3d.com/Manual/JSONSerialization.html
It says types such as "Dictionaries" are not supported. Seeing as though you have several Dictionaries in your SaveData example, might I suggest using a more robust JSON library such as JSON.NET, which is free on the asset store:
https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347

how to serialize a YamlDocument to yaml string

I use YamlDotnet and I have a YamlDocument. Now I want to convert it to his yaml text representation in memory but I don't see how to achieve that.
var yaml = new YamlDocument(new YamlMappingNode());
yaml.Add("one", "other")
var text = yaml.ToYamlText()
and I should get in text something like :
one: "other"
I tried zith Serializer class but with no success
ok so I found the solution in unit test of the source code :
var yaml = new YamlDocument(new YamlMappingNode());
yaml.Add("one", "other");
var yamlStream = new YamlStream(yaml);
var buffer = new StringBuilder();
using (var writer = new StringWriter(buffer))
{
yamlStream.Save(writer);
yamlText = writer.ToString();
}
Anyway, I have now another problem, I need all my values to be surrounded by double quotes. In another application I was using a QuoteSurroundingEventEmitter : ChainedEventEmitter with an object graph selrialization. But with yamlStream.Save() I don't see how to implement this mecanism

Why is XmlSerializer's Deserialize() spitting out a child object which is a XmlNode[]?

I'm using XmlSerializer to serialize and then deserialize a simple object. When I deserialize the object to my surprise I find a child object was not properly deserialized but instead turned into XmlNode[].
Here is very nearly the structure I've got:
// This line I put in here as a way of sneaking into the XML the
// root node's C# namespace, since it's not the same as the
// deserializing code and the deserializing code seemed unable to
// deserialize properly without knowing the Type (see my code below).
// So I basically just use this fake construct to get the namespace
// and make a Type of it to feed the XmlSerializer() instantiation.
[XmlRoot(Namespace = "http://foo.com/CSharpNamespace/Foo.Bar")]
// This is because QueuedFile can be given to the Argument array.
[XmlInclude(typeof(QueuedFile))]
// This class is Foo.Bar.CommandAndArguments
public class CommandAndArguments {
public String Command;
public object[] Arguments;
}
// I don't think this matters to XmlSerialize, but just in case...
[Serializable()]
// I added this line just thinking maybe it would help, but it doesn't
// do anything. I tried it without the XmlType first, and that
// didn't work.
[XmlType("Foo.Baz.Bat.QueuedFile")]
// This class is Foo.Baz.Bat.QueuedFile (in a different c#
// namespace than CommandAndArguments and the deserializing code)
public QueuedFile {
public String FileName;
public String DirectoryName;
}
And the code which deserializes it looks like:
public static object DeserializeXml(String objectToDeserialize)
{
String rootNodeName = "";
String rootNodeNamespace = "";
using (XmlReader xmlReader = XmlReader.Create(new StringReader(objectToDeserialize)))
{
if (xmlReader.MoveToContent() == XmlNodeType.Element)
{
rootNodeName = xmlReader.Name;
rootNodeNamespace = xmlReader.NamespaceURI;
if (rootNodeNamespace.StartsWith("http://foo.com/CSharpNamespace/"))
{
rootNodeName = rootNodeNamespace.Substring("http://foo.com/CSharpNamespace/".Length) + "." +
rootNodeName;
}
}
}
//MessageBox.Show(rootNodeName);
try
{
Type t = DetermineTypeFromName(rootNodeName);
if (t == null)
{
throw new Exception("Could not determine type of serialized string. Type listed as: "+rootNodeName);
}
var s = new XmlSerializer(t);
return s.Deserialize(new StringReader(objectToDeserialize));
// object o = new object();
// MethodInfo castMethod = o.GetType().GetMethod("Cast").MakeGenericMethod(t);
// return castMethod.Invoke(null, new object[] { s.Deserialize(new StringReader(objectToDeserialize)) });
}
catch (InvalidOperationException)
{
return null;
}
}
And here is the XML when the CommandAndArguments is serialized:
<?xml version="1.0" encoding="utf-16"?>
<CommandAndArguments xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://foo.com/CSharpNamespace/Foo.Bar">
<Command>I am a command</Command>
<Arguments>
<anyType xsi:type="Foo.Baz.Bat.QueuedFile">
<FileName xmlns="">HelloWorld.txt</FileName>
<DirectoryName xmlns="">C:\foo\bar</DirectoryName>
</anyType>
</Arguments>
</CommandAndArguments>
But when I deserialize I am given a CommandAndArguments object where Arguments is XmlNode[] with the first item being the attribute giving the QueuedFile as the type and the other indices being elements of the properties. But why wasn't the QueuedFile object recreated?
I suspect this might somehow have do with C# namespaces and the engine doing the deserializing not being able to find or work with QueuedFile... But I don't see why since when I forgot the XmlInclude() it made sure to tell me it didn't expect QueuedFile and now that I've added the XmlInclude() I get no error, just an incomplete deserialization.
Help? I've read everything I can find to read and Googled everything I know to Google and am stuck. I certainly have a lot to learn about XML serialization but I'm not sure how I'm failing at something which should be pretty simple (I actually did something almost exactly like this before without any problem, the only difference then was that everything was in the same C# namespace).
Are you able to change the XML format or is it fixed? I don't know what the problem you are having is, but I use the DataContractSerializer classes extensively with no problems.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx
public static void WriteObject(string fileName)
{
Console.WriteLine(
"Creating a Person object and serializing it.");
Person p1 = new Person("Zighetti", "Barbara", 101);
FileStream writer = new FileStream(fileName, FileMode.Create);
DataContractSerializer ser =
new DataContractSerializer(typeof(Person));
ser.WriteObject(writer, p1);
writer.Close();
}
public static void ReadObject(string fileName)
{
Console.WriteLine("Deserializing an instance of the object.");
FileStream fs = new FileStream(fileName,
FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
DataContractSerializer ser = new DataContractSerializer(typeof(Person));
// Deserialize the data and read it from the instance.
Person deserializedPerson =
(Person)ser.ReadObject(reader, true);
reader.Close();
fs.Close();
Console.WriteLine(String.Format("{0} {1}, ID: {2}",
deserializedPerson.FirstName, deserializedPerson.LastName,
deserializedPerson.ID));
}
To anyone coming along with a similar problem, depending on your situation you're probably better off with NetDataContractSerializer. It is an alternative to DataContractSerializer which records the .Net types in the XML making deserialization a breeze, since it knows exactly what types are involved and thus you do not need to tell it what type the root object is with the deserialize command. And it can produce output in XML or binary form (I prefer XML for easier debugging).
Here is some sample code for easily serializing and deserializing an object to and from a string:
private static object Deserialize(string xml)
{
object toReturn = null;
using (Stream stream = new MemoryStream())
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
stream.Write(data, 0, data.Length);
stream.Position = 0;
var netDataContractSerializer = new NetDataContractSerializer();
toReturn = netDataContractSerializer.Deserialize(stream);
}
return toReturn;
}
private static string Serialize(object obj)
{
using (var memoryStream = new MemoryStream())
using (var reader = new StreamReader(memoryStream))
{
var netDataContractSerializer = new NetDataContractSerializer();
netDataContractSerializer.Serialize(memoryStream, obj);
memoryStream.Position = 0;
return reader.ReadToEnd();
}
}
Easy as pie!

C#, insert strongly typed object into XML Document as Element

I have an XML document that contains the following structure:
Its more or less a collection of Events:
<Events>
<Event>
<DateTime></DateTime>
<EventType></EventType>
<Result></Result>
<Provider></Provider>
<ErrorMessage></ErrorMessage>
<InnerException></InnerException>
</Event>
</Events>
In C# I have a persistent object called Event:
Now given that the document already exists, and saved to file... I call :
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(dataPath);
Now how can I add a new Event item to events?
I've got a strongly typed Event item in my C# code, and want it inserted into the Events collection in the XML object as last child.
I guess this is what I am really asking : https://stackoverflow.com/questions/1457033/c-insert-a-strongly-typed-object-as-node-in-existing-xml-document
Take a look at the Xml Serialization attributes.
You can do this:
[XmlRoot("Event")]
public class Event
{
[XmlElement("DateTime")]
public string DateTime
{
get;
set;
}
[XmlElement("EventType")]
public EnumReportingEventType EventType
{
get;
set;
}
[XmlElement("Result")]
public EnumReportingResult Result
{
get;
set;
}
[XmlElement("Provider")]
public string Provider
{
get;
set;
}
[XmlElement("ErrorMessage")]
public string ErrorMessage
{
get;
set;
}
[XmlElement("InnerException")]
public string InnerException
{
get;
set;
}
}
In fact, if the properties of your class have the same name as the elements in your Xml, you do not have to use the XmlElement attributes.
Then, you can use the XmlSerializer to serialize and deserialize.
Edit:
Then, wouldn't it be better to create a type which represent the entire type that is stored in the existing xml ?
Deserialize it, give a value to the additional property, and serialize it back ?
Just hand code it using the XmlDocument. You can add a class that does the conversion or insertion into the document you are going to save.
This is based on the restriction of .net 2.0 and what you said in these comments:
#Fred, I want to try minimize the write time, hence the reason for not writing all at once, the less write time in this app, the less chance of file corruption. – JL 16 mins ago
Why do you think you have a lot of chance of file corruption ? – Frederik Gheysels 9 mins ago
From existing test results on code I already have using serialization as a whole.
If you are using .Net 3.5 you can use Linq to XML, something like the following will work
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Event document"),
new XElement("Events",
new XElement ("Event",
new XElement("DateTime", event.DateTime),
new XElement("EventType", event.EventType),
new XElement("Result", event.Result),
new XElement("Provider", event.Provider),
new XElement("ErrorMessage", event.ErrorMessage),
new XElement("InnerException", event.InnerException)
)
));
doc.Save(#"c:\sample.xml");
If you have an existing xml document that you want to append to somthing like the following is required.
XDocument doc = XDocument.Load(#"c:\sample.xml");
XElement events = doc.Element(XName.Get("Events"));
events.Add(new XElement ("Event",
new XElement("DateTime", event.DateTime),
new XElement("EventType", event.EventType),
new XElement("Result", event.Result),
new XElement("Provider", event.Provider),
new XElement("ErrorMessage", event.ErrorMessage),
new XElement("InnerException", event.InnerException)
));
doc.Save(#"c:\sample.xml");
Insert a XmlElement field near the end of the "Event" class like so:
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.XmlElement Any { get; set }
You can name it whatever you want as long as you have the "XmlAnyElementAttribute" on it.
You can use something like the following to serialize a strongly-typed object into this field:
MyParentObject parent = new MyParentObject(){ ... };
MyObject obj = new MyObject(){ /*... initialize*/ };
XmlSerializer ser = new XmlSerializer(typeof(MyObject));
XmlDocument doc = new XmlDocument();
using (StringWriter sw = new StringWriter())
{
ser.Serialize(sw, obj);
doc.LoadXml(sw.ToString());
}
parent.Any = (XmlElement)doc.DocumentElement;
The serialized XML will append nicely to your class, it will event include the correct namespaces.
Assuming that your Event class can already be serialized the way you want using XmlSerializer, you can do the following:
XmlSerializer ser = new XmlSerializer(typeof(Event));
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(dataPath);
Event evt = ...;
XmlDocument evtDoc = new XmlDocument();
using (XmlWriter writer = evtDoc.CreateNavigator().AppendChild())
{
ser.Serialize(writer, evt);
}
XmlNode evtNode = evtDoc.RemoveChild(evtDoc.DocumentElement);
XmlNode events = xmlDoc.SelectSingleNode("/Events");
events.AppendChild(evtNode);
What you are looking to do is something like:
doc.ChildNode[0].AppendChild(MethodToReturnADeserializedObject(event));
Create a method to deserialize the event object into an xml node. Then use AppendChild to insert that as the last element amongst it's child nodes.

Categories

Resources