Newtonsoft JSON

This sample demonstrates a complete file-based save system using Newtonsoft JSON within Luna. It shows how to implement a concrete save manager that works seamlessly with Luna's UI components.

Refer to Save System Framework to learn about the UI-agnostic save system architecture.

Key Features: Pluggable serializers, secure type allow-lists, fast metadata loading, and complete UI integration.

What You Get

  • GameSaveManagerExample — concrete manager handling files, folders, and JSON IO
  • GameSaveDataExample — example player/save data model
  • GameSaveMetadataExample — lightweight metadata for lists and previews
  • InitializeSerializerExample — safe serializer initialization with converters and type allow-list

Quick Start

1. Initialize Serializer

Add the serializer initializer to your bootstrap scene:

csharp
_serializationManager.Initialize( new Type[] { typeof(GameSaveMetadata), typeof(GameSaveMetadataExample), typeof(GameSaveDataExample), typeof(Inventory), typeof(InventoryItem), typeof(Potion), typeof(Equipment), }, new JsonConverter[] { new Vector2IntConverter(), new GenericDictionaryConverter() }, new PrivateSetterContractResolver() );

Warning: Keep the type allow-list tight for security when using TypeNameHandling features.

2. Create Save Data Classes

csharp
public class MyGameSaveData : IGameSaveData { [JsonProperty(Order = -100)] public GameSaveMetadata Metadata { get; set; } public string PlayerName; public int Gold; public Inventory Inventory; public MyGameSaveData() { PlayerName = "Player"; Gold = 0; Inventory = new Inventory(); } public GameSaveMetadata CreateMetadata(string saveVersion, bool isAutosave) { return new MyGameSaveMetadata { SaveVersion = saveVersion, SaveDate = DateTime.Now, IsAutosave = isAutosave, Gold = Gold, }; } public void LoadFrom(IGameSaveData other, int slot) { var o = (MyGameSaveData)other; Metadata = o.Metadata; PlayerName = o.PlayerName; Gold = o.Gold; Inventory = o.Inventory; } }

3. Create Metadata Class

Define lightweight metadata for UI display:

csharp
public class MyGameSaveMetadata : GameSaveMetadata { public int Gold; }

Info: Use [JsonProperty(Order = -100)] on Metadata so it appears first in JSON for fast metadata reads.

4. Implement Save Manager

Inherit from GameSaveManager<MyGameSaveData, MyGameSaveMetadata> and implement the JSON-specific methods:

csharp
public class MyGameSaveManager : GameSaveManager<MyGameSaveData, MyGameSaveMetadata> { protected override string[] GetAllFileNames() { // Enumerate .json files in save directory return Directory.GetFiles(GetSaveDirectory(), "*.json") .Select(Path.GetFileName) .ToArray(); } protected override MyGameSaveData GetNewSave(string saveVersion) { return new MyGameSaveData(); } protected override void OnSaveRequest(int saveSlot, string fileName, MyGameSaveData data, bool autosave) { // Serialize and write JSON file string json = JsonConvert.SerializeObject(data, Formatting.Indented); File.WriteAllText(Path.Combine(GetSaveDirectory(), fileName), json); } protected override MyGameSaveData LoadFromFile(string fileName) { // Deserialize full save data string json = File.ReadAllText(Path.Combine(GetSaveDirectory(), fileName)); return JsonConvert.DeserializeObject<MyGameSaveData>(json); } protected override MyGameSaveMetadata LoadMetadataFromFile(string fileName) { // Fast path to read only metadata using var reader = new JsonTextReader(new StringReader( File.ReadAllText(Path.Combine(GetSaveDirectory(), fileName)))); while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == "Metadata") { reader.Read(); return JsonConvert.DeserializeObject<MyGameSaveMetadata>( reader.ReadAsString()); } } return null; } protected override void OnDeleteRequest(int saveSlot, string fileName) { // Delete JSON file File.Delete(Path.Combine(GetSaveDirectory(), fileName)); } protected override string GetFileExtension() => "json"; protected override string GetSaveVersion() => "1.0"; }

UI Integration

Wire your manager into Luna UI components by overriding the provider method:

csharp
public class MyMainMenuView : MainMenuView<MyGameSaveData, MyGameSaveMetadata> { [SerializeField] private MyGameSaveManager saveManager; protected override GameSaveManager<MyGameSaveData, MyGameSaveMetadata> GetSaveManager() { return saveManager; } }

Info: Once connected, UI buttons (Continue, Load, New Game, Delete) automatically work through your manager. No additional UI code needed!

File Locations

  • Save Directory: Application.persistentDataPath/saves/
  • File Extension: .json (configured by your manager)
  • Version: Set via GetSaveVersion() method

Advanced Features

Encryption

Add encryption/decryption in your manager's save/load methods:

csharp
protected override void OnSaveRequest(int saveSlot, string fileName, MyGameSaveData data, bool autosave) { string json = JsonConvert.SerializeObject(data, Formatting.Indented); byte[] encrypted = Encrypt(json); // Your encryption logic File.WriteAllBytes(Path.Combine(GetSaveDirectory(), fileName), encrypted); } protected override MyGameSaveData LoadFromFile(string fileName) { byte[] encrypted = File.ReadAllBytes(Path.Combine(GetSaveDirectory(), fileName)); string json = Decrypt(encrypted); // Your decryption logic return JsonConvert.DeserializeObject<MyGameSaveData>(json); }

Cloud Storage

Replace file IO with cloud API calls while keeping the same UI interface:

csharp
protected override void OnSaveRequest(int saveSlot, string fileName, MyGameSaveData data, bool autosave) { string json = JsonConvert.SerializeObject(data); CloudStorage.Upload(fileName, json); // Your cloud API } protected override MyGameSaveData LoadFromFile(string fileName) { string json = CloudStorage.Download(fileName); // Your cloud API return JsonConvert.DeserializeObject<MyGameSaveData>(json); }

Troubleshooting

Warning: No save files found

Ensure Application.persistentDataPath/saves exists and the platform has write permissions.

Warning: Metadata shows null

Ensure Metadata is serialized first with [JsonProperty(Order = -100)] and created before saving.

Warning: Deserialization errors

Update the serializer allow-list and add required converters for custom types.

Warning: UI buttons disabled

Verify your manager returns valid metadata from GetLastMetadata() and the serializer is properly initialized.

Best Practices

  • Use [JsonProperty(Order = -100)] on Metadata for fast metadata reads
  • Implement fast metadata loading to avoid deserializing full saves for UI lists
  • Keep type allow-lists minimal for security when using TypeNameHandling
  • Handle version migration in your load methods for schema changes
  • Use autosave functionality built into the manager
  • Test save/load on target platforms early in development

Info: Remember: The UI remains completely unchanged when you swap between JSON, binary, cloud, or encrypted storage. Only your manager implementation changes.

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL