Add IId-aware reference tracking to serializer/deserializer
Enhances both binary deserialization and JSON serialization to support IId-based reference tracking and handling. Refactors object reference reading in AcBinaryDeserializer to use a unified TryGetValue approach. Adds IId-aware tracking, ID writing, and reference lookup methods to AcJsonSerializer and updates ScanReferences and WriteObject to use them. Improves support for custom identity types and robust reference management.
This commit is contained in:
parent
6dbe4d76c1
commit
7d133a4b24
|
|
@ -913,18 +913,20 @@ public static partial class AcBinaryDeserializer
|
||||||
private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth)
|
||||||
{
|
{
|
||||||
var wrapper = context.ContextClass.GetWrapper(targetType);
|
var wrapper = context.ContextClass.GetWrapper(targetType);
|
||||||
|
return ReadObjectRef(ref context, ref wrapper, depth);
|
||||||
|
}
|
||||||
|
private static object? ReadObjectRef(ref BinaryDeserializationContext context, ref TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth)
|
||||||
|
{
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
|
|
||||||
if (metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None)
|
if (metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None)
|
||||||
{
|
{
|
||||||
var map = wrapper.IdentityMap;
|
|
||||||
|
|
||||||
// Read ID based on type and lookup in IdentityMap
|
// Read ID based on type and lookup in IdentityMap
|
||||||
return metadata.IdAccessorType switch
|
return metadata.IdAccessorType switch
|
||||||
{
|
{
|
||||||
AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, map),
|
AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, ref wrapper),
|
||||||
AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, map),
|
AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, ref wrapper),
|
||||||
AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, map),
|
AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, ref wrapper),
|
||||||
_ => throw new Exception("metadata.IdAccessorType not valid")
|
_ => throw new Exception("metadata.IdAccessorType not valid")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -933,30 +935,24 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object? ReadObjectRefInt32(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map)
|
private static object? ReadObjectRefInt32(ref BinaryDeserializationContext context, ref TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper)
|
||||||
{
|
{
|
||||||
var id = context.ReadVarInt();
|
var id = context.ReadVarInt();
|
||||||
if (map is AcSerializerCommon.IdentityMap<int> intMap && intMap.TryGetValue(id, out var obj))
|
return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefInt32");
|
||||||
return obj;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object? ReadObjectRefInt64(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map)
|
private static object? ReadObjectRefInt64(ref BinaryDeserializationContext context, ref TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper)
|
||||||
{
|
{
|
||||||
var id = context.ReadVarLong(); // VarLong, not VarInt!
|
var id = context.ReadVarLong();
|
||||||
if (map is AcSerializerCommon.IdentityMap<long> longMap && longMap.TryGetValue(id, out var obj))
|
return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefInt64");;
|
||||||
return obj;
|
|
||||||
return null; // Long IIds don't use flat reference tracking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object? ReadObjectRefGuid(ref BinaryDeserializationContext context, AcSerializerCommon.IIdentityMap? map)
|
private static object? ReadObjectRefGuid(ref BinaryDeserializationContext context, ref TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper)
|
||||||
{
|
{
|
||||||
var id = context.ReadGuidUnsafe(); // 16 bytes raw!
|
var id = context.ReadGuidUnsafe(); // 16 bytes raw!
|
||||||
if (map is AcSerializerCommon.IdentityMap<Guid> guidMap && guidMap.TryGetValue(id, out var obj))
|
return context.ContextClass.TryGetValue(wrapper, id, out var instance) ? instance : throw new Exception("ReadObjectRefGuid");;
|
||||||
return obj;
|
|
||||||
return null; // Guid IIds don't use flat reference tracking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth)
|
||||||
|
|
|
||||||
|
|
@ -86,15 +86,47 @@ public static partial class AcJsonSerializer
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj);
|
public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IId-aware tracking for reference scanning.
|
||||||
|
/// Uses IId.Id for IId types, RuntimeHelpers.GetHashCode for others.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrackForScanningWithIId(object obj, JsonSerializeTypeMetadata metadata)
|
||||||
|
{
|
||||||
|
int existingRefId;
|
||||||
|
return _refTracker.TrackForScanningWithIId(obj, metadata, out existingRefId);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool ShouldWriteId(object obj, out int id) => _refTracker.ShouldWriteId(obj, out id);
|
public bool ShouldWriteId(object obj, out int id) => _refTracker.ShouldWriteId(obj, out id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IId-aware check for reference ID writing.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool ShouldWriteIdForIId(object obj, JsonSerializeTypeMetadata metadata, out int id)
|
||||||
|
=> _refTracker.ShouldWriteIdForIId(obj, metadata, out id);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void MarkAsWritten(object obj, int id) => _refTracker.MarkAsWritten(obj, id);
|
public void MarkAsWritten(object obj, int id) => _refTracker.MarkAsWritten(obj, id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IId-aware mark as written.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void MarkAsWrittenForIId(object obj, JsonSerializeTypeMetadata metadata, int id)
|
||||||
|
=> _refTracker.MarkAsWrittenForIId(obj, metadata, id);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId);
|
public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IId-aware existing ref lookup.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TryGetExistingRefForIId(object obj, JsonSerializeTypeMetadata metadata, out int refId)
|
||||||
|
=> _refTracker.TryGetExistingRefForIId(obj, metadata, out refId);
|
||||||
|
|
||||||
public string GetResult()
|
public string GetResult()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,13 @@ public static partial class AcJsonSerializer
|
||||||
|
|
||||||
var type = value.GetType();
|
var type = value.GetType();
|
||||||
if (IsPrimitiveOrStringFast(type)) return;
|
if (IsPrimitiveOrStringFast(type)) return;
|
||||||
if (!context.TrackForScanning(value)) return;
|
|
||||||
|
// Get wrapper early to use IId-aware tracking
|
||||||
|
var wrapper = context.GetWrapper(type);
|
||||||
|
var metadata = wrapper.Metadata;
|
||||||
|
|
||||||
|
// Use IId-aware scanning if available
|
||||||
|
if (!context.TrackForScanningWithIId(value, metadata)) return;
|
||||||
|
|
||||||
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
|
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
|
||||||
{
|
{
|
||||||
|
|
@ -139,8 +145,6 @@ public static partial class AcJsonSerializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wrapper = context.GetWrapper(type);
|
|
||||||
var metadata = wrapper.Metadata;
|
|
||||||
var props = metadata.Properties;
|
var props = metadata.Properties;
|
||||||
var propCount = props.Length;
|
var propCount = props.Length;
|
||||||
for (var i = 0; i < propCount; i++)
|
for (var i = 0; i < propCount; i++)
|
||||||
|
|
@ -174,8 +178,11 @@ public static partial class AcJsonSerializer
|
||||||
private static void WriteObject(object value, in Type type, JsonSerializationContext context, int depth)
|
private static void WriteObject(object value, in Type type, JsonSerializationContext context, int depth)
|
||||||
{
|
{
|
||||||
var writer = context.Writer;
|
var writer = context.Writer;
|
||||||
|
var wrapper = context.GetWrapper(type);
|
||||||
|
var metadata = wrapper.Metadata;
|
||||||
|
|
||||||
if (context.UseReferenceHandling && context.TryGetExistingRef(value, out var refId))
|
// Use IId-aware reference handling
|
||||||
|
if (context.UseReferenceHandling && context.TryGetExistingRefForIId(value, metadata, out var refId))
|
||||||
{
|
{
|
||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WriteString(RefPropertyEncoded, refId.ToString(CultureInfo.InvariantCulture));
|
writer.WriteString(RefPropertyEncoded, refId.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
@ -185,14 +192,12 @@ public static partial class AcJsonSerializer
|
||||||
|
|
||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
|
|
||||||
if (context.UseReferenceHandling && context.ShouldWriteId(value, out var id))
|
if (context.UseReferenceHandling && context.ShouldWriteIdForIId(value, metadata, out var id))
|
||||||
{
|
{
|
||||||
writer.WriteString(IdPropertyEncoded, id.ToString(CultureInfo.InvariantCulture));
|
writer.WriteString(IdPropertyEncoded, id.ToString(CultureInfo.InvariantCulture));
|
||||||
context.MarkAsWritten(value, id);
|
context.MarkAsWrittenForIId(value, metadata, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wrapper = context.GetWrapper(type);
|
|
||||||
var metadata = wrapper.Metadata;
|
|
||||||
var props = metadata.Properties;
|
var props = metadata.Properties;
|
||||||
var propCount = props.Length;
|
var propCount = props.Length;
|
||||||
var nextDepth = depth + 1;
|
var nextDepth = depth + 1;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue