I was able to create an XML serializer in Unity which saves my game state and was working fine. I haven't touched it since then, but a few days later it started saving the XML files on one line instead.
This was how it looked before:
<GameData xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<gameFlags/>
<memories/>
<lastSaveTime>-8586819040232916923</lastSaveTime>
<health>50</health>
<speed>0.27</speed>
<playerPosition>
<x>10</x>
<y>-48.2583</y>
<z>0</z>
</playerPosition>
<lastSaveFile/>
<savedScene>Area 0-0</savedScene>
<playerName>Jon</playerName>
<PlayerPositionX>10</PlayerPositionX>
<PlayerPositionY>-48.2583</PlayerPositionY>
<PlayerPositionZ>0</PlayerPositionZ>
</GameData>
This is how it looks now
-8586812865931894767 50 0.27 10 -48.2583 0 Default Area 0-0 Jon 10 -48.2583 0
Everything is all on one line. I reverted to my previous commit as well when it was working but it still is saving on one line. Does anyone have any experience with this change?
Save Game Method
public void SaveGame(string saveFile)
{
CheckDirectory();
gameData.generateGameData (GameObject.FindGameObjectWithTag("Player").GetComponent<Player>());
// Update saveFile name
if (saveFile == null)
{
saveFile = GenerateNewSaveName();
}
this.saveFile = saveFile;
// FileStream fs = File.Create(GameDic.Instance.SavePath + saveFile);
UpdateSaveData(saveFile);
string fullSavePath = SavePath + saveFile + FILE_EXTENSION;
FileStream fs;
// Create a file or open an old one up for writing to
if (!File.Exists(fullSavePath))
{
fs = File.Create(fullSavePath);
}
else
{
fs = File.OpenWrite(fullSavePath);
}
XmlSerializer serializer = new XmlSerializer(typeof(GameData));
TextWriter textWriter = new StreamWriter(fs, System.Text.Encoding.UTF8);
serializer.Serialize(textWriter, gameData);
fs.Close();
Debug.Log("Game Saved to " + fullSavePath);
}
GameData Class
[Serializable]
public class GameData
{
#region Public Fields
public List<GameFlag> gameFlags;
public List<string> memories;
public List<string> conversations;
public List<QuestInstance> questInstances;
public long lastSaveTime;
public float health, speed;
// Needs properties to access
[NonSerialized]
public Vector3 playerPosition;
public string lastSaveFile;
public string savedScene;
public string playerName;
#endregion Public Fields
#region Public Constructors
public void generateGameData(Player player) {
health = player.getStat (Stat.HP);
speed = player.getStat (Stat.SPEED);
savedScene = SceneManager.GetActiveScene ().name;
playerPosition = player.respawnPoint;
playerName = "Jon";
memories = player.GetComponentInChildren<MemoryManager> ().getAccessedMemories ();
conversations = player.GetComponentInChildren<NPCManager> ().getConversations ();
questInstances = player.GetComponent<QuestManager> ().getQuestInstances ();
}
public string GenerateNewSaveName()
{
int attempt = 0;
string newSaveName = "";
while (newSaveName == "")
{
// Save Name is Player Name
string checkString = gameData.playerName;
// Add a number if original already taken
if (attempt != 0) checkString += attempt;
if (!File.Exists(SavePath + checkString))
{
// Make the check string the new file name
newSaveName = checkString;
}
attempt++;
}
return newSaveName;
}
}
For the QuestInstance class I suppose it just can just be any filler class that's empty. It's serialized correctly.
Your problem is most likely this line:
fs = File.OpenWrite(fullSavePath);
If the previous file was longer than the new one, you will still have fragments from the previous save file at the end - that's why you can't deserialize it. Get rid of everything in that if condition except for the File.Create and it should work.
Related
I'm developing a software to manage my collection of coins. I need to export the content of a list of objects in a JSON file but I encounter this error everytime I want to display the coins that are actually inside the database:
Additional text encountered after finished reading JSON content: [. Path '', line 1, position 109.
Here's where everything should happen:
List<Coin> coins = new List<Coin>();
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamWriter streamWriter = new StreamWriter(path, true))
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
jsonSerializer.Serialize(jsonWriter, coins.ToList());
}
return true;
}
The output is stored inside different blocks of square brackets. I've a block for every object inserted. Instead I should have every object inside a unique block of square brackets. Thanks in advance.
EDIT: Here's the content of the JSON file
[{"ID":0,"coinName":"1 Euro","coinNation":"Ita","coinStatus":"FdC","coinYear":2005,"quantity":1,"value":4.7}][{"ID":0,"coinName":"1 Euro","coinNation":"Ita","coinStatus":"FdC","coinYear":2005,"quantity":1,"value":4.7},{"ID":1,"coinName":"2 Euro","coinNation":"Bel","coinStatus":"FdC","coinYear":2004,"quantity":1,"value":30.0}]
As I said, everything should be inside a unique block of square brackets.
I think that I've just found the solution to my problem and I'm going to share it with you. I've changed some lines and now I have:
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamReader streamReader = new StreamReader(path, true))
{
string json = streamReader.ReadToEnd();
coins = JsonConvert.DeserializeObject<List<Coin>>(json);
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
string newJson = JsonConvert.SerializeObject(coins);
streamReader.Close();
File.WriteAllText(path, newJson);
}
return true;
}
If I'm thinking correctly, doing this causes the program to read until it reaches EOF and then, after serializing/deserializing the list, appends the new object. At the moment this seems to works fine.
I recommend you to use NewtonsoftJSON (you can install it via NuGet), clear json file every time you adding new coin, there are coins manager sample for you:
public class CoinsManager
{
public List<Coin> Coins { get; set; }
public string FilePath { get; set; }
public CoinsManager(string filePath)
{
FilePath = filePath;
Coins = new List<Coin>();
}
public void LoadCoins()
{
if (File.Exists(FilePath))
{
//If file exists, but empty, save empty settings to it
if (new FileInfo(FilePath).Length == 0)
{
SaveSettings();
}
else
{
//Read json from file
using (StreamReader r = new StreamReader(FilePath))
{
string json = r.ReadToEnd();
//Convert json to list
Coins = JsonConvert.DeserializeObject<List<Coin>>(json);
}
}
}
else
{
//Create file
File.Create(FilePath).Close();
//Wait for filesystem to create file
while (!File.Exists(FilePath))
{
System.Threading.Thread.Sleep(100);
}
//Save empty settings to file
SaveSettings();
}
}
public void SaveSettings()
{
string json = JsonConvert.SerializeObject(Coins);
File.WriteAllText(FilePath, json);
}
//Can save or update passed coin
public void SaveCoin(Coin coin)
{
//Select old coin
var oldCoin = Coins.Where(c => c.ID == coin.ID).FirstOrDefault();
//If there was no old coin, get last existing coin id, or zero if Coins list is empty
if (oldCoin == null)
{
int lastId;
if (Coins.Count != 0)
lastId = Coins.Count - 1;
else
lastId = 0;
coin.ID = lastId + 1;
Coins.Add(coin);
}
else
{
int index = Coins.IndexOf(oldCoin);
Coins[index] = coin;
}
}
public void DeleteCoin(Coin coin)
{
Coins.RemoveAll(c => c.ID == coin.Id);
}
}
and it's usage:
CoinsManager coinsManager = new CoinsManager("coinsStorage.json");
coinsManager.LoadCoins();
coinsManager.SaveCoin(new Coin {
...
});
coinsManager.SaveSettings();
if i understand correct you just need to change this row:
StreamWriter streamWriter = new StreamWriter(path, true);
to this one:
StreamWriter streamWriter = new StreamWriter(path, false);
your problem is that you always add to the file new json with all the list instead of just writing the list.
Because you work with file you need and you want to append your only option is to read the file then add elements and write it again.
You can read it when application start and menage it like it seems you do becouse your list is global.
Or you can read it right before you want to write the file.
In any one of this cases you need to add the fix I wrote.
You can use this for read the json o your list:
string myJsonString = File.ReadAllText(path);
coins = JsonConvert.DeserializeObject<List<Coin>>(myJsonString);
here is full function:
public bool AddACoin (int ID, String coinName, String coinNation, String coinStatus, int coinYear, int quantity, float value)
{
var jsonSerializer = new JsonSerializer();
using (StreamWriter streamWriter = new StreamWriter(path, false))
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
string myJsonString = File.ReadAllText(path);
coins = JsonConvert.DeserializeObject<List<Coin>>(myJsonString);
coins.Add(new Coin(ID, coinName, coinNation, coinStatus, coinYear, quantity, value));
jsonSerializer.Serialize(jsonWriter, coins.ToList());
}
return true;
}
I have a problem with the highscore code for a game I am creating in school.
When the game ends it says that it cant access HighScore because of its protection level.
This is the code that is giving me an error :
public static HighScore LoadHighScores(string filename)
{
HighScore data;
// Get the path of the save game
string fullpath = "highscores.xml";
// Open the file
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate, FileAccess.Read);
try
{
// Read the data from the file
XmlSerializer serializer = new XmlSerializer(typeof(HighScore));
data = (HighScore)serializer.Deserialize(stream);
}
finally
{
// Close the file
stream.Close();
}
return (data);
}
When i run the code and die i call this code that saves the score i got:
public void SaveHighScore(int score)
{
// Create the data to saved
HighScore data = LoadHighScores(HighScoresFilename);
int scoreIndex = -1;
for (int i = data.Count--; i > -1; i--)
{
if (score >= data.Score[i]) //score.getScore();
{
scoreIndex = i;
}
}
if (scoreIndex > -1)
{
//New high score found ... do swaps
for (int i = data.Count--; i > scoreIndex; i--)
{
data.PlayerNames[i] = data.PlayerNames[i - 1];
data.Score[i] = data.Score[i - 1];
}
data.PlayerNames[scoreIndex] = PlayerName; //Retrieve User Name Here
data.Score[scoreIndex] = score; // Retrieve score here
SaveHighScores(data, HighScoresFilename);
}
}
And SaveHighScores looks like this:
public static void SaveHighScores(HighScore data, string filename)
{
// Get the path of the save game
string fullpath = "highscores.xml";
// Open the file, creating it if necessary
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate);
try
{
// Convert the object to XML data and put it in the stream
XmlSerializer serializer = new XmlSerializer(typeof(HighScore));
serializer.Serialize(stream, data);
}
finally
{
// Close the file
stream.Close();
}
}
Please check this link
Public Class - "is inaccessible due to its protection level. Only public types can be processed."
you can find your answer there....
As a summary mark your HighScore as public.
I have a class, named Schematic, that stores a structure of blocks for use in my game. I'm trying to get a way to save and load them using the BinaryFormatter, however I have an issue with the deserialization. When I deserialize, I cannot cast to my source type, instead it only lets me get one field, a two dimensional array of integers.
Here's the code for the schematic class:
[Serializable]
public class Schematic
{
public static Schematic BlankSchematic = new Schematic("BLANK");
public int[,] Blocks;
public V2Int Size;
public V2Int Location = V2Int.zero;
public string Name;
//---PROPERTIES---
//lower is more rare
public int Rarity = 100;
//---END PROPERTIES---
public Schematic(string name)
{
Name = name;
}
public Schematic(string name, int[,] blocks)
{
Name = name;
ModifyBlockArray(blocks);
}
public void ModifyBlockArray(int[,] newBlocks)
{
Blocks = newBlocks;
Size = new V2Int(newBlocks.GetLength(0), newBlocks.GetLength(1));
}
}
And my methods in a separate class for serialization and deserialization:
public void SaveSchematic(Schematic schem)
{
using (Stream stream = new FileStream(SchematicsDirectory + "/" + schem.Name + ".schem", FileMode.Create, FileAccess.Write, FileShare.None))
{
BinaryFormatter bf = new BinaryFormatter();
Debug.Log(schem.GetType());
bf.Serialize(stream, schem);
}
}
public void LoadSchematics(string dir)
{
BinaryFormatter bf = new BinaryFormatter();
DirectoryInfo info = new DirectoryInfo(dir);
FileInfo[] fileinfo = info.GetFiles("*.schem");
for (int i = 0; i < fileinfo.Length; i++)
{
FileStream fs = new FileStream(dir + fileinfo[i].Name, FileMode.Open);
object tempO = bf.Deserialize(fs);
Debug.Log(tempO + ", " + tempO.GetType());
Schematic temp = (Schematic)tempO;
SchematicsByName.Add(temp.Name, temp);
Schematics.Add(temp);
print("Loaded Schematic: " + temp.Name);
fs.Close();
fs.Dispose();
}
}
It's very strange because when I look into a serialized file, I see the other fields and the class name "Schematic." Here is a small little example file:
ÿÿÿÿ Assembly-CSharp Schematic BlocksSizeLocationNameRarity System.Int32[,]V2Int V2Int V2Int xy
TestSavingd
V2Int is marked as Serializable as well. It's really weird that when I deserialize I get back the Blocks array and not the whole class. Any help would be much appreciated.
This is my first post on here, so sorry if I made any mistakes.
I tried it with a sample snippet, and it serialized and deserialized correctly for me. Please ensure that the Schematic object being sent in has all the right values to start with. And in general,
the deserialized object is your best bet of the final values. don't look at the serialized file.
also, if you had the wrong type deserialized, this line would have typically thrown a cast exception.
Schematic temp = (Schematic)tempO;
So please execute the following snippet and let us know.
It worked alright for me. (I cooked up a random V2Int class based on its constructor)
public static string SchematicsDirectory = "d:\\temp\\s";
static void Main(string[] args)
{
var p = new Program();
var array = new int[2, 2];
array[0, 0] = 1;
array[0, 1] = 2;
array[1, 0] = 3;
array[1, 1] = 4;
var testObject = new Schematic("fezzik", array);
p.SaveSchematic(testObject);
p.LoadSchematics(SchematicsDirectory + "/");
}
I have a console application using web services. What I am trying to achieve its to extract several information about projects which works fine.
The web service GetProjectData() outputs several parameters and one of those its the status.
I have created a xml file with the status that I need because the GetProjectData() does not take any arguments.
When I run the following code I have an error There is an error in XML document (0, 0).
Can you please point me how to achieve this?
Thanks
This is the xml created
<root>
<status>
<List>Published</List>
<List>expired</List>
</status>
</root>
Here is my code
class ProjectEqualityComparer : IEqualityComparer<WS.ProjectMetaData>
{
#region IEqualityComparer<Project> Members
public bool Equals(WS.ProjectMetaData x, WS.ProjectMetaData y)
{
return x.ProjectID.Equals(y.ProjectID);
}
public int GetHashCode(WS.ProjectMetaData obj)
{
return obj.ProjectID.GetHashCode();
}
#endregion
}
[XmlRoot("root")]
public class ListOFStatus
{
public ListOFStatus() { Items = new List<Status>(); }
[XmlElement("status")]
public List<Status> Items { get; set; }
}
public class Status
{
[XmlElement("List")]
public String Name { get; set; }
}
string customerList = "customerList";
string outCsvFile = string.Format(#"C:\\customerList\\{0}.csv", customerList + DateTime.Now.ToString("_yyyyMMdd HHmms"));
XmlSerializer ser = new XmlSerializer(typeof(ListOFStatus));
List<ListOFStatus> list = new List<ListOFStatus>();
object obj = null;
WS.ProjectData[] pr = db.GetProjectData();
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
using (StreamWriter file = new StreamWriter(fs))
{
file.WriteLine("ProjectID, ProjectTitle,PublishStatus,NumberOfCustomers,ProjectStartDate,ProjectEndDate, URL");
foreach(WS.ProjectData proj in pr.Distinct(new ProjectEqualityComparer()))
{
var userIDs = db.GetList(proj.ProjectID);
file.WriteLine("{0},\"{1}\",{2},{3},{4},{5},{6}",
proj.ProjectID,
proj.ProjectTitle,
obj = ser.Deserialize(fs), //where I need to display the status from the projects either Published or expired
userIDs.Length.ToString(NumberFormatInfo.InvariantInfo),
proj.ProjectStartDate.ToString(DateTimeFormatInfo.InvariantInfo),
proj.ProjectEndDate.ToString(DateTimeFormatInfo.InvariantInfo),
url[i].ToString());
}
}
Here you have my expected output.
You are trying to deserialize from your output file. You have
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
// ...
{
// ...
obj = ser.Deserialize(fs);
// ...
}
Since the FileStream is opened in Append Mode, there is nothing to read.
Furthermore, you are writing to that same file, which seems very wrong.
UPDATE:
You would need to do something along the lines of:
using (FileStream xml = File.OpenRead("xmlfilename"))
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
// ...
{
// ...
obj = ser.Deserialize(xml);
// ...
}
I have a infopath form that may contain multiple attachments: By using a group of repeating elements, the user can click an “click add item” option he will be able to upload more attachments.
In Sharepoint I am using a workflow to extract the attachments and put them in a separate list. So far I manage only to extract the first one and the workflow finishes successfully.
can I put a loop or something to iterate trough the form?
I attach the code below:
public sealed partial class FileCopyFeature : SharePointSequentialWorkflowActivity
{
public FileCopyFeature()
{
InitializeComponent();
}
public Guid workflowId = default(System.Guid);
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();
private void CopyFile(object sender, EventArgs e)
{
// Retrieve the file associated with the item
// on which the workflow has been instantiated
SPFile file = workflowProperties.Item.File;
if (file == null)
return;
// Get the binary data of the file
byte[] xmlFormData = null;
xmlFormData = file.OpenBinary();
// Load the data into an XPathDocument object
XPathDocument ipForm = null;
if (xmlFormData != null)
{
using (MemoryStream ms = new MemoryStream(xmlFormData))
{
ipForm = new XPathDocument(ms);
ms.Close();
}
}
if (ipForm == null)
return;
// Create an XPathNavigator object to navigate the XML
XPathNavigator ipFormNav = ipForm.CreateNavigator();
ipFormNav.MoveToFollowing(XPathNodeType.Element);
XmlNamespaceManager nsManager =
new XmlNamespaceManager(new NameTable());
foreach (KeyValuePair<string, string> ns
in ipFormNav.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (ns.Key == String.Empty)
{
nsManager.AddNamespace("def", ns.Value);
}
else
{
nsManager.AddNamespace(ns.Key, ns.Value);
}
}
do
{
XPathNavigator nodeNav = ipFormNav.SelectSingleNode("//my:field2", nsManager);
// Retrieve the value of the attachment in the InfoPath form
//XPathNavigator nodeNav = ipFormNav.SelectSingleNode(
//"//my:field2", nsManager);
string ipFieldValue = string.Empty;
if (nodeNav != null)
{
ipFieldValue = nodeNav.Value;
// Decode the InfoPath file attachment
InfoPathAttachmentDecoder dec =
new InfoPathAttachmentDecoder(ipFieldValue);
string fileName = dec.Filename;
byte[] data = dec.DecodedAttachment;
// Add the file to a document library
using (SPWeb web = workflowProperties.Web)
{
SPFolder docLib = web.Folders["Doc"];
docLib.Files.Add(fileName, data);
docLib.Update();
// workflowProperties.Item.CopyTo(data + "/Doc/" + fileName);
}
}
}
while (ipFormNav.MoveToNext());
}
}
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
It appears that your issue has to do with your use of MoveToNext. Per the documentation, this function moves to the next sibling, and does not navigate to children elements. Your code appears to go to the first element it finds (presumably my:myFields), looks for the first child named my:field2 (it only pulls the first one since you are using SelectSingleNode, and then goes to the next sibling of my:myFields (not the next sibling of my:field2). One way to fix this might be to replace your current do-while loop with a call to SelectNodes like the following and then iterate over nodeList.
XmlNodeList nodelist = ipFormNav.SelectNodes("//my:field2", nsManager);