using RobotNet10.CustomConfiguration.Models; using System.Text.Json; using System.Text.Json.Serialization; namespace RobotNet10.CustomConfiguration.Helpers; /// /// Helper class cho parse và serialize JSON config files /// Format JSON mới: Object với metadata và variables /// Format: {"id": "guid", "configType": "...", "createdAt": "...", "updatedAt": "...", "description": "...", "variables": [...]} /// public static class JsonConfigParser { private static readonly JsonSerializerOptions JsonOptions = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never, WriteIndented = true }; /// /// Parse JSON stream thành ConfigFile (format mới với metadata) /// public static ConfigFile ParseConfigFile(Stream jsonStream) { using var reader = new StreamReader(jsonStream); var json = reader.ReadToEnd(); return ParseConfigFile(json); } /// /// Parse JSON string thành ConfigFile (format mới với metadata) /// public static ConfigFile ParseConfigFile(string json) { ArgumentException.ThrowIfNullOrWhiteSpace(json, nameof(json)); using var jsonDoc = JsonDocument.Parse(json); var root = jsonDoc.RootElement; if (root.ValueKind != JsonValueKind.Object) { throw new ArgumentException("JSON must be an object with metadata and variables"); } // Parse metadata var id = root.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.String ? Guid.Parse(idProp.GetString() ?? throw new ArgumentException("Invalid id format")) : throw new ArgumentException("id is required"); var configType = root.GetProperty("configType").GetString() ?? throw new ArgumentException("configType is required"); var createdAt = root.TryGetProperty("createdAt", out var createdAtProp) && createdAtProp.ValueKind == JsonValueKind.String ? DateTime.Parse(createdAtProp.GetString() ?? throw new ArgumentException("Invalid createdAt format")) : DateTime.UtcNow; var updatedAt = root.TryGetProperty("updatedAt", out var updatedAtProp) && updatedAtProp.ValueKind == JsonValueKind.String ? DateTime.Parse(updatedAtProp.GetString() ?? throw new ArgumentException("Invalid updatedAt format")) : DateTime.UtcNow; var description = root.TryGetProperty("description", out var descProp) && descProp.ValueKind == JsonValueKind.String ? descProp.GetString() : null; // Parse variables if (!root.TryGetProperty("variables", out var variablesProp) || variablesProp.ValueKind != JsonValueKind.Array) { throw new ArgumentException("variables array is required"); } var variables = ParseVariablesArray(variablesProp); return new ConfigFile { Id = id, ConfigType = configType, Variables = variables, CreatedAt = createdAt, UpdatedAt = updatedAt, Description = description, FilePath = $"configs/{configType}.config.json" }; } /// /// Parse variables array từ JSON element /// private static List ParseVariablesArray(JsonElement variablesElement) { var variables = new List(); foreach (var element in variablesElement.EnumerateArray()) { var variable = new ConfigVariable { Name = element.GetProperty("name").GetString() ?? throw new ArgumentException("Variable name is required"), Type = VariableTypeConverter.ParseType(element.GetProperty("type").GetString() ?? throw new ArgumentException("Variable type is required")), Value = ParseValue(element), Min = element.TryGetProperty("min", out var minProp) && minProp.ValueKind != JsonValueKind.Null ? minProp.GetDouble() : null, Max = element.TryGetProperty("max", out var maxProp) && maxProp.ValueKind != JsonValueKind.Null ? maxProp.GetDouble() : null, Roles = element.TryGetProperty("roles", out var rolesProp) ? rolesProp.GetString() ?? string.Empty : string.Empty, EnumValues = element.TryGetProperty("enumValues", out var enumProp) && enumProp.ValueKind == JsonValueKind.Array ? [.. enumProp.EnumerateArray().Select(e => e.GetString() ?? string.Empty)] : null }; variables.Add(variable); } return variables; } /// /// Parse JSON stream thành list of ConfigVariable (backward compatibility cho import từ format cũ) /// Format cũ: [{"name": "port", "type": "int", "value": 8080, "Min": 0, "Max": 65535, "Roles": ""}, ...] /// public static List ParseVariables(Stream jsonStream) { using var reader = new StreamReader(jsonStream); var json = reader.ReadToEnd(); return ParseVariables(json); } /// /// Parse JSON string thành list of ConfigVariable (backward compatibility cho import từ format cũ) /// public static List ParseVariables(string json) { using var jsonDoc = JsonDocument.Parse(json); var root = jsonDoc.RootElement; // Check if it's new format (object) or old format (array) if (root.ValueKind == JsonValueKind.Object) { // New format - extract variables if (root.TryGetProperty("variables", out var variablesProp) && variablesProp.ValueKind == JsonValueKind.Array) { return ParseVariablesArray(variablesProp); } throw new ArgumentException("JSON object must contain 'variables' array"); } if (root.ValueKind != JsonValueKind.Array) { throw new ArgumentException("JSON must be an array of variables (old format) or object with metadata and variables (new format)"); } // Old format - array of variables return ParseVariablesArray(root); } /// /// Parse value từ JSON element theo type /// private static object? ParseValue(JsonElement element) { if (!element.TryGetProperty("value", out var valueProp)) { throw new ArgumentException("Variable value is required"); } var typeStr = element.GetProperty("type").GetString()?.ToLower(); return typeStr switch { "string" => valueProp.GetString(), "int" => valueProp.GetInt32(), "double" => valueProp.GetDouble(), "bool" => valueProp.GetBoolean(), "object" => ParseJsonObject(valueProp), "array" => ParseJsonArray(valueProp), "enum" => valueProp.GetString(), // Enum values are strings _ => throw new ArgumentException($"Unsupported type: {typeStr}") }; } /// /// Parse JSON object thành Dictionary /// private static Dictionary ParseJsonObject(JsonElement element) { if (element.ValueKind != JsonValueKind.Object) { throw new ArgumentException("Value must be a JSON object"); } return JsonSerializer.Deserialize>( element.GetRawText()) ?? []; } /// /// Parse JSON array thành List /// private static List ParseJsonArray(JsonElement element) { if (element.ValueKind != JsonValueKind.Array) { throw new ArgumentException("Value must be a JSON array"); } var result = new List(); foreach (var item in element.EnumerateArray()) { result.Add(item.GetRawText()); } return result; } /// /// Serialize ConfigFile thành JSON string (format mới với metadata và variables) /// public static string SerializeConfigFile(ConfigFile config) { var jsonObject = new { id = config.Id, configType = config.ConfigType, createdAt = config.CreatedAt.ToString("O"), // ISO 8601 format updatedAt = config.UpdatedAt.ToString("O"), // ISO 8601 format description = config.Description, variables = config.Variables.Select(v => new { name = v.Name, type = v.Type.ToString().ToLower(), value = SerializeValue(v.Value, v.Type), min = v.Min, max = v.Max, roles = v.Roles ?? string.Empty, enumValues = v.EnumValues }).ToArray() }; return JsonSerializer.Serialize(jsonObject, JsonOptions); } /// /// Serialize list of ConfigVariable thành JSON string (chỉ variables, dùng cho export backward compatibility) /// public static string SerializeVariables(List variables) { var jsonArray = variables.Select(v => new { name = v.Name, type = v.Type.ToString().ToLower(), value = SerializeValue(v.Value, v.Type), min = v.Min, max = v.Max, roles = v.Roles ?? string.Empty, enumValues = v.EnumValues }).ToArray(); return JsonSerializer.Serialize(jsonArray, JsonOptions); } /// /// Serialize value theo type (đặc biệt cho Object và Array) /// private static object SerializeValue(object? value, ConfigVariableType type) { if (value == null) return null!; return type switch { ConfigVariableType.Object => value is Dictionary dict ? dict : System.Text.Json.JsonSerializer.Deserialize>(value.ToString() ?? "{}", JsonOptions) ?? [], ConfigVariableType.Array => value is List list ? list : System.Text.Json.JsonSerializer.Deserialize>(value.ToString() ?? "[]", JsonOptions) ?? [], _ => value }; } }