summaryrefslogtreecommitdiff
path: root/src/Task/JsonHelper.cs
blob: 79e9d279147665dc88343cb803968443279b21b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
    {
        /// <summary>
        /// Converts a dictionary of string and JsonElement pairs to a JsonElement.
        /// </summary>
        /// <param name="dictionary">A dictionary containing string keys and JsonElement values.</param>
        /// <returns>A JsonElement representing the dictionary.</returns>
        public static Task<JsonElement> ConvertToJsonElement(Dictionary<string, JsonElement> dictionary)
        {
            return ConvertObjectToJsonElement(dictionary);
        }

        /// <summary>
        /// Converts a list of JsonElements to a JsonElement.
        /// </summary>
        /// <param name="list">A list containing JsonElement objects.</param>
        /// <returns>A JsonElement representing the list.</returns>
        public static Task<JsonElement> ConvertToJsonElement(List<JsonElement> list)
        {
            return ConvertObjectToJsonElement(list);
        }

        /// <summary>
        /// Converts dictionary keys to strings recursively for any dictionary or list.
        /// </summary>
        /// <param name="data">The object to process, can be a dictionary or list.</param>
        /// <returns>An object where dictionary keys are converted to strings.</returns>
        public static object ConvertKeysToString(object data)
        {
            if (data is IDictionary<object, object> dict)
            {
                var convertedDict = new Dictionary<string, object>(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<object> list)
            {
                var convertedList = new List<object>();

                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
        }

        /// <summary>
        /// Parses an array of key-value pairs from an <see cref="ITaskItem"/> 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 '\='.
        /// </summary>
        /// <param name="properties">An array of key-value pairs in the form of ITaskItem[] or legacy format strings.</param>
        /// <returns>A dictionary containing the parsed key-value pairs.</returns>
        public static Dictionary<string, string> ParseAdditionalProperties(ITaskItem[] properties)
        {
            var additionalPropertiesDict = new Dictionary<string, string>(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;
        }

        /// <summary>
        /// Converts any object to a JsonElement.
        /// </summary>
        /// <param name="value">The object to convert to JsonElement.</param>
        /// <returns>A JsonElement representing the object.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
        public static async Task<JsonElement> 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();
                }
            }
        }

        /// <summary>
        /// Converts a JsonElement to a dictionary of string and JsonElement pairs.
        /// </summary>
        /// <param name="jsonElement">The JsonElement to convert.</param>
        /// <returns>A dictionary representing the JsonElement.</returns>
        public static Dictionary<string, JsonElement> JsonElementToDictionary(JsonElement jsonElement)
        {
            var dictionary = new Dictionary<string, JsonElement>(StringComparer.Ordinal);

            foreach (var property in jsonElement.EnumerateObject())
            {
                dictionary.Add(property.Name, property.Value);
            }

            return dictionary;
        }

        /// <summary>
        /// Converts a JsonElement to an appropriate object representation, such as a dictionary, list, or primitive.
        /// </summary>
        /// <param name="element">The JsonElement to convert.</param>
        /// <returns>An object representing the JsonElement.</returns>
        /// <exception cref="InvalidOperationException">Thrown when we fail to find an .NET type for the <see cref="JsonElement"/>.</exception>
        public static object ConvertJsonElementToObject(JsonElement element)
        {
            switch (element.ValueKind)
            {
                case JsonValueKind.Object:
                    return JsonElementToDictionary(element);
                case JsonValueKind.Array:
                    var list = new List<object>();
                    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}");
            }
        }
    }
}