using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Build.Framework; namespace AggregateConfigBuildTask { internal static class JsonHelper { /// /// Converts a dictionary of string and JsonElement pairs to a JsonElement. /// /// A dictionary containing string keys and JsonElement values. /// A JsonElement representing the dictionary. public static Task ConvertToJsonElement(Dictionary dictionary) { return ConvertObjectToJsonElement(dictionary); } /// /// Converts a list of JsonElements to a JsonElement. /// /// A list containing JsonElement objects. /// A JsonElement representing the list. public static Task ConvertToJsonElement(List list) { return ConvertObjectToJsonElement(list); } /// /// Converts dictionary keys to strings recursively for any dictionary or list. /// /// The object to process, can be a dictionary or list. /// An object where dictionary keys are converted to strings. public static object ConvertKeysToString(object data) { if (data is IDictionary dict) { var convertedDict = new Dictionary(StringComparer.Ordinal); foreach (var key in dict.Keys) { var stringKey = key.ToString(); // Ensure the key is a string convertedDict[stringKey] = ConvertKeysToString(dict[key]); // Recursively convert values } return convertedDict; } else if (data is IList list) { var convertedList = new List(); foreach (var item in list) { convertedList.Add(ConvertKeysToString(item)); // Recursively convert list items } return convertedList; } return data; // Return the item as-is if it's not a dictionary or list } /// /// Parses an array of key-value pairs from an array. /// Prioritizes the 'Value' metadata when provided. Falls back to legacy format "key=value" if no 'Value' is present. /// Supports escaping of the '=' sign using '\='. /// /// An array of key-value pairs in the form of ITaskItem[] or legacy format strings. /// A dictionary containing the parsed key-value pairs. public static Dictionary ParseAdditionalProperties(ITaskItem[] properties) { var additionalPropertiesDict = new Dictionary(StringComparer.Ordinal); const string unicodeEscape = "\u001F"; char[] split = new[] { '=' }; if (properties != null) { foreach (var property in properties) { // Check if the new 'Value' metadata is present string key = property.ItemSpec; string value = property.GetMetadata("Value"); if (!string.IsNullOrEmpty(value)) { // Use the new method if the 'Value' metadata is provided additionalPropertiesDict[key] = value; continue; } // Fallback to legacy parsing if no 'Value' metadata is provided and key contains '=' var sanitizedProperty = key.Replace(@"\=", unicodeEscape); var keyValue = sanitizedProperty.Split(split, 2); if (keyValue.Length == 2) { additionalPropertiesDict[keyValue[0].Replace(unicodeEscape, "=")] = keyValue[1].Replace(unicodeEscape, "="); } } } return additionalPropertiesDict; } /// /// Converts any object to a JsonElement. /// /// The object to convert to JsonElement. /// A JsonElement representing the object. /// is null. public static async Task ConvertObjectToJsonElement(object value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } using (var memoryStream = new MemoryStream()) { await JsonSerializer.SerializeAsync(memoryStream, value).ConfigureAwait(false); memoryStream.Seek(0, SeekOrigin.Begin); using (var jsonDocument = await JsonDocument.ParseAsync(memoryStream).ConfigureAwait(false)) { return jsonDocument.RootElement.Clone(); } } } /// /// Converts a JsonElement to a dictionary of string and JsonElement pairs. /// /// The JsonElement to convert. /// A dictionary representing the JsonElement. public static Dictionary JsonElementToDictionary(JsonElement jsonElement) { var dictionary = new Dictionary(StringComparer.Ordinal); foreach (var property in jsonElement.EnumerateObject()) { dictionary.Add(property.Name, property.Value); } return dictionary; } /// /// Converts a JsonElement to an appropriate object representation, such as a dictionary, list, or primitive. /// /// The JsonElement to convert. /// An object representing the JsonElement. /// Thrown when we fail to find an .NET type for the . public static object ConvertJsonElementToObject(JsonElement element) { switch (element.ValueKind) { case JsonValueKind.Object: return JsonElementToDictionary(element); case JsonValueKind.Array: var list = new List(); foreach (var item in element.EnumerateArray()) { list.Add(ConvertJsonElementToObject(item)); } return list; case JsonValueKind.String: return element.GetString(); case JsonValueKind.Number: return element.TryGetInt64(out long l) ? l : element.GetDouble(); case JsonValueKind.True: case JsonValueKind.False: return element.GetBoolean(); case JsonValueKind.Null: return null; default: throw new InvalidOperationException($"Unsupported JsonValueKind: {element.ValueKind}"); } } } }