TableDefinition instances from JSON schema files. Supports single table definitions or arrays of definitions." /> TableDefinition instances from JSON schema files. Supports single table definitions or arrays of definitions." /> TableDefinition instances from JSON schema files. Supports single table definitions or arrays of definitions." />
Class Static
public static class TableDefinitionLoader

Namespace: SharpMeter.Core.Tables

Loads TableDefinition instances from JSON schema files. Supports single table definitions or arrays of definitions.

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

NameDescription
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

NameTypeDescription
filePathstringThe 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

NameTypeDescription
jsonstringThe 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

NameTypeDescription
jsonstringThe 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
}
Was this page helpful?