Files
FlowScope/Packages/com.flowscope.gamecore/Runtime/Save/JsonSaveSerializer.cs
2026-06-04 14:54:49 +08:00

120 lines
3.4 KiB
C#

using System;
using System.Text;
using FlowScope.Data;
namespace FlowScope.Save
{
public interface ISaveSerializer
{
byte[] Serialize<T>(T data);
T Deserialize<T>(byte[] bytes);
}
public sealed class JsonSaveSerializer : ISaveSerializer
{
private readonly int _currentVersion;
private readonly SaveMigrationRegistry _migrations;
public JsonSaveSerializer(
int currentVersion = 1,
SaveMigrationRegistry migrations = null)
{
if (currentVersion < 1)
{
throw new ArgumentOutOfRangeException(nameof(currentVersion));
}
_currentVersion = currentVersion;
_migrations = migrations ?? new SaveMigrationRegistry();
}
public byte[] Serialize<T>(T data)
{
return Encoding.UTF8.GetBytes(SerializeToString(data));
}
public T Deserialize<T>(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException(nameof(bytes));
}
return DeserializeFromString<T>(Encoding.UTF8.GetString(bytes));
}
public string SerializeToString<T>(T data)
{
var payload = ReactivePropertyJsonConverter.SerializeObject(data);
if (_currentVersion <= 1)
{
return payload;
}
return ReactivePropertyJsonConverter.SerializeObject(new SaveEnvelope
{
Version = _currentVersion,
Payload = payload
});
}
public T DeserializeFromString<T>(string json)
{
var instance = (T)Activator.CreateInstance(typeof(T), true);
PopulateFromString(ResolvePayload<T>(json), instance);
return instance;
}
public void PopulateFromString<T>(string json, T target)
{
ReactivePropertyJsonConverter.PopulateObject(ResolvePayload<T>(json), target);
}
private string ResolvePayload<T>(string json)
{
if (!TryReadEnvelope(json, out var envelope))
{
return _currentVersion > 1
? _migrations.Migrate(GetMigrationKey<T>(), 1, _currentVersion, json)
: json;
}
if (envelope.Version == _currentVersion)
{
return envelope.Payload;
}
if (envelope.Version > _currentVersion)
{
throw new InvalidOperationException(
$"Save version {envelope.Version} is newer than current version {_currentVersion}.");
}
return _migrations.Migrate(
GetMigrationKey<T>(),
envelope.Version,
_currentVersion,
envelope.Payload);
}
private static bool TryReadEnvelope(string json, out SaveEnvelope envelope)
{
envelope = null;
try
{
envelope = ReactivePropertyJsonConverter.DeserializeObject<SaveEnvelope>(json);
return envelope.Version > 0 && envelope.Payload != null;
}
catch
{
return false;
}
}
private static string GetMigrationKey<T>()
{
return typeof(T).FullName;
}
}
}