Refactor: new high-performance binary serializer/deserializer
Major overhaul of binary serialization: - Rewrote AcBinarySerializer as a static, optimized, feature-rich serializer with VarInt encoding, string interning, property name tables, reference handling, and optional metadata. - Added AcBinaryDeserializer with matching features, including merge/populate support and robust error handling. - Introduced AcBinarySerializerOptions and AcSerializerOptions base class for unified serializer configuration (JSON/binary). - Added generic extension methods for "any" serialization/deserialization based on options. - Updated tests and benchmarks for new APIs; fixed null byte code and added DateTimeKind test. - Fixed namespace typos and improved code style and documentation.
This commit is contained in:
parent
b9e83e2ef8
commit
2147d981db
|
|
@ -1,7 +1,7 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
|
|
||||||
namespace AyCode.Core.Tests.Serialization;
|
namespace AyCode.Core.Tests.serialization;
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class AcBinarySerializerTests
|
public class AcBinarySerializerTests
|
||||||
|
|
@ -13,7 +13,7 @@ public class AcBinarySerializerTests
|
||||||
{
|
{
|
||||||
var result = AcBinarySerializer.Serialize<object?>(null);
|
var result = AcBinarySerializer.Serialize<object?>(null);
|
||||||
Assert.AreEqual(1, result.Length);
|
Assert.AreEqual(1, result.Length);
|
||||||
Assert.AreEqual((byte)32, result[0]); // BinaryTypeCode.Null = 32
|
Assert.AreEqual((byte)0, result[0]); // BinaryTypeCode.Null = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -70,6 +70,20 @@ public class AcBinarySerializerTests
|
||||||
Assert.AreEqual(value, result);
|
Assert.AreEqual(value, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(DateTimeKind.Unspecified)]
|
||||||
|
[DataRow(DateTimeKind.Utc)]
|
||||||
|
[DataRow(DateTimeKind.Local)]
|
||||||
|
public void Serialize_DateTime_PreservesKind(DateTimeKind kind)
|
||||||
|
{
|
||||||
|
var value = new DateTime(2024, 12, 25, 10, 30, 45, kind);
|
||||||
|
var binary = AcBinarySerializer.Serialize(value);
|
||||||
|
var result = AcBinaryDeserializer.Deserialize<DateTime>(binary);
|
||||||
|
|
||||||
|
Assert.AreEqual(value.Ticks, result.Ticks);
|
||||||
|
Assert.AreEqual(value.Kind, result.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Serialize_Guid_RoundTrip()
|
public void Serialize_Guid_RoundTrip()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,179 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for AcBinarySerializer and AcBinaryDeserializer.
|
||||||
|
/// Optimized for speed and memory efficiency over raw size.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
|
{
|
||||||
|
public override AcSerializerType SerializerType { get; init; } = AcSerializerType.Binary;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current binary format version. Incremented when breaking changes are made.
|
||||||
|
/// </summary>
|
||||||
|
public const byte FormatVersion = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default options instance with metadata and string interning enabled.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly AcBinarySerializerOptions Default = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options optimized for maximum speed (no metadata, no interning).
|
||||||
|
/// Use when deserializer knows the exact type structure.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly AcBinarySerializerOptions FastMode = new()
|
||||||
|
{
|
||||||
|
UseMetadata = false,
|
||||||
|
UseStringInterning = false,
|
||||||
|
UseReferenceHandling = false
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for shallow serialization (root level only).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly AcBinarySerializerOptions ShallowCopy = new()
|
||||||
|
{
|
||||||
|
MaxDepth = 0,
|
||||||
|
UseReferenceHandling = false
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to include metadata header with property names.
|
||||||
|
/// When enabled, property names are stored once and referenced by index.
|
||||||
|
/// Improves deserialization speed and allows schema evolution.
|
||||||
|
/// Default: true
|
||||||
|
/// </summary>
|
||||||
|
public bool UseMetadata { get; init; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to intern repeated strings.
|
||||||
|
/// When enabled, duplicate strings are stored once and referenced by index.
|
||||||
|
/// Reduces size and memory for objects with many repeated string values.
|
||||||
|
/// Default: true
|
||||||
|
/// </summary>
|
||||||
|
public bool UseStringInterning { get; init; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum string length to consider for interning.
|
||||||
|
/// Shorter strings are written inline to avoid overhead.
|
||||||
|
/// Default: 4 (strings shorter than 4 chars are not interned)
|
||||||
|
/// </summary>
|
||||||
|
public byte MinStringInternLength { get; init; } = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initial capacity for serialization buffer.
|
||||||
|
/// Default: 4096 bytes
|
||||||
|
/// </summary>
|
||||||
|
public int InitialBufferCapacity { get; init; } = 4096;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates options with specified max depth.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static AcBinarySerializerOptions WithMaxDepth(byte maxDepth) => new() { MaxDepth = maxDepth };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates options without reference handling.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static AcBinarySerializerOptions WithoutReferenceHandling() => new() { UseReferenceHandling = false };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates options without metadata (faster but less flexible).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static AcBinarySerializerOptions WithoutMetadata() => new() { UseMetadata = false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary type codes for serialization.
|
||||||
|
/// Designed for fast switch dispatch and compact storage.
|
||||||
|
/// Lower 5 bits = type code (0-31)
|
||||||
|
/// Upper 3 bits = flags (interned, reference, has-type-info)
|
||||||
|
/// </summary>
|
||||||
|
internal static class BinaryTypeCode
|
||||||
|
{
|
||||||
|
// Primitive types (0-15)
|
||||||
|
public const byte Null = 0;
|
||||||
|
public const byte True = 1;
|
||||||
|
public const byte False = 2;
|
||||||
|
public const byte Int8 = 3;
|
||||||
|
public const byte UInt8 = 4;
|
||||||
|
public const byte Int16 = 5;
|
||||||
|
public const byte UInt16 = 6;
|
||||||
|
public const byte Int32 = 7;
|
||||||
|
public const byte UInt32 = 8;
|
||||||
|
public const byte Int64 = 9;
|
||||||
|
public const byte UInt64 = 10;
|
||||||
|
public const byte Float32 = 11;
|
||||||
|
public const byte Float64 = 12;
|
||||||
|
public const byte Decimal = 13;
|
||||||
|
public const byte Char = 14;
|
||||||
|
|
||||||
|
// String types (16-19)
|
||||||
|
public const byte String = 16; // Inline UTF8 string
|
||||||
|
public const byte StringInterned = 17; // Reference to interned string by index
|
||||||
|
public const byte StringEmpty = 18; // Empty string marker
|
||||||
|
|
||||||
|
// Date/Time types (20-23)
|
||||||
|
public const byte DateTime = 20;
|
||||||
|
public const byte DateTimeOffset = 21;
|
||||||
|
public const byte TimeSpan = 22;
|
||||||
|
public const byte Guid = 23;
|
||||||
|
|
||||||
|
// Enum (24)
|
||||||
|
public const byte Enum = 24;
|
||||||
|
|
||||||
|
// Complex types (25-31)
|
||||||
|
public const byte Object = 25; // Start of object
|
||||||
|
public const byte ObjectEnd = 26; // End of object marker
|
||||||
|
public const byte ObjectRef = 27; // Reference to previously serialized object
|
||||||
|
public const byte Array = 28; // Start of array/list
|
||||||
|
public const byte Dictionary = 29; // Start of dictionary
|
||||||
|
public const byte ByteArray = 30; // Optimized byte[] storage
|
||||||
|
|
||||||
|
// Special markers (32+, for header/meta)
|
||||||
|
public const byte MetadataHeader = 32; // Binary has metadata section
|
||||||
|
public const byte NoMetadataHeader = 33; // Binary has no metadata
|
||||||
|
|
||||||
|
// Compact integer variants (for VarInt optimization)
|
||||||
|
public const byte Int32Tiny = 64; // -16 to 111 stored in single byte (value = code - 64 - 16)
|
||||||
|
public const byte Int32TinyMax = 191; // Upper bound for tiny int
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if type code represents a reference (string or object).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsReference(byte code) => code == StringInterned || code == ObjectRef;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if type code is a tiny int (single byte int32 encoding).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsTinyInt(byte code) => code >= Int32Tiny && code <= Int32TinyMax;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decode tiny int value from type code.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int DecodeTinyInt(byte code) => code - Int32Tiny - 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode small int value (-16 to 111) as type code.
|
||||||
|
/// Returns true if value fits in tiny encoding.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool TryEncodeTinyInt(int value, out byte code)
|
||||||
|
{
|
||||||
|
if (value >= -16 && value <= 111)
|
||||||
|
{
|
||||||
|
code = (byte)(value + 16 + Int32Tiny);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
code = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
namespace AyCode.Core.Extensions;
|
||||||
|
|
||||||
|
public enum AcSerializerType : byte
|
||||||
|
{
|
||||||
|
Json = 0,
|
||||||
|
Binary = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class AcSerializerOptions
|
||||||
|
{
|
||||||
|
public abstract AcSerializerType SerializerType { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to use $id/$ref reference handling for circular references.
|
||||||
|
/// Default: true
|
||||||
|
/// </summary>
|
||||||
|
public bool UseReferenceHandling { get; init; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum depth for serialization/deserialization.
|
||||||
|
/// 0 = root level only (primitives of root object)
|
||||||
|
/// 1 = root + first level of nested objects/collections
|
||||||
|
/// byte.MaxValue (255) = effectively unlimited
|
||||||
|
/// Default: byte.MaxValue
|
||||||
|
/// </summary>
|
||||||
|
public byte MaxDepth { get; init; } = byte.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for AcJsonSerializer and AcJsonDeserializer.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AcJsonSerializerOptions : AcSerializerOptions
|
||||||
|
{
|
||||||
|
public override AcSerializerType SerializerType { get; init; } = AcSerializerType.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default options instance with reference handling enabled and max depth.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly AcJsonSerializerOptions Default = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for shallow serialization (root level only, no references).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly AcJsonSerializerOptions ShallowCopy = new() { MaxDepth = 0, UseReferenceHandling = false };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates options with specified max depth.
|
||||||
|
/// </summary>
|
||||||
|
public static AcJsonSerializerOptions WithMaxDepth(byte maxDepth) => new() { MaxDepth = maxDepth };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates options without reference handling.
|
||||||
|
/// </summary>
|
||||||
|
public static AcJsonSerializerOptions WithoutReferenceHandling() => new() { UseReferenceHandling = false };
|
||||||
|
}
|
||||||
|
|
@ -11,47 +11,6 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace AyCode.Core.Extensions;
|
namespace AyCode.Core.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Options for AcJsonSerializer and AcJsonDeserializer.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class AcJsonSerializerOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Default options instance with reference handling enabled and max depth.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly AcJsonSerializerOptions Default = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Options for shallow serialization (root level only, no references).
|
|
||||||
/// </summary>
|
|
||||||
public static readonly AcJsonSerializerOptions ShallowCopy = new() { MaxDepth = 0, UseReferenceHandling = false };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to use $id/$ref reference handling for circular references.
|
|
||||||
/// Default: true
|
|
||||||
/// </summary>
|
|
||||||
public bool UseReferenceHandling { get; init; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum depth for serialization/deserialization.
|
|
||||||
/// 0 = root level only (primitives of root object)
|
|
||||||
/// 1 = root + first level of nested objects/collections
|
|
||||||
/// byte.MaxValue (255) = effectively unlimited
|
|
||||||
/// Default: byte.MaxValue
|
|
||||||
/// </summary>
|
|
||||||
public byte MaxDepth { get; init; } = byte.MaxValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates options with specified max depth.
|
|
||||||
/// </summary>
|
|
||||||
public static AcJsonSerializerOptions WithMaxDepth(byte maxDepth) => new() { MaxDepth = maxDepth };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates options without reference handling.
|
|
||||||
/// </summary>
|
|
||||||
public static AcJsonSerializerOptions WithoutReferenceHandling() => new() { UseReferenceHandling = false };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cached result for IId type info lookup.
|
/// Cached result for IId type info lookup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -340,8 +340,7 @@ public static class SerializeObjectExtensions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to JSON string with default options.
|
/// Serialize object to JSON string with default options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ToJson<T>(this T source)
|
public static string ToJson<T>(this T source) => AcJsonSerializer.Serialize(source);
|
||||||
=> AcJsonSerializer.Serialize(source);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to JSON string with specified options.
|
/// Serialize object to JSON string with specified options.
|
||||||
|
|
@ -445,14 +444,83 @@ public static class SerializeObjectExtensions
|
||||||
public static T MessagePackTo<T>(this byte[] message, MessagePackSerializerOptions options)
|
public static T MessagePackTo<T>(this byte[] message, MessagePackSerializerOptions options)
|
||||||
=> MessagePackSerializer.Deserialize<T>(message, options);
|
=> MessagePackSerializer.Deserialize<T>(message, options);
|
||||||
|
|
||||||
|
|
||||||
|
public static object ToAny<T>(this T source, AcSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json) return ToJson(source, (AcJsonSerializerOptions)options);
|
||||||
|
return ToBinary(source, (AcBinarySerializerOptions)options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserialize data (JSON string or binary byte[]) to object based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static T? AnyTo<T>(this object data, AcSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
return ((string)data).JsonTo<T>((AcJsonSerializerOptions)options);
|
||||||
|
return ((byte[])data).BinaryTo<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserialize data to specified type based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static object? AnyTo(this object data, Type targetType, AcSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
return ((string)data).JsonTo(targetType, (AcJsonSerializerOptions)options);
|
||||||
|
return ((byte[])data).BinaryTo(targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate existing object from data based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static void AnyTo<T>(this object data, T target, AcSerializerOptions options) where T : class
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
((string)data).JsonTo(target, (AcJsonSerializerOptions)options);
|
||||||
|
else
|
||||||
|
((byte[])data).BinaryTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate existing object with merge semantics based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static void AnyToMerge<T>(this object data, T target, AcSerializerOptions options) where T : class
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
((string)data).JsonTo(target, (AcJsonSerializerOptions)options); // JSON always merges
|
||||||
|
else
|
||||||
|
((byte[])data).BinaryToMerge(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clone object via serialization based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static T? CloneToAny<T>(this T source, AcSerializerOptions options) where T : class
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
return source.CloneTo<T>((AcJsonSerializerOptions)options);
|
||||||
|
return source.BinaryCloneTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy object properties to target via serialization based on options.
|
||||||
|
/// </summary>
|
||||||
|
public static void CopyToAny<T>(this T source, T target, AcSerializerOptions options) where T : class
|
||||||
|
{
|
||||||
|
if (options.SerializerType == AcSerializerType.Json)
|
||||||
|
source.CopyTo(target, (AcJsonSerializerOptions)options);
|
||||||
|
else
|
||||||
|
source.BinaryCopyTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
#region Binary Serialization Extension Methods
|
#region Binary Serialization Extension Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to binary byte array with default options.
|
/// Serialize object to binary byte array with default options.
|
||||||
/// Significantly faster than JSON, especially for large data in WASM.
|
/// Significantly faster than JSON, especially for large data in WASM.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static byte[] ToBinary<T>(this T source)
|
public static byte[] ToBinary<T>(this T source) => AcBinarySerializer.Serialize(source);
|
||||||
=> AcBinarySerializer.Serialize(source);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to binary byte array with specified options.
|
/// Serialize object to binary byte array with specified options.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public abstract class AcWebSignalRHubBase<TSignalRTags, TLogger>(IConfiguration
|
||||||
protected TLogger Logger = logger;
|
protected TLogger Logger = logger;
|
||||||
protected IConfiguration Configuration = configuration;
|
protected IConfiguration Configuration = configuration;
|
||||||
|
|
||||||
|
protected AcSerializerOptions SerializerOptions = new AcBinarySerializerOptions();
|
||||||
|
|
||||||
#region Connection Lifecycle
|
#region Connection Lifecycle
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ namespace BenchmarkSuite1
|
||||||
pointsPerMeasurement: 5);
|
pointsPerMeasurement: 5);
|
||||||
|
|
||||||
var binaryWithRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
|
var binaryWithRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
|
||||||
var binaryNoRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.WithoutReferenceHandling);
|
var binaryNoRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.WithoutReferenceHandling());
|
||||||
var json = AcJsonSerializer.Serialize(order, AcJsonSerializerOptions.WithoutReferenceHandling());
|
var json = AcJsonSerializer.Serialize(order, AcJsonSerializerOptions.WithoutReferenceHandling());
|
||||||
var jsonBytes = Encoding.UTF8.GetByteCount(json);
|
var jsonBytes = Encoding.UTF8.GetByteCount(json);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue