using System.Buffers; using System.Runtime.CompilerServices; using AyCode.Core.Compression; using AyCode.Core.Extensions; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Jsons; namespace AyCode.Services.SignalRs; /// /// Centralized helper for SignalR serialization operations. /// Provides optimized primitives for JSON/Binary serialization with pooled buffers. /// public static class SignalRSerializationHelper { // Pre-boxed boolean values to avoid repeated boxing private static readonly string JsonTrue = "true"; private static readonly string JsonFalse = "false"; #region Primitive JSON Serialization /// /// Serialize a primitive value to JSON string with minimal overhead. /// Falls back to full JSON serialization for complex types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SerializePrimitiveToJson(object value) { return value switch { int i => i.ToString(), long l => l.ToString(), Guid g => SerializeGuidToJson(g), bool b => b ? JsonTrue : JsonFalse, // Strings need proper JSON escaping for special characters string => value.ToJson(), _ => value.ToJson() }; } /// /// Serialize a Guid to JSON string with pre-allocated buffer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SerializeGuidToJson(Guid g) { // Pre-allocate exact size: 38 chars = 2 quotes + 36 guid chars return string.Create(38, g, static (span, guid) => { span[0] = '"'; guid.TryFormat(span[1..], out _); span[37] = '"'; }); } #endregion #region Binary Serialization /// /// Serialize object to binary using pooled ArrayBufferWriter. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] SerializeToBinary(T value, AcBinarySerializerOptions? options = null) { var writer = new ArrayBufferWriter(256); value.ToBinary(writer, options ?? AcBinarySerializerOptions.Default); return writer.WrittenSpan.ToArray(); } /// /// Serialize object to binary and write to existing ArrayBufferWriter. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SerializeToBinary(T value, ArrayBufferWriter writer, AcBinarySerializerOptions? options = null) { value.ToBinary(writer, options ?? AcBinarySerializerOptions.Default); } /// /// Deserialize binary data to object. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? DeserializeFromBinary(byte[] data) { return data.BinaryTo(); } /// /// Deserialize binary data from ReadOnlySpan. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? DeserializeFromBinary(ReadOnlySpan data) { return data.BinaryTo(); } #endregion #region JSON Serialization with Brotli /// /// Serialize object to JSON and compress with Brotli. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] SerializeToCompressedJson(T value, AcJsonSerializerOptions? options = null) { var json = value.ToJson(options ?? AcJsonSerializerOptions.Default); return BrotliHelper.Compress(json); } /// /// Decompress Brotli data and deserialize JSON to object. /// Uses pooled buffer for decompression. /// public static T? DeserializeFromCompressedJson(byte[] compressedData) { var (buffer, length) = BrotliHelper.DecompressToRentedBuffer(compressedData.AsSpan()); try { return AcJsonDeserializer.Deserialize(new ReadOnlySpan(buffer, 0, length)); } finally { ArrayPool.Shared.Return(buffer); } } /// /// Decompress Brotli data to rented buffer for direct processing. /// Caller must return buffer to ArrayPool. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (byte[] Buffer, int Length) DecompressToRentedBuffer(byte[] compressedData) { return BrotliHelper.DecompressToRentedBuffer(compressedData.AsSpan()); } #endregion #region Response Data Helpers /// /// Check if string appears to be valid JSON (starts with { or [). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsValidJsonString(ReadOnlySpan text) { var trimmed = text.Trim(); return trimmed.Length > 1 && (trimmed[0] == '{' || trimmed[0] == '[') && (trimmed[^1] == '}' || trimmed[^1] == ']'); } /// /// Create response binary data based on serializer type. /// public static byte[]? CreateResponseData(object? responseData, AcSerializerOptions serializerOptions) { if (responseData == null) return null; if (serializerOptions.SerializerType == AcSerializerType.Binary) { if (responseData is byte[] byteData) return byteData; var binaryOptions = serializerOptions as AcBinarySerializerOptions ?? AcBinarySerializerOptions.Default; return SerializeToBinary(responseData, binaryOptions); } // JSON mode with Brotli compression string json; if (responseData is string strData && IsValidJsonString(strData.AsSpan())) { json = strData; } else { var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default; json = responseData.ToJson(jsonOptions); } return BrotliHelper.Compress(json); } #endregion }