XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension

【DLL】XFE各类拓展是一个C#的DLL库,旨在优化C#代码中常用语句的使用,并提供更简洁的访问方式,同时提供Xunit测试框架,快速搭建服务器/客户端,免费ChatGPTAPI接口,免费通讯服务器,XFE下载器,新增格式等

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers;
using System.Collections;
using System.Globalization;
using System.Reflection;

namespace XFEExtension.NetCore.XFETransform.Json;

internal static class JsonCodec
{
    public static string Serialize(object? value, Type declaredType, JsonSettings settings)
    {
        using var writer = new PooledJsonWriter(settings);
        var activeReferences = new HashSet<object>(ReferenceEqualityComparer.Instance);
        WriteValue(writer, value, declaredType, settings, activeReferences, "$", 0);
        return writer.ToString();
    }

    public static object? Deserialize(XFEJsonNode node, Type targetType, JsonSettings settings)
    {
        ArgumentNullException.ThrowIfNull(targetType);
        try
        {
            return ReadValue(node, targetType, settings, 0);
        }
        catch (XFEJsonException)
        {
            throw;
        }
        catch (Exception exception) when (exception is FormatException or OverflowException or InvalidCastException or TargetInvocationException or MemberAccessException)
        {
            throw node.Error($"无法将 JSON 值反序列化为 {targetType.FullName}。", innerException: exception);
        }
    }

    private static void WriteValue(
        PooledJsonWriter writer,
        object? value,
        Type declaredType,
        JsonSettings settings,
        HashSet<object> activeReferences,
        string path,
        int depth)
    {
        if (value is null)
        {
            writer.Append("null");
            return;
        }

        var type = value.GetType();
        var nullableType = Nullable.GetUnderlyingType(type);
        if (nullableType is not null)
            type = nullableType;

        if (WriteScalar(writer, value, type, settings, path))
            return;

        if (depth >= settings.MaxDepth)
            throw JsonSource.ConversionError($"序列化深度超过限制 {settings.MaxDepth}。", path);

        var trackReference = !type.IsValueType;
        if (trackReference && !activeReferences.Add(value))
            throw JsonSource.ConversionError("检测到对象引用循环。", path);

        try
        {
            if (value is IDictionary dictionary)
            {
                WriteDictionary(writer, dictionary, settings, activeReferences, path, depth);
                return;
            }

            if (TryGetStringDictionaryInterface(type, out _) && value is IEnumerable genericDictionary)
            {
                WriteGenericDictionary(writer, genericDictionary, settings, activeReferences, path, depth);
                return;
            }

            if (value is IEnumerable enumerable)
            {
                WriteArray(writer, enumerable, settings, activeReferences, path, depth);
                return;
            }

            WriteObject(writer, value, type, settings, activeReferences, path, depth);
        }
        finally
        {
            if (trackReference)
                activeReferences.Remove(value);
        }
    }

    private static bool WriteScalar(PooledJsonWriter writer, object value, Type type, JsonSettings settings, string path)
    {
        if (type == typeof(string))
        {
            writer.WriteString((string)value, path);
            return true;
        }
        if (type == typeof(char))
        {
            writer.WriteString(value.ToString()!, path);
            return true;
        }
        if (type == typeof(bool))
        {
            writer.Append((bool)value ? "true" : "false");
            return true;
        }
        if (type.IsEnum)
        {
            if (settings.EnumFormat == XFEJsonEnumFormat.String)
                writer.WriteString(value.ToString()!, path);
            else
            {
                var underlyingValue = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture);
                writer.Append(((IFormattable)underlyingValue).ToString(null, CultureInfo.InvariantCulture)!);
            }
            return true;
        }
        if (type == typeof(DateTime))
        {
            writer.WriteString(((DateTime)value).ToString("O", CultureInfo.InvariantCulture), path);
            return true;
        }
        if (type == typeof(DateTimeOffset))
        {
            writer.WriteString(((DateTimeOffset)value).ToString("O", CultureInfo.InvariantCulture), path);
            return true;
        }
        if (type == typeof(TimeSpan))
        {
            writer.WriteString(((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture), path);
            return true;
        }
        if (type == typeof(Guid))
        {
            writer.WriteString(((Guid)value).ToString("D", CultureInfo.InvariantCulture), path);
            return true;
        }
        if (type == typeof(IntPtr))
        {
            writer.Append(((nint)value).ToString(CultureInfo.InvariantCulture));
            return true;
        }
        if (type == typeof(UIntPtr))
        {
            writer.Append(((nuint)value).ToString(CultureInfo.InvariantCulture));
            return true;
        }

        switch (Type.GetTypeCode(type))
        {
            case TypeCode.SByte:
            case TypeCode.Byte:
            case TypeCode.Int16:
            case TypeCode.UInt16:
            case TypeCode.Int32:
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
            case TypeCode.Decimal:
                writer.Append(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture)!);
                return true;
            case TypeCode.Single:
                {
                    var number = (float)value;
                    if (!float.IsFinite(number))
                        throw JsonSource.ConversionError("JSON 不支持 NaN 或无穷大浮点数。", path);
                    writer.Append(number.ToString("R", CultureInfo.InvariantCulture));
                    return true;
                }
            case TypeCode.Double:
                {
                    var number = (double)value;
                    if (!double.IsFinite(number))
                        throw JsonSource.ConversionError("JSON 不支持 NaN 或无穷大浮点数。", path);
                    writer.Append(number.ToString("R", CultureInfo.InvariantCulture));
                    return true;
                }
            default:
                return false;
        }
    }

    private static void WriteDictionary(
        PooledJsonWriter writer,
        IDictionary dictionary,
        JsonSettings settings,
        HashSet<object> activeReferences,
        string path,
        int depth)
    {
        writer.Append('{');
        var index = 0;
        foreach (DictionaryEntry entry in dictionary)
        {
            if (entry.Key is not string key)
                throw JsonSource.ConversionError("JSON 对象字典只支持 string 键。", path);
            writer.WriteItemPrefix(index++, depth + 1);
            writer.WriteString(key, path);
            writer.WriteNameSeparator();
            WriteValue(writer, entry.Value, entry.Value?.GetType() ?? typeof(object), settings, activeReferences,
                JsonScanner.AppendPropertyPath(path, key), depth + 1);
        }
        writer.WriteContainerEnd('}', index, depth);
    }

    private static void WriteGenericDictionary(
        PooledJsonWriter writer,
        IEnumerable dictionary,
        JsonSettings settings,
        HashSet<object> activeReferences,
        string path,
        int depth)
    {
        writer.Append('{');
        var index = 0;
        foreach (var entry in dictionary)
        {
            if (entry is null)
                continue;
            var entryType = entry.GetType();
            var key = entryType.GetProperty("Key")?.GetValue(entry) as string
                ?? throw JsonSource.ConversionError("JSON 对象字典只支持 string 键。", path);
            var entryValue = entryType.GetProperty("Value")?.GetValue(entry);
            writer.WriteItemPrefix(index++, depth + 1);
            writer.WriteString(key, path);
            writer.WriteNameSeparator();
            WriteValue(writer, entryValue, entryValue?.GetType() ?? typeof(object), settings, activeReferences,
                JsonScanner.AppendPropertyPath(path, key), depth + 1);
        }
        writer.WriteContainerEnd('}', index, depth);
    }

    private static void WriteArray(
        PooledJsonWriter writer,
        IEnumerable values,
        JsonSettings settings,
        HashSet<object> activeReferences,
        string path,
        int depth)
    {
        writer.Append('[');
        var index = 0;
        foreach (var item in values)
        {
            writer.WriteItemPrefix(index, depth + 1);
            WriteValue(writer, item, item?.GetType() ?? typeof(object), settings, activeReferences, $"{path}[{index}]", depth + 1);
            index++;
        }
        writer.WriteContainerEnd(']', index, depth);
    }

    private static void WriteObject(
        PooledJsonWriter writer,
        object value,
        Type type,
        JsonSettings settings,
        HashSet<object> activeReferences,
        string path,
        int depth)
    {
        var contract = JsonContractCache.Get(type, settings);
        writer.Append('{');
        var index = 0;
        foreach (var member in contract.Members)
        {
            object? memberValue;
            try
            {
                memberValue = member.Getter(value);
            }
            catch (Exception exception)
            {
                throw JsonSource.ConversionError($"读取成员 {member.Name} 失败。", JsonScanner.AppendPropertyPath(path, member.Name), exception);
            }
            writer.WriteItemPrefix(index++, depth + 1);
            writer.WriteString(member.Name, path);
            writer.WriteNameSeparator();
            WriteValue(writer, memberValue, member.MemberType, settings, activeReferences,
                JsonScanner.AppendPropertyPath(path, member.Name), depth + 1);
        }
        writer.WriteContainerEnd('}', index, depth);
    }

    private static object? ReadValue(XFEJsonNode node, Type targetType, JsonSettings settings, int depth)
    {
        if (depth > settings.MaxDepth)
            throw node.Error($"反序列化深度超过限制 {settings.MaxDepth}。");

        var nullableType = Nullable.GetUnderlyingType(targetType);
        if (node.Kind == XFEJsonValueKind.Null)
        {
            node.Validate();
            if (!targetType.IsValueType || nullableType is not null)
                return null;
            throw node.Error($"不能将 JSON null 赋给非可空类型 {targetType.FullName}。");
        }

        if (nullableType is not null)
            return ReadValue(node, nullableType, settings, depth);
        if (targetType == typeof(XFEJsonNode))
            return node;
        if (targetType == typeof(object))
            return ReadUntyped(node, settings, depth);
        if (targetType == typeof(string))
            return node.GetString();
        if (targetType == typeof(char))
        {
            var value = node.GetString();
            if (value is { Length: 1 })
                return value[0];
            throw node.Error("char 目标要求 JSON 字符串恰好包含一个 UTF-16 字符。");
        }
        if (targetType == typeof(bool))
            return node.GetBoolean();
        if (targetType.IsEnum)
            return ReadEnum(node, targetType, settings);
        if (IsNumericType(targetType))
            return ReadNumber(node, targetType);
        if (targetType == typeof(DateTime))
            return ParseDateTime(node);
        if (targetType == typeof(DateTimeOffset))
            return ParseDateTimeOffset(node);
        if (targetType == typeof(TimeSpan))
            return ParseTimeSpan(node);
        if (targetType == typeof(Guid))
            return ParseGuid(node);
        if (targetType.IsArray)
            return ReadArray(node, targetType.GetElementType()!, settings, depth + 1);
        if (TryGetStringDictionaryInterface(targetType, out var dictionaryValueType))
            return ReadDictionary(node, targetType, dictionaryValueType, settings, depth + 1);
        if (TryGetListElementType(targetType, out var elementType))
            return ReadList(node, targetType, elementType, settings, depth + 1);

        return ReadObject(node, targetType, settings, depth + 1);
    }

    private static object? ReadUntyped(XFEJsonNode node, JsonSettings settings, int depth)
    {
        return node.Kind switch
        {
            XFEJsonValueKind.Null => null,
            XFEJsonValueKind.String => node.GetString(),
            XFEJsonValueKind.True or XFEJsonValueKind.False => node.GetBoolean(),
            XFEJsonValueKind.Number => ReadUntypedNumber(node),
            XFEJsonValueKind.Array => node.EnumerateArray().Select(item => ReadUntyped(item, settings, depth + 1)).ToList(),
            XFEJsonValueKind.Object => ReadUntypedObject(node, settings, depth + 1),
            _ => throw node.Error("无法识别 JSON 值类型。")
        };
    }

    private static object ReadUntypedNumber(XFEJsonNode node)
    {
        var span = RawSpan(node);
        if (long.TryParse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var integer))
            return integer;
        if (decimal.TryParse(span, NumberStyles.Float, CultureInfo.InvariantCulture, out var decimalValue))
            return decimalValue;
        if (double.TryParse(span, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue) && double.IsFinite(doubleValue))
            return doubleValue;
        throw node.Error("JSON 数字超出受支持的动态数值范围。");
    }

    private static Dictionary<string, object?> ReadUntypedObject(XFEJsonNode node, JsonSettings settings, int depth)
    {
        var result = new Dictionary<string, object?>(StringComparer.Ordinal);
        foreach (var property in node.EnumerateObject())
            result.TryAdd(property.Name, ReadUntyped(property.Value, settings, depth));
        return result;
    }

    private static object ReadEnum(XFEJsonNode node, Type enumType, JsonSettings settings)
    {
        if (node.Kind == XFEJsonValueKind.String)
        {
            var name = node.GetString()!;
            if (Enum.TryParse(enumType, name, settings.PropertyNameCaseInsensitive, out var result))
                return result!;
            throw node.Error($"“{name}”不是 {enumType.FullName} 的有效枚举名称。");
        }
        var underlying = Enum.GetUnderlyingType(enumType);
        var number = ReadNumber(node, underlying);
        return Enum.ToObject(enumType, number);
    }

    private static object ReadNumber(XFEJsonNode node, Type type)
    {
        if (node.Kind != XFEJsonValueKind.Number)
            throw node.Error($"当前节点不是数字,无法转换为 {type.FullName}。");
        var span = RawSpan(node);
        try
        {
            if (type == typeof(sbyte)) return sbyte.Parse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
            if (type == typeof(byte)) return byte.Parse(span, NumberStyles.None, CultureInfo.InvariantCulture);
            if (type == typeof(short)) return short.Parse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
            if (type == typeof(ushort)) return ushort.Parse(span, NumberStyles.None, CultureInfo.InvariantCulture);
            if (type == typeof(int)) return int.Parse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
            if (type == typeof(uint)) return uint.Parse(span, NumberStyles.None, CultureInfo.InvariantCulture);
            if (type == typeof(long)) return long.Parse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
            if (type == typeof(ulong)) return ulong.Parse(span, NumberStyles.None, CultureInfo.InvariantCulture);
            if (type == typeof(nint)) return (nint)long.Parse(span, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
            if (type == typeof(nuint)) return (nuint)ulong.Parse(span, NumberStyles.None, CultureInfo.InvariantCulture);
            if (type == typeof(decimal)) return decimal.Parse(span, NumberStyles.Float, CultureInfo.InvariantCulture);
            if (type == typeof(float))
            {
                var value = float.Parse(span, NumberStyles.Float, CultureInfo.InvariantCulture);
                if (!float.IsFinite(value)) throw new OverflowException();
                return value;
            }
            if (type == typeof(double))
            {
                var value = double.Parse(span, NumberStyles.Float, CultureInfo.InvariantCulture);
                if (!double.IsFinite(value)) throw new OverflowException();
                return value;
            }
        }
        catch (Exception exception) when (exception is FormatException or OverflowException)
        {
            throw node.Error($"JSON 数字无法转换为 {type.FullName}。", innerException: exception);
        }
        throw node.Error($"不支持的数值类型 {type.FullName}。");
    }

    private static DateTime ParseDateTime(XFEJsonNode node)
    {
        var text = node.GetString();
        if (DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var value))
            return value;
        throw node.Error("JSON 字符串不是有效的 DateTime 往返格式。");
    }

    private static DateTimeOffset ParseDateTimeOffset(XFEJsonNode node)
    {
        var text = node.GetString();
        if (DateTimeOffset.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var value))
            return value;
        throw node.Error("JSON 字符串不是有效的 DateTimeOffset 往返格式。");
    }

    private static TimeSpan ParseTimeSpan(XFEJsonNode node)
    {
        var text = node.GetString();
        if (TimeSpan.TryParseExact(text, "c", CultureInfo.InvariantCulture, out var value))
            return value;
        throw node.Error("JSON 字符串不是有效的 TimeSpan 常量格式。");
    }

    private static Guid ParseGuid(XFEJsonNode node)
    {
        var text = node.GetString();
        if (Guid.TryParseExact(text, "D", out var value))
            return value;
        throw node.Error("JSON 字符串不是有效的 Guid D 格式。");
    }

    private static Array ReadArray(XFEJsonNode node, Type elementType, JsonSettings settings, int depth)
    {
        if (node.Kind != XFEJsonValueKind.Array)
            throw node.Error("目标数组要求 JSON 数组。");
        var elements = node.EnumerateArray().ToArray();
        var result = Array.CreateInstance(elementType, elements.Length);
        for (var index = 0; index < elements.Length; index++)
            result.SetValue(ReadValue(elements[index], elementType, settings, depth), index);
        return result;
    }

    private static object ReadList(XFEJsonNode node, Type targetType, Type elementType, JsonSettings settings, int depth)
    {
        if (node.Kind != XFEJsonValueKind.Array)
            throw node.Error($"类型 {targetType.FullName} 要求 JSON 数组。");
        var concreteType = targetType.IsInterface || targetType.IsAbstract
            ? typeof(List<>).MakeGenericType(elementType)
            : targetType;
        if (Activator.CreateInstance(concreteType) is not IList result)
            throw node.Error($"集合类型 {targetType.FullName} 必须具有公开无参构造并实现 IList。");
        foreach (var element in node.EnumerateArray())
            result.Add(ReadValue(element, elementType, settings, depth));
        return result;
    }

    private static object ReadDictionary(XFEJsonNode node, Type targetType, Type valueType, JsonSettings settings, int depth)
    {
        if (node.Kind != XFEJsonValueKind.Object)
            throw node.Error($"类型 {targetType.FullName} 要求 JSON 对象。");
        var concreteType = targetType.IsInterface || targetType.IsAbstract
            ? typeof(Dictionary<,>).MakeGenericType(typeof(string), valueType)
            : targetType;
        if (Activator.CreateInstance(concreteType) is not IDictionary result)
            throw node.Error($"字典类型 {targetType.FullName} 必须具有公开无参构造并实现 IDictionary。");
        var seen = new HashSet<string>(StringComparer.Ordinal);
        foreach (var property in node.EnumerateObject())
        {
            if (seen.Add(property.Name))
                result.Add(property.Name, ReadValue(property.Value, valueType, settings, depth));
        }
        return result;
    }

    private static object ReadObject(XFEJsonNode node, Type targetType, JsonSettings settings, int depth)
    {
        if (node.Kind != XFEJsonValueKind.Object)
            throw node.Error($"类型 {targetType.FullName} 要求 JSON 对象。");
        var contract = JsonContractCache.Get(targetType, settings);
        if (contract.Factory is null)
            throw node.Error($"类型 {targetType.FullName} 必须是非抽象类型并具有公开无参构造。");
        var instance = contract.Factory();
        var seen = new HashSet<string>(settings.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
        foreach (var property in node.EnumerateObject())
        {
            if (!seen.Add(property.Name))
                continue;
            if (!contract.TryGetMember(property.Name, settings.PropertyNameCaseInsensitive, out var member) || member.Setter is null)
            {
                if (settings.UnmappedMemberHandling == XFEJsonUnmappedMemberHandling.Error)
                    throw property.Value.Error($"JSON 属性“{property.Name}”无法映射到 {targetType.FullName} 的可写成员。");
                continue;
            }

            var memberValue = ReadValue(property.Value, member.MemberType, settings, depth);
            try
            {
                member.Setter(instance, memberValue);
            }
            catch (Exception exception)
            {
                throw property.Value.Error($"设置成员 {targetType.FullName}.{member.Name} 失败。", innerException: exception);
            }
        }
        return instance;
    }

    private static ReadOnlySpan<char> RawSpan(XFEJsonNode node) =>
        node.Source.Text.AsSpan(node.Start, node.End - node.Start);

    private static bool IsNumericType(Type type) => type == typeof(sbyte) || type == typeof(byte) ||
        type == typeof(short) || type == typeof(ushort) || type == typeof(int) || type == typeof(uint) ||
        type == typeof(long) || type == typeof(ulong) || type == typeof(nint) || type == typeof(nuint) ||
        type == typeof(float) || type == typeof(double) || type == typeof(decimal);

    private static bool TryGetStringDictionaryInterface(Type type, out Type valueType)
    {
        foreach (var candidate in EnumerateTypeAndInterfaces(type))
        {
            if (!candidate.IsGenericType)
                continue;
            var definition = candidate.GetGenericTypeDefinition();
            if (definition != typeof(IDictionary<,>) && definition != typeof(IReadOnlyDictionary<,>) &&
                definition != typeof(Dictionary<,>))
                continue;
            var arguments = candidate.GetGenericArguments();
            if (arguments[0] != typeof(string))
                throw JsonSource.ConversionError($"JSON 对象字典不支持键类型 {arguments[0].FullName}。", "$");
            valueType = arguments[1];
            return true;
        }
        valueType = null!;
        return false;
    }

    private static bool TryGetListElementType(Type type, out Type elementType)
    {
        if (type == typeof(IList) || type == typeof(ArrayList))
        {
            elementType = typeof(object);
            return true;
        }
        foreach (var candidate in EnumerateTypeAndInterfaces(type))
        {
            if (!candidate.IsGenericType)
                continue;
            var definition = candidate.GetGenericTypeDefinition();
            if (definition == typeof(List<>) || definition == typeof(IList<>) ||
                definition == typeof(ICollection<>) || definition == typeof(IEnumerable<>) ||
                definition == typeof(IReadOnlyList<>) || definition == typeof(IReadOnlyCollection<>))
            {
                elementType = candidate.GetGenericArguments()[0];
                return true;
            }
        }
        elementType = null!;
        return false;
    }

    private static IEnumerable<Type> EnumerateTypeAndInterfaces(Type type)
    {
        yield return type;
        foreach (var interfaceType in type.GetInterfaces())
            yield return interfaceType;
    }
}

