File: src\libraries\System.Text.Json\Common\JsonHelpers.cs
Web Access
Project: src\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
 
namespace System.Text.Json
{
    internal static partial class JsonHelpers
    {
#if !NET
        /// <summary>
        /// netstandard/netfx polyfill for Dictionary.TryAdd
        /// </summary>
        public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value) where TKey : notnull
        {
            if (!dictionary.ContainsKey(key))
            {
                dictionary[key] = value;
                return true;
            }
 
            return false;
        }
 
        /// <summary>
        /// netstandard/netfx polyfill for Queue.TryDequeue
        /// </summary>
        public static bool TryDequeue<T>(this Queue<T> queue, [NotNullWhen(true)] out T? result)
        {
            if (queue.Count > 0)
            {
                result = queue.Dequeue();
                return true;
            }
 
            result = default;
            return false;
        }
#endif
 
        internal static bool RequiresSpecialNumberHandlingOnWrite(JsonNumberHandling? handling)
        {
            return handling != null
                ? (handling.Value & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) != 0
                : false;
        }
 
        /// <summary>
        /// Provides an in-place, stable sorting implementation for List.
        /// </summary>
        internal static void StableSortByKey<T, TKey>(this List<T> items, Func<T, TKey> keySelector)
            where TKey : unmanaged, IComparable<TKey>
        {
#if NET
            Span<T> span = CollectionsMarshal.AsSpan(items);
 
            // Tuples implement lexical ordering OOTB which can be used to encode stable sorting
            // using the actual key as the first element and index as the second element.
            const int StackallocThreshold = 32;
            Span<(TKey, int)> keys = span.Length <= StackallocThreshold
                ? (stackalloc (TKey, int)[StackallocThreshold]).Slice(0, span.Length)
                : new (TKey, int)[span.Length];
 
            for (int i = 0; i < keys.Length; i++)
            {
                keys[i] = (keySelector(span[i]), i);
            }
 
            MemoryExtensions.Sort(keys, span);
#else
            T[] arrayCopy = items.ToArray();
            (TKey, int)[] keys = new (TKey, int)[arrayCopy.Length];
            for (int i = 0; i < keys.Length; i++)
            {
                keys[i] = (keySelector(arrayCopy[i]), i);
            }
 
            Array.Sort(keys, arrayCopy);
            items.Clear();
            items.AddRange(arrayCopy);
#endif
        }
 
        /// <summary>
        /// Traverses a DAG and returns its nodes applying topological sorting to the result.
        /// </summary>
        public static T[] TraverseGraphWithTopologicalSort<T>(T entryNode, Func<T, ICollection<T>> getChildren, IEqualityComparer<T>? comparer = null)
            where T : notnull
        {
            comparer ??= EqualityComparer<T>.Default;
 
            // Implements Kahn's algorithm.
            // Step 1. Traverse and build the graph, labeling each node with an integer.
 
            var nodes = new List<T> { entryNode }; // the integer-to-node mapping
            var nodeIndex = new Dictionary<T, int>(comparer) { [entryNode] = 0 }; // the node-to-integer mapping
            var adjacency = new List<bool[]?>(); // the growable adjacency matrix
            var childlessQueue = new Queue<int>(); // the queue of nodes without children or whose children have been visited
 
            for (int i = 0; i < nodes.Count; i++)
            {
                T next = nodes[i];
                ICollection<T> children = getChildren(next);
                int count = children.Count;
 
                if (count == 0)
                {
                    adjacency.Add(null); // can use null in this row of the adjacency matrix.
                    childlessQueue.Enqueue(i);
                    continue;
                }
 
                var adjacencyRow = new bool[Math.Max(nodes.Count, count)];
                foreach (T childNode in children)
                {
                    if (!nodeIndex.TryGetValue(childNode, out int index))
                    {
                        // this is the first time we're encountering this node.
                        // Assign it an index and append it to the maps.
 
                        index = nodes.Count;
                        nodeIndex.Add(childNode, index);
                        nodes.Add(childNode);
                    }
 
                    // Grow the adjacency row as appropriate.
                    if (index >= adjacencyRow.Length)
                    {
                        Array.Resize(ref adjacencyRow, index + 1);
                    }
 
                    // Set the relevant bit in the adjacency row.
                    adjacencyRow[index] = true;
                }
 
                // Append the row to the adjacency matrix.
                adjacency.Add(adjacencyRow);
            }
 
            Debug.Assert(childlessQueue.Count > 0, "The graph contains cycles.");
 
            // Step 2. Build the sorted array, walking from the nodes without children upward.
            var sortedNodes = new T[nodes.Count];
            int idx = sortedNodes.Length;
 
            do
            {
                int nextIndex = childlessQueue.Dequeue();
                sortedNodes[--idx] = nodes[nextIndex];
 
                // Iterate over the adjacency matrix, removing any occurrence of nextIndex.
                for (int i = 0; i < adjacency.Count; i++)
                {
                    if (adjacency[i] is { } childMap && nextIndex < childMap.Length && childMap[nextIndex])
                    {
                        childMap[nextIndex] = false;
 
                        if (childMap.AsSpan().IndexOf(true) == -1)
                        {
                            // nextIndex was the last child removed from i, add to queue.
                            childlessQueue.Enqueue(i);
                        }
                    }
                }
 
            } while (childlessQueue.Count > 0);
 
            Debug.Assert(idx == 0, "should have populated the entire sortedNodes array.");
            return sortedNodes;
        }
    }
}