- Home /
Why is JSON not saving my Class?
I need to save a List of a class i made. The class is called HeroCard, so naturally i'll just go with heroCards for my List. I'll be saving...err, attempting to save this (preferably only this) to a JSON file. At present I have [Serializable] at the top of both my HeroCollection script (where i save it) and HeroCard...well, just above the class declaration.
This is from HeroCollection:
private void LoadHeroesFromJsonFile() {
// if file exist, read and load to heroCards
if (File.Exists(path))
{
var inputString = File.ReadAllText(path);
JsonUtility.FromJsonOverwrite(inputString, heroCards);
if(heroCards.Count < 5)
{
Debug.Log("Hero Cards weren't saved!");
CreateNewSave();
}
}
else
{
FileInfo file = new FileInfo(path);
file.Directory.Create();
CreateNewSave();
}
Debug.Log("Number of Hero Cards: " + heroCards.Count);
}
private void CreateNewSave()
{
for (int i = 0; i < 5; i++)
{
HeroCard hc = new HeroCard(1, 0, 1, firstHeroAssets[i]);
hc.PopulateInfo();
heroCards.Add(hc);
}
SaveHeroesToJsonFile();
}
...What am I missing here? What is loaded is an empty HeroCard List and it Logs "Hero Cards weren't saved!"
Are you trying to overwrite an existing file? Or are you trying To save a new one? From your question, you don’t mention overwriting, but your use of JsonUtility is for overwriting an existing instance of HeroCards with new values and not for creating a new instance. Creating a new instance would look like this:
JsonUtility.FromJson<HeroCard>(jsonString);
"heroCards" is a list? Unfortunately Unity JSON does not support unstructured serialization. If you really need to serialize the list then try newtonsoft json or odin. Otherwise, follow the documentation https://docs.unity3d.com/$$anonymous$$anual/JSONSerialization.html
Answer by Bunny83 · Aug 18, 2020 at 01:08 AM
As others have already mentioned in the comments above, Unity's JsonUtility does not support an array as the root element. The root element has to be an object. So just use a serializable wrapper class that holds your List of HeroCards
[System.Serializable]
public class Data
{
public List<HeroCard> cards;
}
So you should serialize / deserialize this class instead. So your json text would look something like this:
{
"data": [
{
Your hero card fields
},
{
Your hero card fields
}
]
}
i see. i guess i misread something that said Lists were an exception to this rule/lacking capability...
That makes sense. I'll give it a go and be back to confirm I got it working
ok, so this shows the right number of Hero Cards in the List in the GameData class i now save, but not the Hero Asset (ScripableObject) i have on a card. is it just because it's a scriptableobject? should i save the name instead and load from resources?
ScriptableObjects are standalone assets. So they are not serialized in place. Unity's serialization system would usually serialize a reference to that referenced asset. However JSON does not support any kind of referencing.
It depends on what you actually want / need. If the scriptable objects are actual assets inside your project and you want to recover the references to them, you need to find a workaround. If you want to recreate them from the serialized data, you need a different approach.
In the first case, yes you need some way to actually reference your existing scriptable objects. This could be a unique name and using Resources.Load, or you could have a manager in your project that has a simple array with all items so you just have to find and store the index of the item in that array.
In the second case it gets a bit tricky. Since scriptable objects are seperate standalone assets they need to be serialized as such. Of course you could serialize each instance to json, put that string into another class or your list and serialize this collection. Of course the json strings would serialized as text in the outer json text. However this process can be reversed without issues. So when you deserialize the collection, you get a list of strings. You can then deserialize each of those strings to recreate all those scriptable objects and reassemble the original structure.
actually, all it still saves are empty Lists inside the GameData object...
I'm using a custom constructor when i create my HeroCard. even tho it's "saved", would i need to reconstruct my cards? if that's the case, i suppose i could save a List of strings for their names and Lists of ints for their levels, etc....just seems silly to have to go through all that
Answer by CyrinGames · Aug 31, 2020 at 10:37 PM
I'm dumb. After fiddling with having several Lists containing each bit of info, i ran into a wall concerning my method of Loadiing. It relied on everything being in order and if something was removed from one, I'd have to remove the same index from each of the rest. The wall came from the way I load the saved Team by their IDs. Long story short, I went back to my research and found a detail I thought i'd missed, but i hadn't. However, it did lead me to the culprit.
I was creating a new List of HeroCards instead of saving the one i already had. It opened up the possibilities and I came up with some simple sh-tuff. I found the wonders of Dictionaries even more delightful than I had already. Though you can't save Dictionaries themselves, you can...can't remember the word they used...squash (i guess) them down to a List or Array. I'm partial to Lists, myself, and i'll explain how here in a minute. For the Dictionary I use an integer as the Key, the HeroCard's ID, and store the HeroCard itself to it.
Anyway, I "convert" this to a List of HeroCards when i save, and save the List in my GameInfo class. Here's some code. And i won't accept my own answer unless it gets a few points. Didnt realize accepting closed it to more answers.
void SaveCardData()
{
HeroCollection.Instance.heroCards.Clear();
for (int i = 0; i < collectionCap; i++)
{
if (HeroCollection.Instance.CollectionHeroCards.ContainsKey(i))
{
HeroCollection.Instance.heroCards.Add(HeroCollection.Instance.CollectionHeroCards[i]);
Debug.Log("Added Card with ID " + i + " to saved Hero Cards");
if (HeroCollection.Instance.heroCards.Count == HeroCollection.Instance.CollectionHeroCards.Count)
{
Debug.Log("All Hero Cards have been added from the Dictionary. Breaking after " + (i + 1) + " iterations");
break;
}
}
else Debug.Log("No Hero Card with ID " + i + "was found");
}
HeroCards = HeroCollection.Instance.heroCards;
}
Can't do that by the Count of the Dictionary, since there could be cards with IDs higher, so i went with the Collection Cap. Though cards may have higher IDs than the count, a new card will get the lowest missing ID.
Here's how I save, load, create, and delete the save files:
public void SaveDataToJsonFile() { SaveCardData();
string jsonData = JsonUtility.ToJson(this, true);
byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
try
{
File.WriteAllBytes(path, jsonByte);
Debug.Log("Saved Data to: " + path.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Add PlayerInfo Data to: " + path.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}
private void LoadDataFromJsonFile()
{
// if file exist, read and load to heroCards
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
if (!File.Exists(path))
{
Debug.Log("File does not exist");
CreateNewSave();
}
else
{
byte[] jsonByte;
try
{
jsonByte = File.ReadAllBytes(path);
Debug.Log("Loaded Data from: " + path.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Load Data from: " + path.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
DeleteData();
return;
}
//Convert to json string
string jsonData = Encoding.ASCII.GetString(jsonByte);
JsonUtility.FromJsonOverwrite(jsonData, this);
LoadCardData();
LoadTeamData();
}
}
void LoadCardData()
{
///*
for (int i = 0; i < HeroCards.Count; i++)
{
HeroCard newHeroCard = HeroCards[i];
if(newHeroCard.cardID < 6)
{
newHeroCard.cardLocked = true;
}
HeroCollection.Instance.heroCards.Add(newHeroCard);
HeroCollection.Instance.CollectionHeroCards.Add(newHeroCard.cardID, newHeroCard);
}
//*/
if (HeroCollection.Instance.heroCards.Count < 5)
{
Debug.Log("Hero Cards weren't saved!");
CreateNewSave();
}
}
public void CreateNewSave()
{
for (int i = 0; i < 5; i++)
{
HeroCollection.Instance.SummonNewCard(HeroCollection.Instance.firstHeroAssets[i].name);
}
SaveDataToJsonFile();
}
public void DeleteData()
{
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Debug.LogWarning("Directory does not exist");
return;
}
if (!File.Exists(path))
{
Debug.Log("File does not exist");
return;
}
try
{
File.Delete(path);
Debug.Log("Data deleted from: " + path.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Delete Data: " + e.Message);
}
UnityEngine.SceneManagement.SceneManager.LoadScene(0);
}
Oh, and here's my path:
string path = Path.Combine(Application.persistentDataPath, "saved files", "game_data.txt");
I believe it is important to make this a .txt file and not a .json, at least for Android dev
Your answer
Follow this Question
Related Questions
Saving/Loading class list with SimpleJSON,Saving class list with SimpleJSON 1 Answer
How i can PlayerPrefs List<>? 0 Answers
How to save and load `List myList`? 1 Answer
Save and Load Function 2 Answers
A node in a childnode? 1 Answer