[feat:] added json schema migration (ai slop, since this would be boring to implement) (untested)
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Logof_Client;
|
||||
|
||||
/// <summary>
|
||||
/// Handles schema versioning and migrations for JSON data structures.
|
||||
/// This system allows tracking schema changes and automatically migrating
|
||||
/// old data to new formats when the schema version changes.
|
||||
/// </summary>
|
||||
public static class SchemaMigration
|
||||
{
|
||||
// Current schema version - increment when you make breaking changes
|
||||
public const int CURRENT_SCHEMA_VERSION = 3;
|
||||
|
||||
// Dictionary of migration functions, keyed by the version they migrate TO
|
||||
private static readonly Dictionary<int, Func<JObject, JObject>> Migrations = new()
|
||||
{
|
||||
// Example: Migration from v1 to v2
|
||||
// When loading v1 data, this will be called to upgrade to v2
|
||||
// { 2, MigrateV1ToV2 },
|
||||
|
||||
// Example: Migration from v2 to v3
|
||||
// { 3, MigrateV2ToV3 },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks the schema version of loaded JSON and applies migrations if needed.
|
||||
/// </summary>
|
||||
public static JObject MigrateIfNeeded(JObject jsonObject, int? loadedVersion)
|
||||
{
|
||||
// If no version found, assume it's version 1 (legacy data)
|
||||
int sourceVersion = loadedVersion ?? 1;
|
||||
|
||||
// If versions match, no migration needed
|
||||
if (sourceVersion == CURRENT_SCHEMA_VERSION)
|
||||
return jsonObject;
|
||||
|
||||
// If loaded version is newer than current, warn and return as-is
|
||||
if (sourceVersion > CURRENT_SCHEMA_VERSION)
|
||||
{
|
||||
Logger.Log($"Warning: Data schema version {sourceVersion} is newer than application schema version {CURRENT_SCHEMA_VERSION}. " +
|
||||
"This might indicate data was created with a newer version of the application.",
|
||||
Logger.LogType.Warning);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
// Apply migrations sequentially from source version to current version
|
||||
Logger.Log($"Migrating data from schema version {sourceVersion} to {CURRENT_SCHEMA_VERSION}", Logger.LogType.Info);
|
||||
|
||||
for (int targetVersion = sourceVersion + 1; targetVersion <= CURRENT_SCHEMA_VERSION; targetVersion++)
|
||||
{
|
||||
if (Migrations.TryGetValue(targetVersion, out var migration))
|
||||
{
|
||||
try
|
||||
{
|
||||
jsonObject = migration(jsonObject);
|
||||
Logger.Log($"Successfully migrated to schema version {targetVersion}", Logger.LogType.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"Error during migration to version {targetVersion}: {ex.Message}", Logger.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"No migration defined from version {targetVersion - 1} to {targetVersion}", Logger.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example migration function: from v1 to v2.
|
||||
/// Rename 'old_field_name' to 'new_field_name'
|
||||
/// </summary>
|
||||
private static JObject MigrateV1ToV2(JObject obj)
|
||||
{
|
||||
// Example: Rename a field
|
||||
if (obj.TryGetValue("old_field_name", out var value))
|
||||
{
|
||||
obj["new_field_name"] = value;
|
||||
obj.Remove("old_field_name");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example migration function: from v2 to v3.
|
||||
/// Add a new required field with a default value.
|
||||
/// </summary>
|
||||
private static JObject MigrateV2ToV3(JObject obj)
|
||||
{
|
||||
// Example: Add a new field that didn't exist before
|
||||
if (!obj.ContainsKey("new_required_field"))
|
||||
{
|
||||
obj["new_required_field"] = "default_value";
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new migration function that will be called when upgrading to a specific version.
|
||||
/// Call this during application startup to register your custom migrations.
|
||||
/// </summary>
|
||||
/// <param name="targetVersion">The schema version this migration upgrades TO</param>
|
||||
/// <param name="migration">The migration function that transforms the JObject</param>
|
||||
public static void RegisterMigration(int targetVersion, Func<JObject, JObject> migration)
|
||||
{
|
||||
if (targetVersion <= 1)
|
||||
throw new ArgumentException("Target version must be greater than 1");
|
||||
|
||||
if (Migrations.ContainsKey(targetVersion))
|
||||
Logger.Log($"Warning: Overwriting existing migration for version {targetVersion}", Logger.LogType.Warning);
|
||||
|
||||
Migrations[targetVersion] = migration;
|
||||
Logger.Log($"Registered migration to schema version {targetVersion}", Logger.LogType.Info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method: Add a new property to an object if it doesn't exist
|
||||
/// </summary>
|
||||
public static void AddPropertyIfMissing(JObject obj, string propertyName, object defaultValue)
|
||||
{
|
||||
if (!obj.ContainsKey(propertyName))
|
||||
{
|
||||
obj[propertyName] = JToken.FromObject(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method: Rename a property in a JSON object
|
||||
/// </summary>
|
||||
public static void RenameProperty(JObject obj, string oldName, string newName)
|
||||
{
|
||||
if (obj.TryGetValue(oldName, out var value))
|
||||
{
|
||||
obj[newName] = value;
|
||||
obj.Remove(oldName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method: Move a nested property to the root level
|
||||
/// </summary>
|
||||
public static void FlattenProperty(JObject obj, string sourcePath, string targetPropertyName)
|
||||
{
|
||||
var value = obj.SelectToken(sourcePath);
|
||||
if (value != null)
|
||||
{
|
||||
obj[targetPropertyName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method: Merge a root property into a nested object
|
||||
/// </summary>
|
||||
public static void NestProperty(JObject obj, string propertyName, string nestedPath)
|
||||
{
|
||||
if (obj.TryGetValue(propertyName, out var value))
|
||||
{
|
||||
var parts = nestedPath.Split('.');
|
||||
JObject current = obj;
|
||||
|
||||
for (int i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
if (!current.ContainsKey(parts[i]))
|
||||
current[parts[i]] = new JObject();
|
||||
current = (JObject)current[parts[i]];
|
||||
}
|
||||
|
||||
current[parts[^1]] = value;
|
||||
obj.Remove(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user