using System; using System.Text; using FlowScope.Data; namespace FlowScope.Save { public interface ISaveSerializer { byte[] Serialize(T data); T Deserialize(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 data) { return Encoding.UTF8.GetBytes(SerializeToString(data)); } public T Deserialize(byte[] bytes) { if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } return DeserializeFromString(Encoding.UTF8.GetString(bytes)); } public string SerializeToString(T data) { var payload = ReactivePropertyJsonConverter.SerializeObject(data); if (_currentVersion <= 1) { return payload; } return ReactivePropertyJsonConverter.SerializeObject(new SaveEnvelope { Version = _currentVersion, Payload = payload }); } public T DeserializeFromString(string json) { var instance = (T)Activator.CreateInstance(typeof(T), true); PopulateFromString(ResolvePayload(json), instance); return instance; } public void PopulateFromString(string json, T target) { ReactivePropertyJsonConverter.PopulateObject(ResolvePayload(json), target); } private string ResolvePayload(string json) { if (!TryReadEnvelope(json, out var envelope)) { return _currentVersion > 1 ? _migrations.Migrate(GetMigrationKey(), 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(), envelope.Version, _currentVersion, envelope.Payload); } private static bool TryReadEnvelope(string json, out SaveEnvelope envelope) { envelope = null; try { envelope = ReactivePropertyJsonConverter.DeserializeObject(json); return envelope.Version > 0 && envelope.Payload != null; } catch { return false; } } private static string GetMigrationKey() { return typeof(T).FullName; } } }