Refactor JSON/Binary context & metadata class structure

Renamed and split internal context and metadata classes for JSON and binary serialization/deserialization.
- Replaced CrossTypeDeserializerBase with DeserializeCrossTypeBase; updated all usages.
- Renamed DeserializationContextPool to JsonDeserializationContextPool; SerializationContextPool now uses JsonSerializationContext.
- Renamed DeserializeTypeMetadata to JsonDeserializeTypeMetadata; TypeMetadata to JsonTypeMetadata, each in their own files.
- Updated all method signatures and internal references to use new names.
- Simplified BinaryDeserializeTypeMetadata by removing internal property dictionary.
No changes to public APIs; improves code clarity and maintainability.
This commit is contained in:
Loretta 2026-01-03 08:34:49 +01:00
parent f875738b08
commit 60ca154c6f
11 changed files with 50 additions and 60 deletions

View File

@ -10,8 +10,6 @@ public static partial class AcBinaryDeserializer
{ {
internal sealed class BinaryDeserializeTypeMetadata : BinaryTypeMetadataBase internal sealed class BinaryDeserializeTypeMetadata : BinaryTypeMetadataBase
{ {
private readonly FrozenDictionary<string, BinaryPropertySetterInfo> _properties;
/// <summary> /// <summary>
/// Properties array ordered alphabetically by name for index-based lookup. /// Properties array ordered alphabetically by name for index-based lookup.
/// This matches the serializer's ordering, enabling O(1) array access. /// This matches the serializer's ordering, enabling O(1) array access.
@ -27,10 +25,6 @@ public static partial class AcBinaryDeserializer
{ {
PropertiesArray[i] = new BinaryPropertySetterInfo(orderedProperties[i], type); PropertiesArray[i] = new BinaryPropertySetterInfo(orderedProperties[i], type);
} }
_properties = PropertiesArray.Length == 0
? FrozenDictionary<string, BinaryPropertySetterInfo>.Empty
: PropertiesArray.ToFrozenDictionary(static p => p.Name, static p => p, StringComparer.Ordinal);
} }
/// <summary> /// <summary>
@ -39,10 +33,6 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BinaryPropertySetterInfo? GetPropertyByIndex(int index) public BinaryPropertySetterInfo? GetPropertyByIndex(int index)
=> (uint)index < (uint)PropertiesArray.Length ? PropertiesArray[index] : null; => (uint)index < (uint)PropertiesArray.Length ? PropertiesArray[index] : null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetProperty(string name, out BinaryPropertySetterInfo? propertyInfo)
=> _properties.TryGetValue(name, out propertyInfo);
} }
/// <summary> /// <summary>

View File

