- Home /
How can I save a Dictionary in a class to JSON?
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.
Answer by FortisVenaliter · May 12, 2016 at 10:39 PM
Dictionaries generally aren't natively serializable. You need to do one of the following to make it work:
Use a third party serializer that understands Dictionaries
Modify or extend your current serializer to handle those fields
Convert the dictionary to a serializable structure before saving, and convert back after saving.
Well, the usual .NET serialization system does support the generic Dictionary. Or to put it right, the generic Dictionary does implement the ISerializable interface. So the BinaryFormatter for example can serialize Dictionaries by default.
However the Unity's "JsonUtility" class use the same approach as Unity's internal serialization system which does not support Dictionaries. It has the same limitations and it does as well support the ISerializationCallbackReceiver interface.
So the JsonUtility class just provides some very basic serialization features.
I'm not sure if there's a JSON serializer that's build on top of ISerializable. If there is one, it would support Dictionaries as well.. Though i wouldn't rely on the fact that the serialized data does look the way you want. The .NET serialization system does serialize type information as well, so the serialized object can be deserialized without any external "hints".
@Rellac:
What is "Columns"? Is it even serializable?
Answer by Bunny83 · May 13, 2016 at 10:44 AM
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 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 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[i]);
writer.Write(rotation.Count);
for (int i = 0; i < rotation.Count; i++)
writer.Write(rotation[i]);
writer.Write(height.Count);
for (int i = 0; i < height.Count; i++)
writer.Write(height[i]);
}
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.
Your answer
![](https://koobas.hobune.stream/wayback/20220612080653im_/https://answers.unity.com/themes/thub/images/avi.jpg)