public static class TableDefinitionLoader
Namespace: SharpMeter.Core.Tables
Remarks
JSON format:
{
"tableId": 1,
"name": "General Manufacturer ID",
"description": "Standard Table 1",
"expectedSize": 32,
"fields": [
{ "name": "MANUFACTURER", "offset": 0, "size": 4, "type": "ascii", "description": "Manufacturer code" },
{ "name": "HW_VERSION", "offset": 12, "size": 1, "type": "uint8" }
]
}
Methods
| Name | Description |
|---|---|
FromFile(string filePath) static |
Loads table definition(s) from a JSON file. Supports single object or array. |
FromJson(string json) static |
Loads a single table definition from a JSON string. |
FromJsonArray(string json) static |
Loads multiple table definitions from a JSON array string. |
FromFile(string filePath)
Result<IReadOnlyList<TableDefinition>> TableDefinitionLoader.FromFile(string filePath)
Loads table definition(s) from a JSON file. Supports single object or array.
Parameters
| Name | Type | Description |
|---|---|---|
filePath | string | The path to the JSON file. |
Returns: The list of table definitions, or an error.
FromJson(string json)
Result<TableDefinition> TableDefinitionLoader.FromJson(string json)
Loads a single table definition from a JSON string.
Parameters
| Name | Type | Description |
|---|---|---|
json | string | The JSON string. |
Returns: The table definition, or an error.
FromJsonArray(string json)
Result<IReadOnlyList<TableDefinition>> TableDefinitionLoader.FromJsonArray(string json)
Loads multiple table definitions from a JSON array string.
Parameters
| Name | Type | Description |
|---|---|---|
json | string | The JSON array string. |
Returns: The list of table definitions, or an error.
View Source
/// <summary>
/// Loads <see cref = "TableDefinition"/> instances from JSON schema files.
/// Supports single table definitions or arrays of definitions.
/// </summary>
/// <remarks>
/// <para>JSON format:</para>
/// <code>
/// {
/// "tableId": 1,
/// "name": "General Manufacturer ID",
/// "description": "Standard Table 1",
/// "expectedSize": 32,
/// "fields": [
/// { "name": "MANUFACTURER", "offset": 0, "size": 4, "type": "ascii", "description": "Manufacturer code" },
/// { "name": "HW_VERSION", "offset": 12, "size": 1, "type": "uint8" }
/// ]
/// }
/// </code>
/// </remarks>
public static class TableDefinitionLoader
{
#region JSON Options
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
#endregion
#region Public Methods
/// <summary>
/// Loads a single table definition from a JSON string.
/// </summary>
/// <param name = "json">The JSON string.</param>
/// <returns>The table definition, or an error.</returns>
public static Result<TableDefinition> FromJson(string json)
{
ArgumentNullException.ThrowIfNull(json);
try
{
JsonTableSchema? schema = JsonSerializer.Deserialize<JsonTableSchema>(json, JsonOptions);
if (schema is null)
return PsemError.Framing("Failed to parse JSON table schema");
return ConvertSchema(schema);
}
catch (JsonException ex)
{
return PsemError.Framing($"Invalid JSON table schema: {ex.Message}");
}
}
/// <summary>
/// Loads multiple table definitions from a JSON array string.
/// </summary>
/// <param name = "json">The JSON array string.</param>
/// <returns>The list of table definitions, or an error.</returns>
public static Result<IReadOnlyList<TableDefinition>> FromJsonArray(string json)
{
ArgumentNullException.ThrowIfNull(json);
try
{
JsonTableSchema[]? schemas = JsonSerializer.Deserialize<JsonTableSchema[]>(json, JsonOptions);
if (schemas is null)
return PsemError.Framing("Failed to parse JSON table schema array");
var definitions = new List<TableDefinition>(schemas.Length);
foreach (JsonTableSchema schema in schemas)
{
Result<TableDefinition> result = ConvertSchema(schema);
if (result.IsFailure)
return result.Error;
definitions.Add(result.Value);
}
return Result<IReadOnlyList<TableDefinition>>.Success(definitions);
}
catch (JsonException ex)
{
return PsemError.Framing($"Invalid JSON table schema: {ex.Message}");
}
}
/// <summary>
/// Loads table definition(s) from a JSON file. Supports single object or array.
/// </summary>
/// <param name = "filePath">The path to the JSON file.</param>
/// <returns>The list of table definitions, or an error.</returns>
public static Result<IReadOnlyList<TableDefinition>> FromFile(string filePath)
{
ArgumentNullException.ThrowIfNull(filePath);
if (!File.Exists(filePath))
return PsemError.Framing($"Table definition file not found: {filePath}");
var json = File.ReadAllText(filePath);
var trimmed = json.TrimStart();
// Detect array vs object
if (trimmed.StartsWith('['))
return FromJsonArray(json);
Result<TableDefinition> single = FromJson(json);
return single.IsSuccess ? Result<IReadOnlyList<TableDefinition>>.Success([single.Value]) : single.Error;
}
#endregion
#region Schema Conversion
private static Result<TableDefinition> ConvertSchema(JsonTableSchema schema)
{
if (schema.Fields is null || schema.Fields.Length == 0)
return PsemError.Framing($"Table '{schema.Name}' has no fields defined");
ImmutableArray<TableFieldDefinition>.Builder fields = ImmutableArray.CreateBuilder<TableFieldDefinition>(schema.Fields.Length);
foreach (JsonFieldSchema field in schema.Fields)
{
FieldType? fieldType = ParseFieldType(field.Type);
if (fieldType is null)
return PsemError.Framing($"Unknown field type '{field.Type}' in field '{field.Name}'");
ImmutableArray<BitFieldDefinition> bitFields = ImmutableArray<BitFieldDefinition>.Empty;
if (field.BitFields is { Length: > 0 })
{
ImmutableArray<BitFieldDefinition>.Builder bitBuilder = ImmutableArray.CreateBuilder<BitFieldDefinition>(field.BitFields.Length);
foreach (JsonBitFieldSchema bf in field.BitFields)
{
ImmutableDictionary<int, string>? enumValues = null;
if (bf.Values is { Count: > 0 })
enumValues = ImmutableDictionary.CreateRange(bf.Values.Select(kv => KeyValuePair.Create(int.Parse(kv.Key, CultureInfo.InvariantCulture), kv.Value)));
bitBuilder.Add(new BitFieldDefinition(bf.Name ?? string.Empty, bf.StartBit, bf.BitCount, enumValues));
}
bitFields = bitBuilder.ToImmutable();
}
ImmutableDictionary<int, string> enumValues2 = ImmutableDictionary<int, string>.Empty;
if (field.EnumValues is { Count: > 0 })
enumValues2 = ImmutableDictionary.CreateRange(field.EnumValues.Select(kv => KeyValuePair.Create(int.Parse(kv.Key, CultureInfo.InvariantCulture), kv.Value)));
fields.Add(new TableFieldDefinition { Name = field.Name ?? string.Empty, Offset = field.Offset, Size = field.Size, Type = fieldType.Value, Description = field.Description ?? string.Empty, IsReadOnly = field.IsReadOnly, BitFields = bitFields, EnumValues = enumValues2 });
}
return new TableDefinition
{
TableId = schema.TableId,
Name = schema.Name ?? string.Empty,
Description = schema.Description ?? string.Empty,
ExpectedSize = schema.ExpectedSize,
Fields = fields.ToImmutable()
};
}
private static FieldType? ParseFieldType(string? type) => type?.ToUpperInvariant() switch
{
"HEX" => FieldType.Hex,
"ASCII" => FieldType.Ascii,
"STRING" or "UTF8" => FieldType.String,
"UINT8" or "BYTE" => FieldType.UInt8,
"UINT16" or "USHORT" => FieldType.UInt16,
"UINT32" or "UINT" => FieldType.UInt32,
"INT8" or "SBYTE" => FieldType.Int8,
"INT16" or "SHORT" => FieldType.Int16,
"INT32" or "INT" => FieldType.Int32,
"BOOL" or "BOOLEAN" => FieldType.Boolean,
"BITFIELD" or "BITS" => FieldType.BitField,
"BITARRAY" => FieldType.BitArray,
"ENUM" => FieldType.Enum,
"BCD" => FieldType.Bcd,
"DATETIME" or "TIME" => FieldType.DateTime,
_ => null
};
#endregion
#region JSON Schema Types
internal sealed class JsonTableSchema
{
[JsonPropertyName("tableId")]
public ushort TableId { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("description")]
public string? Description { get; set; }
[JsonPropertyName("expectedSize")]
public int ExpectedSize { get; set; }
[JsonPropertyName("fields")]
public JsonFieldSchema[]? Fields { get; set; }
}
internal sealed class JsonFieldSchema
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("offset")]
public int Offset { get; set; }
[JsonPropertyName("size")]
public int Size { get; set; }
[JsonPropertyName("type")]
public string? Type { get; set; }
[JsonPropertyName("description")]
public string? Description { get; set; }
[JsonPropertyName("isReadOnly")]
public bool IsReadOnly { get; set; }
[JsonPropertyName("bitFields")]
public JsonBitFieldSchema[]? BitFields { get; set; }
[JsonPropertyName("enumValues")]
public Dictionary<string, string>? EnumValues { get; set; }
}
internal sealed class JsonBitFieldSchema
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("startBit")]
public int StartBit { get; set; }
[JsonPropertyName("bitCount")]
public int BitCount { get; set; }
[JsonPropertyName("values")]
public Dictionary<string, string>? Values { get; set; }
}
#endregion
}