Interface ICookieSource and extension methods to save and load instance properties to a cookie store. It finds properties marked with PropertyAttribute (excluding static or JsonIgnore), serializes them to JSON and stores them via Game.Cookies, and can read and deserialize them back into the instance.
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// We want to save the properties of this instance as cookies.
/// </summary>
public interface ICookieSource
{
/// <summary>
/// Prefix to put before the names of any cookies, without a trailing period.
/// For example: <c>"tool.balloon"</c>
/// </summary>
string CookiePrefix { get; }
}
/// <summary>
/// Extension methods for <see cref="ICookieSource"/>.
/// </summary>
public static class CookieSourceExtensions
{
private static bool IsCookieProperty( PropertyDescription property )
{
// TODO: maybe we want to support static properties too?
if ( property.IsStatic ) return false;
if ( !property.HasAttribute<PropertyAttribute>() ) return false;
if ( property.HasAttribute<JsonIgnoreAttribute>() ) return false;
if ( !property.CanWrite || !property.CanRead ) return false;
return true;
}
private static string GetCookieName( PropertyDescription property, string prefix )
{
var name = property.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? property.Name.ToLowerInvariant();
return !string.IsNullOrEmpty( prefix ) ? $"{prefix}.{name}" : name;
}
extension( ICookieSource source )
{
private IEnumerable<(string CookieName, PropertyDescription Property)> GetCookieProperties()
{
var typeDesc = TypeLibrary.GetType( source.GetType() );
if ( typeDesc is null ) return [];
var prefix = source.CookiePrefix;
return typeDesc.Properties.Where( IsCookieProperty )
.Select( x => (GetCookieName( x, prefix ), x) );
}
/// <summary>
/// Saves any instance properties with a <see cref="PropertyAttribute"/>, excluding any
/// with <see cref="JsonIgnoreAttribute"/>, into <see cref="Game.Cookies"/>.
/// </summary>
public void SaveCookies()
{
foreach ( var (cookieName, property) in source.GetCookieProperties() )
{
try
{
var cookieValue = property.GetValue( source );
Game.Cookies.SetString( cookieName, JsonSerializer.Serialize( cookieValue, property.PropertyType ) );
}
catch ( Exception ex )
{
Log.Warning( ex, $"Exception while saving cookie \"{cookieName}\"." );
}
}
}
/// <summary>
/// Loads any instance properties with a <see cref="PropertyAttribute"/>, excluding any
/// with <see cref="JsonIgnoreAttribute"/>, from <see cref="Game.Cookies"/>.
/// </summary>
public void LoadCookies()
{
foreach ( var (cookieName, property) in source.GetCookieProperties() )
{
if ( !Game.Cookies.TryGetString( cookieName, out var jsonString ) ) continue;
try
{
var cookieValue = JsonSerializer.Deserialize( jsonString, property.PropertyType );
property.SetValue( source, cookieValue );
}
catch ( Exception ex )
{
Log.Warning( ex, $"Exception while loading cookie \"{cookieName}\"." );
}
}
}
}
}