feat(commons): AttributeValueCodec for canonical list value encode/decode
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical, round-trippable codec for attribute values. Scalars encode to an
|
||||
/// invariant-culture string (identical to the historical representation); List
|
||||
/// attributes encode to a JSON array. Used wherever a value is stored or
|
||||
/// transmitted (DB Value column, site SQLite, gRPC wire). <see cref="ValueFormatter"/>
|
||||
/// remains a separate, display-only (comma-joined) formatter.
|
||||
/// </summary>
|
||||
public static class AttributeValueCodec
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = false };
|
||||
|
||||
/// <summary>Encodes a value to its canonical string form.</summary>
|
||||
public static string? Encode(object? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null: return null;
|
||||
case string s: return s; // already canonical
|
||||
case IFormattable f: return f.ToString(null, CultureInfo.InvariantCulture);
|
||||
case IEnumerable e:
|
||||
var items = e.Cast<object?>()
|
||||
.Select(x => x is IFormattable xf
|
||||
? xf.ToString(null, CultureInfo.InvariantCulture)
|
||||
: x?.ToString());
|
||||
return JsonSerializer.Serialize(items, JsonOpts);
|
||||
default: return value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a canonical string. For <see cref="DataType.List"/> returns a typed
|
||||
/// <c>List<T></c>; for scalars returns the string unchanged. Throws
|
||||
/// <see cref="FormatException"/> on malformed list JSON or an un-parseable element.
|
||||
/// </summary>
|
||||
public static object? Decode(string? value, DataType dataType, DataType? elementType)
|
||||
{
|
||||
if (dataType != DataType.List) return value; // scalar: unchanged
|
||||
if (string.IsNullOrEmpty(value)) return null;
|
||||
if (elementType is null)
|
||||
throw new FormatException("List attribute requires an element type.");
|
||||
|
||||
string?[] raw;
|
||||
try { raw = JsonSerializer.Deserialize<string?[]>(value) ?? []; }
|
||||
catch (JsonException ex) { throw new FormatException("Malformed list JSON.", ex); }
|
||||
|
||||
var clrType = ElementClrType(elementType.Value);
|
||||
var listType = typeof(List<>).MakeGenericType(clrType);
|
||||
var result = (IList)Activator.CreateInstance(listType)!;
|
||||
foreach (var item in raw)
|
||||
result.Add(ParseScalar(item, elementType.Value));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Type ElementClrType(DataType t) => t switch
|
||||
{
|
||||
DataType.String => typeof(string),
|
||||
DataType.Int32 => typeof(int),
|
||||
DataType.Float => typeof(float),
|
||||
DataType.Double => typeof(double),
|
||||
DataType.Boolean => typeof(bool),
|
||||
DataType.DateTime => typeof(DateTime),
|
||||
_ => throw new FormatException($"Unsupported list element type '{t}'.")
|
||||
};
|
||||
|
||||
private static object? ParseScalar(string? s, DataType t)
|
||||
{
|
||||
if (s is null) throw new FormatException("List elements may not be null.");
|
||||
var c = CultureInfo.InvariantCulture;
|
||||
try
|
||||
{
|
||||
return t switch
|
||||
{
|
||||
DataType.String => s,
|
||||
DataType.Int32 => int.Parse(s, c),
|
||||
DataType.Float => float.Parse(s, c),
|
||||
DataType.Double => double.Parse(s, c),
|
||||
DataType.Boolean => bool.Parse(s),
|
||||
DataType.DateTime => DateTime.Parse(s, c, DateTimeStyles.RoundtripKind),
|
||||
_ => throw new FormatException($"Unsupported list element type '{t}'.")
|
||||
};
|
||||
}
|
||||
catch (Exception ex) when (ex is FormatException or OverflowException)
|
||||
{
|
||||
throw new FormatException($"List element '{s}' is not a valid {t}.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>True if the type may be a List element scalar.</summary>
|
||||
public static bool IsValidElementType(DataType t) =>
|
||||
t is DataType.String or DataType.Int32 or DataType.Float
|
||||
or DataType.Double or DataType.Boolean or DataType.DateTime;
|
||||
}
|
||||
Reference in New Issue
Block a user