Archived on 2026-10-24. This is a historical implementation note, not current setup instructions or an active task list. Original claims, code examples, file paths, and completion statements have not all been revalidated. Use the current documentation and issue register for maintenance decisions.
Newtonsoft.Json Implementation - Dictionary Serialization Solution¶
Overview¶
This document explains the implementation of Newtonsoft.Json (Json.NET) to handle Dictionary serialization in the Tiny-Chaos project, replacing Unity's limited JsonUtility.
The Problem¶
Unity's built-in JsonUtility cannot serialize Dictionary<TKey, TValue> types because:
- Dictionaries use internal data structures that JsonUtility cannot access via reflection
- JsonUtility only serializes public fields marked with [Serializable]
- When you serialize PlayerData, all dictionary fields become null after deserialization
Example of the problem with JsonUtility:
PlayerData data = new PlayerData();
data.unlockedAchievement = new Dictionary<string, bool>();
data.unlockedAchievement["ACH_1"] = true;
string json = JsonUtility.ToJson(data);
// Result: {"version":"1.0.0", ...}
// NOTE: unlockedAchievement field is MISSING from JSON!
PlayerData loaded = JsonUtility.FromJson<PlayerData>(json);
// Result: loaded.unlockedAchievement is NULL
The Solution¶
Newtonsoft.Json (Json.NET) provides native Dictionary support out of the box, making it simple and reliable.
What Was Changed¶
1. Added Newtonsoft.Json Package¶
- Added via Unity Package Manager
- Package:
Newtonsoft.Json - Version: Latest stable (compatible with Unity)
2. Rewrote JsonUtils.cs¶
Replaced JsonUtility with Newtonsoft.Json wrapper:
using Newtonsoft.Json;
public static class JsonUtils
{
public static string ToJson<T>(T obj, bool prettyPrint = false)
{
Formatting formatting = prettyPrint ? Formatting.Indented : Formatting.None;
return JsonConvert.SerializeObject(obj, formatting);
}
public static T FromJson<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json);
}
public static T[] GetArray<T>(string json)
{
return JsonConvert.DeserializeObject<T[]>(json);
}
}
3. SaveManager.cs (No Changes Needed!)¶
SaveManager already uses JsonUtils.ToJson() and JsonUtils.FromJson(), so it works seamlessly with the new implementation.
JSON Structure Comparison¶
Before (Unity JsonUtility - Broken)¶
{
"version": "1.0.0",
"soulPoint": 100,
"unlockedChar": {}, // Empty or missing
"unlockedAchievement": {}, // Empty or missing
"shopUpgrade": {}
}
After (Newtonsoft.Json - Working)¶
{
"version": "1.0.0",
"soulPoint": 100,
"unlockedChar": {
"SOLDIER": true,
"KNIGHT": false
},
"unlockedAchievement": {
"ACH_FIRST_WIN": true,
"ACH_HIGH_SCORE": false
},
"shopUpgrade": {
"UPG_DAMAGE": 3,
"UPG_HEALTH": 1
}
}
Note: Newtonsoft.Json serializes dictionaries as proper JSON objects (key-value pairs), which is cleaner and more standard.
Benefits of Newtonsoft.Json¶
1. Native Dictionary Support¶
- ✅
Dictionary<TKey, TValue>works out of the box - ✅ No wrapper classes needed
- ✅ No manual conversion code
- ✅ Clean JSON output
2. No Maintenance Overhead¶
- ✅ Automatically handles new fields in PlayerData
- ✅ No need to update JsonUtils when adding fields
- ✅ No need to manually maintain wrapper classes
- ✅ Just add field to PlayerData and it works
3. Powerful Features¶
- ✅ Supports complex nested types
- ✅ Supports generic types
- ✅ Supports attributes (JsonIgnore, JsonProperty, etc.)
- ✅ Supports custom converters
- ✅ Supports null handling options
- ✅ Supports formatting options
4. Industry Standard¶
- ✅ Widely used in .NET ecosystem
- ✅ Well-documented
- ✅ Active development and support
- ✅ Battle-tested in production
5. Consistent API¶
- ✅ JsonUtils wrapper provides consistent interface
- ✅ SaveManager code unchanged
- ✅ Minimal code changes required
Performance Considerations¶
Serialization Speed¶
- Newtonsof.Json is generally faster than JsonUtility for complex objects
- Dictionary handling is optimized
- No reflection overhead for field conversion
Memory Usage¶
- Similar to JsonUtility for simple types
- More efficient for complex/dictionary types
- Stream serialization available for large files
Build Size¶
- Adds ~500KB to final build size
- Single DLL assembly
- No additional dependencies
Comparison with Alternatives¶
| Approach | Pros | Cons |
|---|---|---|
| Newtonsoft.Json | Native dictionary support, no maintenance, powerful, standard | ~500KB build size |
| Unity JsonUtility | Built-in, no dependencies | Cannot serialize Dictionaries |
| Wrapper-based solution | No external dependencies | High maintenance, error-prone, manual updates needed |
| Manual fields | Fastest | Very verbose, hard to maintain |
Usage Examples¶
Basic Serialization¶
PlayerData data = new PlayerData();
data.InitializeDefaults();
data.unlockedAchievement["TEST_ACH"] = true;
string json = JsonUtils.ToJson(data, prettyPrint: true);
Debug.Log(json);
Basic Deserialization¶
string json = File.ReadAllText("save.json");
PlayerData loaded = JsonUtils.FromJson<PlayerData>(json);
Debug.Log($"Achievements: {loaded.unlockedAchievement.Count}");
Debug.Log($"ACH_TEST_ACH: {loaded.unlockedAchievement["TEST_ACH"]}");
SaveManager Integration¶
// Save
public void SavePlayerData()
{
Player.version = Application.version;
string json = JsonUtils.ToJson(Player);
string encrypted = EncryptionUtils.Encrypt(json);
Game.Save.WriteNew(CURRENT_PLAYER_FILE, encrypted);
}
// Load
private void LoadNewEncryptedSave(string PLAYER_FILE)
{
string encrypted = Game.Save.ReadNew(PLAYER_FILE);
string json = EncryptionUtils.Decrypt(encrypted);
Player = JsonUtils.FromJson<PlayerData>(json);
}
Installation Steps¶
1. Add Package¶
- Open Unity
- Window → Package Manager
- Click "+" → "Add package by name"
- Search:
Newtonsoft.Json - Click "Add" button
- Wait for package to install
2. Verify Installation¶
Check that Newtonsoft.Json appears in:
- Project Settings → Player → Other Settings → Preloaded Assemblies
- Or in Visual Studio references
3. Test Serialization¶
Run game and check console for JSON output with dictionaries properly serialized.
Testing¶
Test 1: Basic Serialization¶
PlayerData data = new PlayerData();
data.InitializeDefaults();
data.unlockedAchievement["TEST_ACH"] = true;
string json = JsonUtils.ToJson(data, prettyPrint: true);
Debug.Log(json);
// Verify JSON contains: "unlockedAchievement": { "TEST_ACH": true }
PlayerData loaded = JsonUtils.FromJson<PlayerData>(json);
Debug.Assert(loaded.unlockedAchievement["TEST_ACH"] == true);
Test 2: Empty Dictionary¶
PlayerData data = new PlayerData();
data.InitializeDefaults();
string json = JsonUtils.ToJson(data);
PlayerData loaded = JsonUtils.FromJson<PlayerData>(json);
Debug.Assert(loaded.unlockedAchievement != null);
Debug.Assert(loaded.unlockedAchievement.Count == 0);
Test 3: Save and Load Cycle¶
// Save
string json = JsonUtils.ToJson(Player);
string encrypted = EncryptionUtils.Encrypt(json);
Game.Save.WriteNew(FileConst.PLAYER_FILE, encrypted);
// Load
string encrypted = Game.Save.ReadNew(FileConst.PLAYER_FILE);
string json = EncryptionUtils.Decrypt(encrypted);
Player = JsonUtils.FromJson<PlayerData>(json);
Debug.Assert(Player.unlockedAchievement != null);
Test 4: Achievement Manager¶
// After loading save data
if (Player.unlockedAchievement != null)
{
Debug.Log($"Achievements loaded: {Player.unlockedAchievement.Count}");
foreach (var achievement in Player.unlockedAchievement)
{
Debug.Log($" {achievement.Key}: {achievement.Value}");
}
}
else
{
Debug.LogError("unlockedAchievement is NULL!");
}
Troubleshooting¶
Issue: "The type or namespace name 'Newtonsoft' could not be found"¶
Cause: Newtonsoft.Json package not installed.
Solution: 1. Open Unity Package Manager 2. Install Newtonsoft.Json package 3. Restart Unity
Issue: Dictionaries are still null after loading¶
Cause: Not using JsonUtils or still using old JsonUtility code.
Solution:
- Ensure all code uses JsonUtils.ToJson() and JsonUtils.FromJson()
- Don't use JsonUtility.ToJson() or JsonUtility.FromJson()
Issue: JSON format different than expected¶
Cause: Newtonsoft.Json uses different formatting than JsonUtility.
Solution:
- This is expected and correct behavior
- Newtonsoft.Json serializes dictionaries as objects: {"key": value}
- This is more standard than JsonUtility's array format
Issue: Build size too large¶
Cause: Newtonsoft.Json adds ~500KB to build.
Solution: - For mobile games, consider code stripping - For web builds, use IL2CPP code optimization - The 500KB is worth it for proper Dictionary support
Advanced Features¶
Custom Formatting¶
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);
Custom Converters¶
public class Vector3Converter : JsonConverter<Vector3>
{
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(value.x);
writer.WritePropertyName("y");
writer.WriteValue(value.y);
writer.WritePropertyName("z");
writer.WriteValue(value.z);
writer.WriteEndObject();
}
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// Implement deserialization
}
}
Attributes¶
[JsonObject(MemberSerialization.OptIn)]
public class PlayerData
{
[JsonProperty("version")]
public string version;
[JsonProperty("soulPoint")]
public int soulPoint;
[JsonProperty("unlockedAchievement")]
public Dictionary<string, bool> unlockedAchievement;
[JsonIgnore]
public string temporaryField; // Won't be serialized
}
Migration from Old Solutions¶
From JsonUtility¶
Simply replace:
- JsonUtility.ToJson(obj) → JsonUtils.ToJson(obj)
- JsonUtility.FromJson<T>(json) → JsonUtils.FromJson<T>(json)
From Wrapper-Based Solution¶
Delete all wrapper classes:
- Remove SerializableDictionaryStringBool
- Remove SerializableDictionaryStringInt
- Remove SerializationWrapper<T>
- Simplify code significantly
Summary¶
Newtonsoft.Json implementation provides: - ✅ Native Dictionary support - Works out of the box - ✅ No external wrapper code - Clean, simple implementation - ✅ No maintenance - Automatic handling of new fields - ✅ Standard JSON format - Clean, readable output - ✅ Powerful features - Attributes, converters, settings - ✅ Industry standard - Well-documented, tested - ✅ Easy to use - Simple JsonUtils wrapper
This solution permanently fixes the NullReferenceException in AchievementManager by ensuring unlockedAchievement dictionary is properly serialized to and from JSON files.
Related Documentation¶
- EncryptionBypassGuide.md - Debug methods for manual JSON inspection
- SaveDataDebuggerDocumentation.md - Comprehensive save data diagnostics
Support¶
For Newtonsoft.Json documentation: - Official docs: https://www.newtonsoft.com/json/help/html/Introduction.htm - GitHub: https://github.com/JamesNK/Newtonsoft.Json - Unity forums: Search for "Newtonsoft.Json Unity"