120 lines
3.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|