@ -14,7 +14,7 @@ public static partial class AcBinaryDeserializer
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int[] GetIndexMapping(Type sourceType, Type destType, PropertyMapperDelegate? customMapper) private static int[] GetIndexMapping(Type sourceType, Type destType, PropertyMapperDelegate? customMapper)
=> IndexMappingCache.GetOrBuild(sourceType, destType, customMapper, CrossTypeDeserializerBase.GetOrderedProperties); => IndexMappingCache.GetOrBuild(sourceType, destType, customMapper, DeserializeCrossTypeBase.GetOrderedProperties);
#region Cross-Type Deserialization #region Cross-Type Deserialization
@ -42,14 +42,14 @@ public static partial class AcBinaryDeserializer
public static TDest? Deserialize<TSource, TDest>(ReadOnlySpan<byte> data, AcBinarySerializerOptions options) public static TDest? Deserialize<TSource, TDest>(ReadOnlySpan<byte> data, AcBinarySerializerOptions options)
{ {
// Early exit checks // Early exit checks
if (CrossTypeDeserializerBase.IsEmptyData(data, BinaryTypeCode.Null)) if (DeserializeCrossTypeBase.IsEmptyData(data, BinaryTypeCode.Null))
return default; return default;
var sourceType = typeof(TSource); var sourceType = typeof(TSource);
var destType = typeof(TDest); var destType = typeof(TDest);
// Fast path: same type // Fast path: same type
if (CrossTypeDeserializerBase.IsSameType(sourceType, destType)) if (DeserializeCrossTypeBase.IsSameType(sourceType, destType))
return Deserialize<TDest>(data, options); return Deserialize<TDest>(data, options);
// Cross-type path: use index mapping // Cross-type path: use index mapping
@ -117,17 +117,17 @@ public static partial class AcBinaryDeserializer
where TDest : class where TDest : class
{ {
// Validation // Validation
CrossTypeDeserializerBase.ValidatePopulateTarget(target); DeserializeCrossTypeBase.ValidatePopulateTarget(target);
// Early exit checks // Early exit checks
if (CrossTypeDeserializerBase.IsEmptyData(data, BinaryTypeCode.Null)) if (DeserializeCrossTypeBase.IsEmptyData(data, BinaryTypeCode.Null))
return; return;
var sourceType = typeof(TSource); var sourceType = typeof(TSource);
var destType = typeof(TDest); var destType = typeof(TDest);
// Fast path: same type // Fast path: same type
if (CrossTypeDeserializerBase.IsSameType(sourceType, destType)) if (DeserializeCrossTypeBase.IsSameType(sourceType, destType))
{ {
Populate(data, target, options); Populate(data, target, options);
return; return;

View File

@ -8,7 +8,7 @@ namespace AyCode.Core.Serializers;
/// Utility class providing common cross-type deserialization functionality. /// Utility class providing common cross-type deserialization functionality.
/// Shared by both JSON and Binary deserializers. /// Shared by both JSON and Binary deserializers.
/// </summary> /// </summary>
public static class CrossTypeDeserializerBase public static class DeserializeCrossTypeBase
{ {
/// <summary> /// <summary>
/// Gets ordered properties for a type using stable PropertyIndex ordering. /// Gets ordered properties for a type using stable PropertyIndex ordering.

View File

@ -5,7 +5,7 @@ namespace AyCode.Core.Serializers.Jsons;
public static partial class AcJsonDeserializer public static partial class AcJsonDeserializer
{ {
private static class DeserializationContextPool private static class JsonDeserializationContextPool
{ {
private static readonly ConcurrentQueue<DeserializationContext> Pool = new(); private static readonly ConcurrentQueue<DeserializationContext> Pool = new();
private const int MaxPoolSize = 16; private const int MaxPoolSize = 16;

View File

@ -9,22 +9,22 @@ namespace AyCode.Core.Serializers.Jsons;
public static partial class AcJsonDeserializer public static partial class AcJsonDeserializer
{ {
private static readonly ConcurrentDictionary<Type, DeserializeTypeMetadata> TypeMetadataCache = new(); private static readonly ConcurrentDictionary<Type, JsonDeserializeTypeMetadata> TypeMetadataCache = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static DeserializeTypeMetadata GetTypeMetadata(in Type type) private static JsonDeserializeTypeMetadata GetTypeMetadata(in Type type)
=> TypeMetadataCache.GetOrAdd(type, static t => new DeserializeTypeMetadata(t)); => TypeMetadataCache.GetOrAdd(type, static t => new JsonDeserializeTypeMetadata(t));
/// <summary> /// <summary>
/// JSON deserialization type metadata. /// JSON deserialization type metadata.
/// Extends DeserializeTypeMetadataBase which provides cached IId info. /// Extends DeserializeTypeMetadataBase which provides cached IId info.
/// </summary> /// </summary>
private sealed class DeserializeTypeMetadata : DeserializeTypeMetadataBase private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase
{ {
public FrozenDictionary<string, PropertySetterInfo> PropertySettersFrozen { get; } public FrozenDictionary<string, PropertySetterInfo> PropertySettersFrozen { get; }
public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects) public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects)
public DeserializeTypeMetadata(Type type) : base(type) public JsonDeserializeTypeMetadata(Type type) : base(type)
{ {
var props = GetSerializableProperties(type, requiresRead: true, requiresWrite: true).ToList(); var props = GetSerializableProperties(type, requiresRead: true, requiresWrite: true).ToList();

View File

@ -110,7 +110,7 @@ public static partial class AcJsonDeserializer
/// <summary> /// <summary>
/// Copies properties from source to target using JSON metadata. /// Copies properties from source to target using JSON metadata.
/// </summary> /// </summary>
private static void CopyPropertiesJson(object source, object target, DeserializeTypeMetadata metadata) private static void CopyPropertiesJson(object source, object target, JsonDeserializeTypeMetadata metadata)
{ {
foreach (var prop in metadata.PropertiesArray) foreach (var prop in metadata.PropertiesArray)
{ {
@ -120,7 +120,7 @@ public static partial class AcJsonDeserializer
} }
} }
private static void PopulateObjectInternal(in JsonElement element, object target, DeserializeTypeMetadata metadata, DeserializationContext context, int depth) private static void PopulateObjectInternal(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth)
{ {
var propsDict = metadata.PropertySettersFrozen; var propsDict = metadata.PropertySettersFrozen;
var nextDepth = depth + 1; var nextDepth = depth + 1;
@ -143,7 +143,7 @@ public static partial class AcJsonDeserializer
} }
} }
private static void PopulateObjectInternalMerge(in JsonElement element, object target, DeserializeTypeMetadata metadata, DeserializationContext context, int depth) private static void PopulateObjectInternalMerge(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth)
{ {
var propsDict = metadata.PropertySettersFrozen; var propsDict = metadata.PropertySettersFrozen;
var nextDepth = depth + 1; var nextDepth = depth + 1;
@ -234,7 +234,7 @@ public static partial class AcJsonDeserializer
var nextDepth = depth + 1; var nextDepth = depth + 1;
// ChainMode: Use cached IId info from element type metadata (no runtime reflection!) // ChainMode: Use cached IId info from element type metadata (no runtime reflection!)
DeserializeTypeMetadata? elementMetadata = null; JsonDeserializeTypeMetadata? elementMetadata = null;
if (context.IsChainMode && !IsPrimitiveOrStringFast(elementType)) if (context.IsChainMode && !IsPrimitiveOrStringFast(elementType))
{ {
elementMetadata = GetTypeMetadata(elementType); elementMetadata = GetTypeMetadata(elementType);

View File

@ -255,7 +255,7 @@ public static partial class AcJsonDeserializer
/// <summary> /// <summary>
/// Read object from reader when we're already at StartObject. /// Read object from reader when we're already at StartObject.
/// </summary> /// </summary>
private static object? ReadObjectFromReaderAtStart(ref Utf8JsonReader reader, Type targetType, DeserializeTypeMetadata metadata, byte maxDepth, int depth) private static object? ReadObjectFromReaderAtStart(ref Utf8JsonReader reader, Type targetType, JsonDeserializeTypeMetadata metadata, byte maxDepth, int depth)
{ {
if (depth > maxDepth) if (depth > maxDepth)
{ {
@ -380,7 +380,7 @@ public static partial class AcJsonDeserializer
/// <summary> /// <summary>
/// Populate object using Utf8JsonReader streaming (no DOM allocation). /// Populate object using Utf8JsonReader streaming (no DOM allocation).
/// </summary> /// </summary>
private static void PopulateObjectWithUtf8Reader(string json, object target, DeserializeTypeMetadata metadata, byte maxDepth) private static void PopulateObjectWithUtf8Reader(string json, object target, JsonDeserializeTypeMetadata metadata, byte maxDepth)
{ {
var (buffer, length) = GetUtf8Bytes(json); var (buffer, length) = GetUtf8Bytes(json);
try try
@ -442,7 +442,7 @@ public static partial class AcJsonDeserializer
/// <summary> /// <summary>
/// Populate object with merge semantics from Utf8JsonReader. /// Populate object with merge semantics from Utf8JsonReader.
/// </summary> /// </summary>
private static void PopulateObjectMergeFromReader(ref Utf8JsonReader reader, object target, DeserializeTypeMetadata metadata, byte maxDepth, int depth) private static void PopulateObjectMergeFromReader(ref Utf8JsonReader reader, object target, JsonDeserializeTypeMetadata metadata, byte maxDepth, int depth)
{ {
var nextDepth = depth + 1; var nextDepth = depth + 1;
var maxDepthReached = nextDepth > maxDepth; var maxDepthReached = nextDepth > maxDepth;
@ -607,7 +607,7 @@ public static partial class AcJsonDeserializer
/// <summary> /// <summary>
/// Copy properties from source to target using metadata. /// Copy properties from source to target using metadata.
/// </summary> /// </summary>
private static void CopyProperties(object source, object target, DeserializeTypeMetadata metadata) private static void CopyProperties(object source, object target, JsonDeserializeTypeMetadata metadata)
{ {
foreach (var prop in metadata.PropertySettersFrozen.Values) foreach (var prop in metadata.PropertySettersFrozen.Values)
{ {

View File

@ -76,7 +76,7 @@ public static partial class AcJsonDeserializer
var jsonBytes = utf8Json.ToArray(); var jsonBytes = utf8Json.ToArray();
using var doc = JsonDocument.Parse(jsonBytes); using var doc = JsonDocument.Parse(jsonBytes);
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
try try
{ {
var result = ReadValue(doc.RootElement, targetType, context, 0); var result = ReadValue(doc.RootElement, targetType, context, 0);
@ -85,7 +85,7 @@ public static partial class AcJsonDeserializer
} }
finally finally
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
} }
} }
catch (AcJsonDeserializationException) { throw; } catch (AcJsonDeserializationException) { throw; }
@ -135,7 +135,7 @@ public static partial class AcJsonDeserializer
} }
using var doc = JsonDocument.Parse(json); using var doc = JsonDocument.Parse(json);
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
try try
{ {
var result = ReadValue(doc.RootElement, targetType, context, 0); var result = ReadValue(doc.RootElement, targetType, context, 0);
@ -144,7 +144,7 @@ public static partial class AcJsonDeserializer
} }
finally finally
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
} }
} }
catch (AcJsonDeserializationException) { throw; } catch (AcJsonDeserializationException) { throw; }
@ -202,7 +202,7 @@ public static partial class AcJsonDeserializer
// Reference handling requires DOM - copy to array for JsonDocument.Parse // Reference handling requires DOM - copy to array for JsonDocument.Parse
var jsonBytes = Encoding.UTF8.GetBytes(json); var jsonBytes = Encoding.UTF8.GetBytes(json);
using var doc = JsonDocument.Parse(jsonBytes); using var doc = JsonDocument.Parse(jsonBytes);
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
try try
{ {
var result = ReadValue(doc.RootElement, targetType, context, 0); var result = ReadValue(doc.RootElement, targetType, context, 0);
@ -211,7 +211,7 @@ public static partial class AcJsonDeserializer
} }
finally finally
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
} }
} }
catch (AcJsonDeserializationException) { throw; } catch (AcJsonDeserializationException) { throw; }
@ -287,7 +287,7 @@ public static partial class AcJsonDeserializer
// Reference handling requires DOM - copy to array for JsonDocument.Parse // Reference handling requires DOM - copy to array for JsonDocument.Parse
var jsonBytes = utf8Json.ToArray(); var jsonBytes = utf8Json.ToArray();
using var doc = JsonDocument.Parse(jsonBytes); using var doc = JsonDocument.Parse(jsonBytes);
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
try try
{ {
var result = ReadValue(doc.RootElement, targetType, context, 0); var result = ReadValue(doc.RootElement, targetType, context, 0);
@ -296,7 +296,7 @@ public static partial class AcJsonDeserializer
} }
finally finally
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
} }
} }
catch (AcJsonDeserializationException) { throw; } catch (AcJsonDeserializationException) { throw; }
@ -398,7 +398,7 @@ public static partial class AcJsonDeserializer
using var doc = JsonDocument.Parse(json); using var doc = JsonDocument.Parse(json);
var rootElement = doc.RootElement; var rootElement = doc.RootElement;
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
context.IsMergeMode = true; context.IsMergeMode = true;
try try
@ -426,7 +426,7 @@ public static partial class AcJsonDeserializer
} }
finally finally
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
} }
} }
catch (AcJsonDeserializationException) { throw; } catch (AcJsonDeserializationException) { throw; }
@ -505,7 +505,7 @@ public static partial class AcJsonDeserializer
var doc = JsonDocument.Parse(json); var doc = JsonDocument.Parse(json);
var chainTracker = new AcSerializerCommon.ChainReferenceTracker(); var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
var context = DeserializationContextPool.Get(options); var context = JsonDeserializationContextPool.Get(options);
context.ChainTracker = chainTracker; context.ChainTracker = chainTracker;
try try
@ -516,7 +516,7 @@ public static partial class AcJsonDeserializer
} }
catch catch
{ {
DeserializationContextPool.Return(context); JsonDeserializationContextPool.Return(context);
doc.Dispose(); doc.Dispose();
throw; throw;
} }
@ -629,7 +629,7 @@ public static partial class AcJsonDeserializer
if (_context != null) if (_context != null)
{ {
DeserializationContextPool.Return(_context); JsonDeserializationContextPool.Return(_context);
_context = null; _context = null;
} }
_document?.Dispose(); _document?.Dispose();

View File

@ -10,22 +10,22 @@ public static partial class AcJsonSerializer
{ {
private static class SerializationContextPool private static class SerializationContextPool
{ {
private static readonly ConcurrentQueue<SerializationContext> Pool = new(); private static readonly ConcurrentQueue<JsonSerializationContext> Pool = new();
private const int MaxPoolSize = 16; private const int MaxPoolSize = 16;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SerializationContext Get(in AcJsonSerializerOptions options) public static JsonSerializationContext Get(in AcJsonSerializerOptions options)
{ {
if (Pool.TryDequeue(out var context)) if (Pool.TryDequeue(out var context))
{ {
context.Reset(options); context.Reset(options);
return context; return context;
} }
return new SerializationContext(options); return new JsonSerializationContext(options);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Return(SerializationContext context) public static void Return(JsonSerializationContext context)
{ {
if (Pool.Count < MaxPoolSize) if (Pool.Count < MaxPoolSize)
{ {
@ -35,7 +35,7 @@ public static partial class AcJsonSerializer
} }
} }
private sealed class SerializationContext : IDisposable private sealed class JsonSerializationContext : IDisposable
{ {
private readonly ArrayBufferWriter<byte> _buffer; private readonly ArrayBufferWriter<byte> _buffer;
public Utf8JsonWriter Writer { get; private set; } public Utf8JsonWriter Writer { get; private set; }
@ -52,7 +52,7 @@ public static partial class AcJsonSerializer
SkipValidation = true // Skip validation for performance SkipValidation = true // Skip validation for performance
}; };
public SerializationContext(in AcJsonSerializerOptions options) public JsonSerializationContext(in AcJsonSerializerOptions options)
{ {
_buffer = new ArrayBufferWriter<byte>(4096); _buffer = new ArrayBufferWriter<byte>(4096);
Writer = new Utf8JsonWriter(_buffer, WriterOptions); Writer = new Utf8JsonWriter(_buffer, WriterOptions);

View File

@ -7,17 +7,17 @@ namespace AyCode.Core.Serializers.Jsons;
public static partial class AcJsonSerializer public static partial class AcJsonSerializer
{ {
private static readonly ConcurrentDictionary<Type, TypeMetadata> TypeMetadataCache = new(); private static readonly ConcurrentDictionary<Type, JsonTypeMetadata> TypeMetadataCache = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeMetadata GetTypeMetadata(in Type type) private static JsonTypeMetadata GetTypeMetadata(in Type type)
=> TypeMetadataCache.GetOrAdd(type, static t => new TypeMetadata(t)); => TypeMetadataCache.GetOrAdd(type, static t => new JsonTypeMetadata(t));
private sealed class TypeMetadata : TypeMetadataBase private sealed class JsonTypeMetadata : TypeMetadataBase
{ {
public PropertyAccessor[] Properties { get; } public PropertyAccessor[] Properties { get; }
public TypeMetadata(Type type) : base(type) public JsonTypeMetadata(Type type) : base(type)
{ {
Properties = GetSerializableProperties(type, requiresRead: true, requiresWrite: false) Properties = GetSerializableProperties(type, requiresRead: true, requiresWrite: false)
.Select(p => new PropertyAccessor(p, type)) .Select(p => new PropertyAccessor(p, type))

View File

@ -121,7 +121,7 @@ public static partial class AcJsonSerializer
#region Reference Scanning #region Reference Scanning
private static void ScanReferences(object? value, SerializationContext context, int depth) private static void ScanReferences(object? value, JsonSerializationContext context, int depth)
{ {
if (value == null || depth > context.MaxDepth) return; if (value == null || depth > context.MaxDepth) return;
@ -150,7 +150,7 @@ public static partial class AcJsonSerializer
#region Serialization #region Serialization
private static void WriteValue(object? value, SerializationContext context, int depth) private static void WriteValue(object? value, JsonSerializationContext context, int depth)
{ {
if (value == null) { context.Writer.WriteNullValue(); return; } if (value == null) { context.Writer.WriteNullValue(); return; }
@ -167,7 +167,7 @@ public static partial class AcJsonSerializer
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObject(object value, in Type type, SerializationContext context, int depth) private static void WriteObject(object value, in Type type, JsonSerializationContext context, int depth)
{ {
var writer = context.Writer; var writer = context.Writer;
@ -207,7 +207,7 @@ public static partial class AcJsonSerializer
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteArray(IEnumerable enumerable, SerializationContext context, int depth) private static void WriteArray(IEnumerable enumerable, JsonSerializationContext context, int depth)
{ {
var writer = context.Writer; var writer = context.Writer;
writer.WriteStartArray(); writer.WriteStartArray();
@ -222,7 +222,7 @@ public static partial class AcJsonSerializer
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteDictionary(IDictionary dictionary, SerializationContext context, int depth) private static void WriteDictionary(IDictionary dictionary, JsonSerializationContext context, int depth)
{ {
var writer = context.Writer; var writer = context.Writer;
writer.WriteStartObject(); writer.WriteStartObject();