I’m trying to save some data to JSON. I have a class that is being saved and it contains a Dictionary.
My class looks as such:
[Serializable]
public class Level
{
public string id;
public int width;
public int depth;
public Dictionary<Vector2, Columns> otherColums = new Dictionary<Vector2, Columns>();
}
and is contained within another class:
[Serializable]
public class LevelArray
{
public Level level;
}
There will eventually be multiple levels to be loaded, but for now I just have the one.
The id, width and depth will all save to json perfectly fine when I attempt as such:
string content = JsonUtility.ToJson (levelArray); // reference to a LevelArray
File.WriteAllText (Application.dataPath + "/" + saveDirectory + "/" + fileName + ".json", content);
However, I don’t seem to get anything out from the Dictionary I’ve tried to save. Here’s an example of a saved json I have:
{"level":{"id":"Test","width":15,"depth":150}}
These are values that I’ve manually manipulated and seems to work for everything else, but I’m having trouble with my dictionary. If anyone could help me out, that’d be great thanks.
Well, the BinaryFormatter approach has a problem: Vector2 is not marked as “Serializable”. So a Vector2 can’t be serialized by usual serialization systems. Unity’s own serializer does support built-in types like Vector2/3/4 explicitly.
In the question you explicitly asked for JSON but it seems you just want to “store some data”. You have several possibilities:
- You can use the BinaryFormatter, but you have to use a replacement for Vector2. Your own class with 2 float variables which is serialisable will do. Keep in mind that the Dictionary needs the key type to implement GetHashCode and Equals in order to work as key. You could implement implicit casting operators to convert between Vector2 and your custom class.
- You can use the BinaryFormatter and have your Level class to implement the ISerializable interface. In the GetObjectData method and the deserialization constructor you would need to handle the serialization of all members yourself, including the dictionary.
- You can use Unity’s JsonUtility, but you have to play by the rules of Unity’s serialization system. So you need to use a [SerializableDictionary][1] implementation instead of the normal Dictionary. This should be serializable by Unity. The resulting JSON of course doesn’t contain a dictionary but two seperate arrays, one for the key list and one for the value list.
- You can more or less roll your own way of serializing the data. You could use JSON by using some kind of JSON library ( for example my [SimpleJSON][2] class) and implement your own serialize / deserialize methods to map the members of your classes to the JSON structure. However since your data look a bit like it should represent some sort of Voxel data i would recommend to use your own binary format in order to keep the size small. The BinaryWriter / BinaryReader classes are quite handy for manual serialization. The size will be quite a bit smaller than what the BinaryFormatter could ever reach. However you have to handle everything yourself.
Example:
[Serializable]
public class Columns
{
public List<int> blockIDs = new List<int>();
public List<int> rotation = new List<int>();
public List<float> height = new List<float>();
public void Serialize(BinaryWriter writer)
{
writer.Write(blockIDs.Count);
for (int i = 0; i < blockIDs.Count; i++)
writer.Write(blockIDs*);*
writer.Write(rotation.Count);
for (int i = 0; i < rotation.Count; i++)
writer.Write(rotation*);*
writer.Write(height.Count);
for (int i = 0; i < height.Count; i++)
writer.Write(height*);*
}
public void Deserialize(BinaryReader reader)
{
int count = reader.ReadInt32();
blockIDs.Clear();
blockIDs.Capacity = count;
for (int i = 0; i < count; i++)
blockIDs.Add(reader.ReadInt32()); // Int32 == int
count = reader.ReadInt32();
rotation.Clear();
rotation.Capacity = count;
for (int i = 0; i < count; i++)
rotation.Add(reader.ReadInt32()); // Int32 == int
count = reader.ReadInt32();
height.Clear();
height.Capacity = count;
for (int i = 0; i < count; i++)
height.Add(reader.ReadSingle()); // Single == float
}
}
[Serializable]
public class Level
{
public string id;
public int width;
public int depth;
public Dictionary<Vector2, Columns> otherColums = new Dictionary<Vector2, Columns>();
public void Serialize(BinaryWriter writer)
{
writer.Write(id);
writer.Write(width);
writer.Write(depth);
writer.Write(otherColums.Count);
foreach(var kv in otherColums)
{
writer.Write(kv.Key.x);
writer.Write(kv.Key.y);
kv.Value.Serialize(writer);
}
}
public void Deserialize(BinaryReader reader)
{
id = reader.ReadString();
width = reader.ReadInt32();
depth = reader.ReadInt32();
int count = reader.ReadInt32();
otherColums.Clear();
for (int i = 0; i < count; i++)
{
Vector2 key = new Vector2(reader.ReadSingle(), reader.ReadSingle());
Columns c = new Columns();
c.Deserialize(reader);
otherColums.Add(key, c);
}
}
}
[Serializable]
public class LevelArray
{
public Level level;
public static void SaveLevelToFile(Level level, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
using (BinaryWriter writer = new BinaryWriter(fs))
{
level.Serialize(writer);
}
}
public static Level LoadLevelFromFile(string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
Level level = new Level();
level.Deserialize(reader);
return level;
}
}
}
This is pretty much the most compact format you can get without compression. It’s just plain binary data. Keep in mind that reading and writing always has to match 100%. So you have to ensure the deserialize method “knows” everything it needs to interpret the binary stream. Also make sure to use the right format.
Binary formats have massive problems when it comes to changes of the data structures. You would need to adjust the serialize and deserialize methods accordingly. Also any save file that has been saved with the “old” format can’t be loaded with the “new” loader unless you take care of this by storing some sort of “version” along with the file and based on that use a different serialization method.
*[1]: http://answers.unity3d.com/answers/809221/view.html*_
*[2]: http://wiki.unity3d.com/index.php/SimpleJSON*_
Dictionaries generally aren’t natively serializable. You need to do one of the following to make it work:
1.) Use a third party serializer that understands Dictionaries
2.) Modify or extend your current serializer to handle those fields
3.) Convert the dictionary to a serializable structure before saving, and convert back after saving.