internal sealed class PooledJsonWriter(JsonSettings settings) : IDisposable
{
    private char[] _buffer = ArrayPool<char>.Shared.Rent(256);
    private int _length;

    public void Append(char value)
    {
        EnsureCapacity(1);
        _buffer[_length++] = value;
    }

    public void Append(string value)
    {
        EnsureCapacity(value.Length);
        value.AsSpan().CopyTo(_buffer.AsSpan(_length));
        _length += value.Length;
    }

    public void WriteString(string value, string path)
    {
        Append('"');
        for (var index = 0; index < value.Length; index++)
        {
            var current = value[index];
            switch (current)
            {
                case '"': Append("\\\""); break;
                case '\\': Append("\\\\"); break;
                case '\b': Append("\\b"); break;
                case '\f': Append("\\f"); break;
                case '\n': Append("\\n"); break;
                case '\r': Append("\\r"); break;
                case '\t': Append("\\t"); break;
                case < ' ':
                    Append("\\u");
                    Append(((int)current).ToString("X4", CultureInfo.InvariantCulture));
                    break;
                default:
                    if (char.IsHighSurrogate(current))
                    {
                        if (index + 1 >= value.Length || !char.IsLowSurrogate(value[index + 1]))
                            throw JsonSource.ConversionError("字符串包含孤立的高代理项。", path);
                        Append(current);
                        Append(value[++index]);
                    }
                    else if (char.IsLowSurrogate(current))
                    {
                        throw JsonSource.ConversionError("字符串包含孤立的低代理项。", path);
                    }
                    else
                    {
                        Append(current);
                    }
                    break;
            }
        }
        Append('"');
    }

    public void WriteItemPrefix(int index, int depth)
    {
        if (index > 0)
            Append(',');
        if (settings.WriteIndented)
            WriteNewLineAndIndent(depth);
    }

    public void WriteNameSeparator()
    {
        Append(':');
        if (settings.WriteIndented)
            Append(' ');
    }

    public void WriteContainerEnd(char close, int itemCount, int depth)
    {
        if (settings.WriteIndented && itemCount > 0)
            WriteNewLineAndIndent(depth);
        Append(close);
    }

    public override string ToString() => new(_buffer, 0, _length);

    public void Dispose()
    {
        var buffer = Interlocked.Exchange(ref _buffer, []);
        if (buffer.Length > 0)
            ArrayPool<char>.Shared.Return(buffer);
        _length = 0;
    }

    private void WriteNewLineAndIndent(int depth)
    {
        Append(Environment.NewLine);
        EnsureCapacity(depth * 2);
        for (var index = 0; index < depth * 2; index++)
            _buffer[_length++] = ' ';
    }

    private void EnsureCapacity(int additionalLength)
    {
        if (_length + additionalLength <= _buffer.Length)
            return;
        var newSize = Math.Max(_length + additionalLength, _buffer.Length * 2);
        var replacement = ArrayPool<char>.Shared.Rent(newSize);
        _buffer.AsSpan(0, _length).CopyTo(replacement);
        ArrayPool<char>.Shared.Return(_buffer);
        _buffer = replacement;
    }
}