diff --git a/AyCode.Core.sln b/AyCode.Core.sln
index 08722dd..2ffa18a 100644
--- a/AyCode.Core.sln
+++ b/AyCode.Core.sln
@@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
AyCode.Core.targets = AyCode.Core.targets
RunQuickBenchmark.bat = RunQuickBenchmark.bat
RunQuickBenchmark.ps1 = RunQuickBenchmark.ps1
+ ToonExtendedInfo.txt = ToonExtendedInfo.txt
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Benchmark", "AyCode.Benchmark\AyCode.Benchmark.csproj", "{A20861A9-411E-6150-BF5C-69E8196E5D22}"
diff --git a/AyCode.Core/AyCode.Core.csproj b/AyCode.Core/AyCode.Core.csproj
index 0e81e47..9d77902 100644
--- a/AyCode.Core/AyCode.Core.csproj
+++ b/AyCode.Core/AyCode.Core.csproj
@@ -18,4 +18,5 @@
+
diff --git a/AyCode.Core/Compression/BrotliHelper.cs b/AyCode.Core/Compression/BrotliHelper.cs
index 2a0dd0b..ea6b2c3 100644
--- a/AyCode.Core/Compression/BrotliHelper.cs
+++ b/AyCode.Core/Compression/BrotliHelper.cs
@@ -1,3 +1,4 @@
+using AyCode.Core.Serializers.Toons;
using System.Buffers;
using System.IO.Compression;
using System.Runtime.CompilerServices;
@@ -11,6 +12,7 @@ namespace AyCode.Core.Compression;
///
public static class BrotliHelper
{
+ //[ToonDescription("Unique identifier for the person")]
private const int DefaultBufferSize = 4096;
private const int MaxStackAllocSize = 1024;
diff --git a/AyCode.Core/Loggers/AcLoggerBase.cs b/AyCode.Core/Loggers/AcLoggerBase.cs
index 1a9e10e..17f0dbe 100644
--- a/AyCode.Core/Loggers/AcLoggerBase.cs
+++ b/AyCode.Core/Loggers/AcLoggerBase.cs
@@ -200,31 +200,35 @@ public abstract class AcLoggerBase : IAcLoggerBase
if (string.IsNullOrEmpty(message) && exception == null)
return;
- var fullMessage = eventId.Id != 0
- ? $"[{eventId.Id}:{eventId.Name}] {message}"
- : message;
+ //var fullMessage = eventId.Id != 0
+ // ? $"[{eventId.Id}:{eventId.Name}] {message}"
+ // : message;
+ var fullMessage = message;
var category = ShortenCategoryNames ? GetShortCategoryName(CategoryName) : CategoryName;
+
+ // Use eventId.Name as memberName if available, otherwise null (will show as empty, not "Log")
+ var memberName = !string.IsNullOrEmpty(eventId.Name) ? $"{eventId.Name}:{eventId.Id}" : "Log";
switch (logLevel)
{
case MsLogLevel.Trace:
- Detail(fullMessage, category);
+ Detail(fullMessage, category, memberName);
break;
case MsLogLevel.Debug:
- Debug(fullMessage, category);
+ Debug(fullMessage, category, memberName);
break;
case MsLogLevel.Information:
- Info(fullMessage, category);
+ Info(fullMessage, category, memberName);
break;
case MsLogLevel.Warning:
- Warning(fullMessage, category);
+ Warning(fullMessage, category, memberName);
break;
case MsLogLevel.Error:
- Error(fullMessage, exception, category);
+ Error(fullMessage, exception, category, memberName);
break;
case MsLogLevel.Critical:
- Error($"[CRITICAL] {fullMessage}", exception, category);
+ Error($"[CRITICAL] {fullMessage}", exception, category, memberName);
break;
case MsLogLevel.None:
default:
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
index e3b3efa..188e36f 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
@@ -1,7 +1,10 @@
+using System;
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializerOptions.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializerOptions.cs
index fa951a1..0addd5a 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonSerializerOptions.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializerOptions.cs
@@ -4,6 +4,7 @@ public enum AcSerializerType : byte
{
Json = 0,
Binary = 1,
+ Toon = 2,
}
public abstract class AcSerializerOptions
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.AttributeExtraction.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.AttributeExtraction.cs
new file mode 100644
index 0000000..4a00c4e
--- /dev/null
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.AttributeExtraction.cs
@@ -0,0 +1,442 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace AyCode.Core.Serializers.Toons;
+
+public static partial class AcToonSerializer
+{
+ ///
+ /// Extract constraints from C# type system (value types, nullables, numeric ranges).
+ ///
+ private static string ExtractTypeConstraints(Type type)
+ {
+ var constraints = new List();
+
+ var underlyingType = Nullable.GetUnderlyingType(type);
+ var isNullable = underlyingType != null || !type.IsValueType;
+
+ // Nullable vs Required
+ if (isNullable)
+ constraints.Add("nullable");
+ else
+ constraints.Add("required");
+
+ var baseType = underlyingType ?? type;
+
+ // Numeric type ranges
+ var typeCode = Type.GetTypeCode(baseType);
+ switch (typeCode)
+ {
+ case TypeCode.Byte:
+ constraints.Add("range: 0-255");
+ break;
+ case TypeCode.SByte:
+ constraints.Add("range: -128-127");
+ break;
+ case TypeCode.Int16:
+ constraints.Add("range: -32768-32767");
+ break;
+ case TypeCode.UInt16:
+ constraints.Add("range: 0-65535");
+ break;
+ case TypeCode.Int32:
+ constraints.Add("numeric");
+ break;
+ case TypeCode.UInt32:
+ constraints.Add("range: 0-4294967295");
+ break;
+ case TypeCode.Int64:
+ constraints.Add("numeric");
+ break;
+ case TypeCode.UInt64:
+ constraints.Add("numeric, non-negative");
+ break;
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ constraints.Add("numeric");
+ break;
+ case TypeCode.Boolean:
+ constraints.Add("boolean: true|false");
+ break;
+ }
+
+ // Enum values
+ if (baseType.IsEnum)
+ {
+ var values = string.Join("|", Enum.GetNames(baseType).Take(5)); // Limit to 5 values
+ var more = Enum.GetNames(baseType).Length > 5 ? "..." : "";
+ constraints.Add($"enum: {values}{more}");
+ }
+
+ return string.Join(", ", constraints);
+ }
+
+ ///
+ /// Extract constraints from Microsoft DataAnnotations attributes.
+ ///
+ private static string ExtractDataAnnotationConstraints(PropertyInfo prop)
+ {
+ var constraints = new List();
+
+ // [Required]
+ if (prop.GetCustomAttribute() != null)
+ constraints.Add("required");
+
+ // [Range]
+ var range = prop.GetCustomAttribute();
+ if (range != null)
+ constraints.Add($"range: {range.Minimum}-{range.Maximum}");
+
+ // [MaxLength]
+ var maxLen = prop.GetCustomAttribute();
+ if (maxLen != null)
+ constraints.Add($"max-length: {maxLen.Length}");
+
+ // [MinLength]
+ var minLen = prop.GetCustomAttribute();
+ if (minLen != null)
+ constraints.Add($"min-length: {minLen.Length}");
+
+ // [StringLength]
+ var strLen = prop.GetCustomAttribute();
+ if (strLen != null)
+ {
+ if (strLen.MinimumLength > 0)
+ constraints.Add($"length: {strLen.MinimumLength}-{strLen.MaximumLength}");
+ else
+ constraints.Add($"max-length: {strLen.MaximumLength}");
+ }
+
+ // [EmailAddress]
+ if (prop.GetCustomAttribute() != null)
+ constraints.Add("email-format");
+
+ // [Phone]
+ if (prop.GetCustomAttribute() != null)
+ constraints.Add("phone-format");
+
+ // [Url]
+ if (prop.GetCustomAttribute() != null)
+ constraints.Add("url-format");
+
+ // [CreditCard]
+ if (prop.GetCustomAttribute() != null)
+ constraints.Add("credit-card-format");
+
+ // [RegularExpression]
+ var regex = prop.GetCustomAttribute();
+ if (regex != null)
+ constraints.Add($"pattern: {regex.Pattern}");
+
+ return string.Join(", ", constraints);
+ }
+
+ ///
+ /// Merge constraints with priority: custom > ms > inferred > type.
+ /// Handles deduplication and cleanup.
+ ///
+ private static string MergeConstraints(
+ string? typeConstraints,
+ string? msConstraints,
+ string? inferredConstraints,
+ string? customConstraints)
+ {
+ var all = new HashSet();
+
+ // Add in reverse priority order (lower priority first)
+ AddConstraintsToSet(all, typeConstraints);
+ AddConstraintsToSet(all, inferredConstraints);
+ AddConstraintsToSet(all, msConstraints);
+ AddConstraintsToSet(all, customConstraints);
+
+ return string.Join(", ", all.OrderBy(GetConstraintPriority));
+ }
+
+ private static void AddConstraintsToSet(HashSet set, string? constraints)
+ {
+ if (string.IsNullOrWhiteSpace(constraints)) return;
+
+ foreach (var c in constraints.Split(',').Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)))
+ {
+ // Remove duplicates based on constraint type
+ var constraintType = c.Split(':')[0].Trim();
+
+ // Remove existing constraints of same type (e.g., old range before adding new range)
+ set.RemoveWhere(x => x.Split(':')[0].Trim().Equals(constraintType, StringComparison.OrdinalIgnoreCase));
+
+ set.Add(c);
+ }
+ }
+
+ private static int GetConstraintPriority(string constraint)
+ {
+ var lower = constraint.ToLowerInvariant();
+
+ // Priority order
+ if (lower.StartsWith("required")) return 1;
+ if (lower.StartsWith("nullable")) return 2;
+ if (lower.StartsWith("range:")) return 3;
+ if (lower.StartsWith("length:")) return 4;
+ if (lower.StartsWith("max-length:")) return 5;
+ if (lower.StartsWith("min-length:")) return 6;
+ if (lower.Contains("-format")) return 7;
+ if (lower.StartsWith("pattern:")) return 8;
+ if (lower.StartsWith("enum:")) return 9;
+
+ return 100; // Other constraints last
+ }
+
+ ///
+ /// Resolve [#...] placeholders in description string.
+ ///
+ private static string ResolveDescriptionPlaceholders(
+ string template,
+ ToonPropertyAccessor prop,
+ Type declaringType)
+ {
+ var result = template;
+
+ // [#Description] → Microsoft [Description]
+ if (result.Contains("[#Description]"))
+ {
+ var msDesc = prop.PropertyInfo.GetCustomAttribute();
+ var value = msDesc?.Description ?? "";
+ result = result.Replace("[#Description]", value);
+ }
+
+ // [#DisplayName] → Microsoft [DisplayName]
+ if (result.Contains("[#DisplayName]"))
+ {
+ var displayName = prop.PropertyInfo.GetCustomAttribute();
+ var value = displayName?.DisplayName ?? prop.Name;
+ result = result.Replace("[#DisplayName]", value);
+ }
+
+ // [#SmartDescription] → Smart inference
+ if (result.Contains("[#SmartDescription]"))
+ {
+ var value = GetPropertyDescription(declaringType, prop.Name, prop.PropertyType);
+ result = result.Replace("[#SmartDescription]", value);
+ }
+
+ return CleanupPlaceholders(result);
+ }
+
+ ///
+ /// Resolve [#...] placeholders in purpose string.
+ ///
+ private static string ResolvePurposePlaceholders(
+ string template,
+ ToonPropertyAccessor prop,
+ Type declaringType)
+ {
+ var result = template;
+
+ // [#SmartPurpose] → Smart inference
+ if (result.Contains("[#SmartPurpose]"))
+ {
+ var value = GetPropertyPurpose(declaringType, prop.Name);
+ result = result.Replace("[#SmartPurpose]", value);
+ }
+
+ return CleanupPlaceholders(result);
+ }
+
+ ///
+ /// Resolve [#...] placeholders in constraints string.
+ ///
+ private static string ResolveConstraintPlaceholders(
+ string template,
+ ToonPropertyAccessor prop)
+ {
+ var result = template;
+
+ // [#Range]
+ if (result.Contains("[#Range]"))
+ {
+ var attr = prop.PropertyInfo.GetCustomAttribute();
+ var value = attr != null ? $"range: {attr.Minimum}-{attr.Maximum}" : "";
+ result = result.Replace("[#Range]", value);
+ }
+
+ // [#Required]
+ if (result.Contains("[#Required]"))
+ {
+ var value = prop.PropertyInfo.GetCustomAttribute() != null ? "required" : "";
+ result = result.Replace("[#Required]", value);
+ }
+
+ // [#MaxLength]
+ if (result.Contains("[#MaxLength]"))
+ {
+ var attr = prop.PropertyInfo.GetCustomAttribute();
+ var value = attr != null ? $"max-length: {attr.Length}" : "";
+ result = result.Replace("[#MaxLength]", value);
+ }
+
+ // [#MinLength]
+ if (result.Contains("[#MinLength]"))
+ {
+ var attr = prop.PropertyInfo.GetCustomAttribute();
+ var value = attr != null ? $"min-length: {attr.Length}" : "";
+ result = result.Replace("[#MinLength]", value);
+ }
+
+ // [#StringLength]
+ if (result.Contains("[#StringLength]"))
+ {
+ var attr = prop.PropertyInfo.GetCustomAttribute();
+ string value = "";
+ if (attr != null)
+ {
+ value = attr.MinimumLength > 0
+ ? $"length: {attr.MinimumLength}-{attr.MaximumLength}"
+ : $"max-length: {attr.MaximumLength}";
+ }
+ result = result.Replace("[#StringLength]", value);
+ }
+
+ // [#EmailAddress]
+ if (result.Contains("[#EmailAddress]"))
+ {
+ var value = prop.PropertyInfo.GetCustomAttribute() != null ? "email-format" : "";
+ result = result.Replace("[#EmailAddress]", value);
+ }
+
+ // [#Phone]
+ if (result.Contains("[#Phone]"))
+ {
+ var value = prop.PropertyInfo.GetCustomAttribute() != null ? "phone-format" : "";
+ result = result.Replace("[#Phone]", value);
+ }
+
+ // [#Url]
+ if (result.Contains("[#Url]"))
+ {
+ var value = prop.PropertyInfo.GetCustomAttribute() != null ? "url-format" : "";
+ result = result.Replace("[#Url]", value);
+ }
+
+ // [#CreditCard]
+ if (result.Contains("[#CreditCard]"))
+ {
+ var value = prop.PropertyInfo.GetCustomAttribute() != null ? "credit-card-format" : "";
+ result = result.Replace("[#CreditCard]", value);
+ }
+
+ // [#RegularExpression]
+ if (result.Contains("[#RegularExpression]"))
+ {
+ var attr = prop.PropertyInfo.GetCustomAttribute();
+ var value = attr != null ? $"pattern: {attr.Pattern}" : "";
+ result = result.Replace("[#RegularExpression]", value);
+ }
+
+ // [#SmartTypeConstraints] → Type-derived constraints
+ if (result.Contains("[#SmartTypeConstraints]"))
+ {
+ var value = ExtractTypeConstraints(prop.PropertyType);
+ result = result.Replace("[#SmartTypeConstraints]", value);
+ }
+
+ // [#SmartInferenceConstraints] → Smart inference
+ if (result.Contains("[#SmartInferenceConstraints]"))
+ {
+ var value = GetInferredConstraints(prop.PropertyType, prop.Name);
+ result = result.Replace("[#SmartInferenceConstraints]", value);
+ }
+
+ return CleanupPlaceholders(result);
+ }
+
+ ///
+ /// Resolve [#...] placeholders in examples string.
+ ///
+ private static string ResolveExamplesPlaceholders(
+ string template,
+ ToonPropertyAccessor prop)
+ {
+ var result = template;
+
+ // [#GeneratedExample] → Generate example based on type
+ if (result.Contains("[#GeneratedExample]"))
+ {
+ var value = GenerateExampleValue(prop.PropertyType);
+ result = result.Replace("[#GeneratedExample]", value);
+ }
+
+ return CleanupPlaceholders(result);
+ }
+
+ ///
+ /// Clean up empty placeholders and formatting artifacts.
+ ///
+ private static string CleanupPlaceholders(string text)
+ {
+ // Remove unresolved placeholders
+ text = Regex.Replace(text, @"\[#\w+\]", "");
+
+ // Remove double commas
+ text = Regex.Replace(text, @",\s*,", ",");
+
+ // Remove leading/trailing commas
+ text = Regex.Replace(text, @"^,\s*", "");
+ text = Regex.Replace(text, @",\s*$", "");
+
+ // Remove multiple spaces
+ text = Regex.Replace(text, @"\s{2,}", " ");
+
+ return text.Trim();
+ }
+
+ ///
+ /// Generate example value for a type.
+ ///
+ private static string GenerateExampleValue(Type type)
+ {
+ var typeCode = Type.GetTypeCode(type);
+ return typeCode switch
+ {
+ TypeCode.Int32 => "42",
+ TypeCode.Int64 => "1000",
+ TypeCode.String => "example",
+ TypeCode.Boolean => "true",
+ TypeCode.DateTime => DateTime.Now.ToString("yyyy-MM-dd"),
+ TypeCode.Decimal => "99.99",
+ TypeCode.Double => "3.14",
+ TypeCode.Single => "2.71",
+ TypeCode.Byte => "255",
+ _ => "value"
+ };
+ }
+
+ ///
+ /// Get inferred constraints based on property name patterns.
+ ///
+ private static string GetInferredConstraints(Type propertyType, string propertyName)
+ {
+ var constraints = new List();
+ var baseType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
+
+ // Email
+ if (baseType == typeof(string) && propertyName.Contains("Email", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("email-format");
+
+ // URL
+ if (baseType == typeof(string) && propertyName.Contains("Url", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("url-format");
+
+ // Age
+ if (IsIntegerType(baseType) && propertyName.Contains("Age", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("range: 0-150");
+
+ // Count (non-negative)
+ if (IsIntegerType(baseType) && propertyName.EndsWith("Count", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("non-negative");
+
+ return string.Join(", ", constraints);
+ }
+}
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs
new file mode 100644
index 0000000..32e0a52
--- /dev/null
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs
@@ -0,0 +1,420 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Text;
+using static AyCode.Core.Helpers.JsonUtilities;
+
+namespace AyCode.Core.Serializers.Toons;
+
+public static partial class AcToonSerializer
+{
+ ///
+ /// Write data section only (for DataOnly mode).
+ ///
+ private static void WriteDataSectionOnly(object value, Type type, ToonSerializationContext context)
+ {
+ WriteDataSection(value, type, context);
+ }
+
+ ///
+ /// Write @data section.
+ ///
+ private static void WriteDataSection(object value, Type type, ToonSerializationContext context)
+ {
+ context.WriteLine("@data {");
+ context.CurrentIndentLevel++;
+
+ WriteValue(value, type, context, 0);
+
+ context.CurrentIndentLevel--;
+ context.WriteLine("}");
+ }
+
+ ///
+ /// Write a value (dispatcher for different types).
+ ///
+ private static void WriteValue(object? value, Type type, ToonSerializationContext context, int depth)
+ {
+ if (value == null)
+ {
+ context.Write("null");
+ return;
+ }
+
+ // Try primitive first
+ if (TryWritePrimitive(value, type, context))
+ return;
+
+ if (depth > context.MaxDepth)
+ {
+ context.Write("null");
+ return;
+ }
+
+ // Check for reference
+ if (context.UseReferenceHandling && context.TryGetExistingRef(value, out var refId))
+ {
+ context.Write($"@ref:{refId}");
+ return;
+ }
+
+ // Handle dictionaries
+ if (value is IDictionary dictionary)
+ {
+ WriteDictionary(dictionary, context, depth);
+ return;
+ }
+
+ // Handle collections/arrays
+ if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
+ {
+ WriteArray(enumerable, type, context, depth);
+ return;
+ }
+
+ // Handle complex objects
+ WriteObject(value, type, context, depth);
+ }
+
+ ///
+ /// Write primitive value inline.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryWritePrimitive(object value, Type type, ToonSerializationContext context)
+ {
+ var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
+ var typeCode = Type.GetTypeCode(underlyingType);
+
+ string valueStr;
+ string? typeHint = null;
+
+ switch (typeCode)
+ {
+ case TypeCode.String:
+ var strValue = (string)value;
+ // Check if multi-line string format should be used
+ if (context.Options.UseMultiLineStrings &&
+ strValue.Length > context.Options.MultiLineStringThreshold)
+ {
+ context.Write(FormatMultiLineString(strValue, context));
+ return true;
+ }
+ valueStr = EscapeString(strValue);
+ typeHint = "string";
+ break;
+ case TypeCode.Int32:
+ valueStr = ((int)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "int32";
+ break;
+ case TypeCode.Int64:
+ valueStr = ((long)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "int64";
+ break;
+ case TypeCode.Boolean:
+ valueStr = (bool)value ? "true" : "false";
+ typeHint = "bool";
+ break;
+ case TypeCode.Double:
+ var d = (double)value;
+ if (double.IsNaN(d) || double.IsInfinity(d))
+ {
+ valueStr = "null";
+ }
+ else
+ {
+ valueStr = d.ToString("G17", CultureInfo.InvariantCulture);
+ typeHint = "float64";
+ }
+ break;
+ case TypeCode.Decimal:
+ valueStr = ((decimal)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "decimal";
+ break;
+ case TypeCode.Single:
+ var f = (float)value;
+ if (float.IsNaN(f) || float.IsInfinity(f))
+ {
+ valueStr = "null";
+ }
+ else
+ {
+ valueStr = f.ToString("G9", CultureInfo.InvariantCulture);
+ typeHint = "float32";
+ }
+ break;
+ case TypeCode.DateTime:
+ valueStr = $"\"{((DateTime)value).ToString("O", CultureInfo.InvariantCulture)}\"";
+ typeHint = "datetime";
+ break;
+ case TypeCode.Byte:
+ valueStr = ((byte)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "byte";
+ break;
+ case TypeCode.Int16:
+ valueStr = ((short)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "int16";
+ break;
+ case TypeCode.UInt16:
+ valueStr = ((ushort)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "uint16";
+ break;
+ case TypeCode.UInt32:
+ valueStr = ((uint)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "uint32";
+ break;
+ case TypeCode.UInt64:
+ valueStr = ((ulong)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "uint64";
+ break;
+ case TypeCode.SByte:
+ valueStr = ((sbyte)value).ToString(CultureInfo.InvariantCulture);
+ typeHint = "sbyte";
+ break;
+ case TypeCode.Char:
+ valueStr = EscapeString(value.ToString()!);
+ typeHint = "char";
+ break;
+ default:
+ // Check special types
+ if (ReferenceEquals(underlyingType, GuidType))
+ {
+ valueStr = $"\"{((Guid)value).ToString("D")}\"";
+ typeHint = "guid";
+ }
+ else if (ReferenceEquals(underlyingType, DateTimeOffsetType))
+ {
+ valueStr = $"\"{((DateTimeOffset)value).ToString("O", CultureInfo.InvariantCulture)}\"";
+ typeHint = "datetimeoffset";
+ }
+ else if (ReferenceEquals(underlyingType, TimeSpanType))
+ {
+ valueStr = $"\"{((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture)}\"";
+ typeHint = "timespan";
+ }
+ else if (underlyingType.IsEnum)
+ {
+ var enumValue = Convert.ToInt32(value);
+ var enumName = Enum.GetName(underlyingType, value);
+ valueStr = enumName != null ? $"\"{enumName}\"" : enumValue.ToString(CultureInfo.InvariantCulture);
+ typeHint = "enum";
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+
+ context.Write(valueStr);
+
+ // Add inline type hint if enabled
+ if (context.Options.UseInlineTypeHints && typeHint != null)
+ {
+ context.Write($" <{typeHint}>");
+ }
+
+ return true;
+ }
+
+ ///
+ /// Write complex object.
+ ///
+ private static void WriteObject(object value, Type type, ToonSerializationContext context, int depth)
+ {
+ var metadata = GetTypeMetadata(type);
+
+ // Write reference ID if this is a multi-referenced object
+ if (context.UseReferenceHandling && context.ShouldWriteRef(value, out var refId))
+ {
+ context.Write($"@{refId} ");
+ context.MarkAsWritten(value, refId);
+ }
+
+ // Write type name if enabled
+ if (context.Options.WriteTypeNames)
+ {
+ context.Write(metadata.ShortTypeName);
+ }
+
+ context.WriteLine(" {");
+ context.CurrentIndentLevel++;
+
+ var nextDepth = depth + 1;
+
+ // Write properties
+ foreach (var prop in metadata.Properties)
+ {
+ var propValue = prop.GetValue(value);
+
+ // Skip null/default values if option is set
+ if (context.Options.OmitDefaultValues && prop.IsDefaultValue(propValue))
+ continue;
+
+ context.WriteIndent();
+ context.Write(prop.Name);
+ context.Write(" = ");
+
+ if (propValue == null)
+ {
+ context.WriteLine("null");
+ }
+ else
+ {
+ WriteValue(propValue, prop.PropertyType, context, nextDepth);
+ context.WriteLine();
+ }
+ }
+
+ context.CurrentIndentLevel--;
+ context.WriteIndent();
+ context.Write("}");
+ }
+
+ ///
+ /// Write array/collection.
+ ///
+ private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context, int depth)
+ {
+ var elementType = GetCollectionElementType(type) ?? typeof(object);
+
+ // Get count if possible
+ var count = 0;
+ if (enumerable is ICollection collection)
+ {
+ count = collection.Count;
+ }
+ else
+ {
+ // Fallback: enumerate to count
+ foreach (var _ in enumerable) count++;
+ }
+
+ // Write array header with optional type hint and count
+ if (context.Options.ShowCollectionCount)
+ {
+ var elementTypeName = GetTypeDisplayName(elementType);
+ context.Write($"<{elementTypeName}[]> (count: {count}) ");
+ }
+
+ context.WriteLine("[");
+ context.CurrentIndentLevel++;
+
+ var nextDepth = depth + 1;
+
+ foreach (var item in enumerable)
+ {
+ context.WriteIndent();
+ WriteValue(item, item?.GetType() ?? elementType, context, nextDepth);
+ context.WriteLine();
+ }
+
+ context.CurrentIndentLevel--;
+ context.WriteIndent();
+ context.Write("]");
+ }
+
+ ///
+ /// Get type display name for a type (used in array hints).
+ ///
+ private static string GetTypeDisplayName(Type type)
+ {
+ var typeCode = Type.GetTypeCode(type);
+ return typeCode switch
+ {
+ TypeCode.Int32 => "int32",
+ TypeCode.Int64 => "int64",
+ TypeCode.Double => "float64",
+ TypeCode.Single => "float32",
+ TypeCode.Decimal => "decimal",
+ TypeCode.Boolean => "bool",
+ TypeCode.String => "string",
+ TypeCode.DateTime => "datetime",
+ TypeCode.Byte => "byte",
+ TypeCode.Int16 => "int16",
+ TypeCode.UInt16 => "uint16",
+ TypeCode.UInt32 => "uint32",
+ TypeCode.UInt64 => "uint64",
+ TypeCode.SByte => "sbyte",
+ TypeCode.Char => "char",
+ _ => type.Name.ToLowerInvariant()
+ };
+ }
+
+ ///
+ /// Format multi-line string with triple-quote syntax.
+ ///
+ private static string FormatMultiLineString(string value, ToonSerializationContext context)
+ {
+ var sb = new StringBuilder();
+ sb.Append("\"\"\"");
+
+ if (context.Options.UseIndentation)
+ {
+ sb.AppendLine();
+
+ // Split into lines and indent each
+ var lines = value.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
+ foreach (var line in lines)
+ {
+ for (var i = 0; i <= context.CurrentIndentLevel; i++)
+ {
+ sb.Append(context.Options.IndentString);
+ }
+ sb.AppendLine(line);
+ }
+
+ // Closing quotes with proper indentation
+ for (var i = 0; i < context.CurrentIndentLevel; i++)
+ {
+ sb.Append(context.Options.IndentString);
+ }
+ sb.Append("\"\"\"");
+ }
+ else
+ {
+ sb.Append(value);
+ sb.Append("\"\"\"");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Write dictionary.
+ ///
+ private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context, int depth)
+ {
+ // Write dictionary header with count
+ if (context.Options.ShowCollectionCount)
+ {
+ context.Write($" (count: {dictionary.Count}) ");
+ }
+
+ context.WriteLine("{");
+ context.CurrentIndentLevel++;
+
+ var nextDepth = depth + 1;
+
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ context.WriteIndent();
+
+ // Write key
+ var keyType = entry.Key?.GetType() ?? typeof(object);
+ WriteValue(entry.Key, keyType, context, nextDepth);
+
+ context.Write(" => ");
+
+ // Write value
+ var valueType = entry.Value?.GetType() ?? typeof(object);
+ WriteValue(entry.Value, valueType, context, nextDepth);
+
+ context.WriteLine();
+ }
+
+ context.CurrentIndentLevel--;
+ context.WriteIndent();
+ context.Write("}");
+ }
+}
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.MetaSection.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.MetaSection.cs
new file mode 100644
index 0000000..8f56683
--- /dev/null
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.MetaSection.cs
@@ -0,0 +1,504 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reflection;
+using static AyCode.Core.Helpers.JsonUtilities;
+
+namespace AyCode.Core.Serializers.Toons;
+
+public static partial class AcToonSerializer
+{
+ ///
+ /// Write meta section only (for MetaOnly mode).
+ ///
+ private static void WriteMetaSectionOnly(Type type, ToonSerializationContext context)
+ {
+ WriteMetaSection(type, context);
+ }
+
+ ///
+ /// Write @meta and @types sections.
+ ///
+ private static void WriteMetaSection(Type type, ToonSerializationContext context)
+ {
+ if (!context.Options.UseMeta) return;
+
+ // @meta header
+ context.WriteLine("@meta {");
+ context.CurrentIndentLevel++;
+ context.WriteProperty("version", $"\"{FormatVersion}\"");
+ context.WriteProperty("format", "\"toon\"");
+
+ // Collect all types that need metadata
+ var typesToDocument = new HashSet();
+ CollectTypes(type, typesToDocument);
+
+ // Write type list
+ context.WriteIndent();
+ context.Write("types = [");
+ var first = true;
+ foreach (var t in typesToDocument)
+ {
+ if (!first) context.Write(", ");
+ context.Write($"\"{t.Name}\"");
+ first = false;
+ }
+ context.WriteLine("]");
+
+ context.CurrentIndentLevel--;
+ context.WriteLine("}");
+ context.WriteLine();
+
+ // @types section with descriptions
+ context.WriteLine("@types {");
+ context.CurrentIndentLevel++;
+
+ foreach (var t in typesToDocument)
+ {
+ WriteTypeDefinition(t, context);
+ }
+
+ context.CurrentIndentLevel--;
+ context.WriteLine("}");
+ }
+
+ ///
+ /// Collect all types that need documentation (recursive).
+ ///
+ private static void CollectTypes(Type type, HashSet types)
+ {
+ if (IsPrimitiveOrStringFast(type)) return;
+
+ var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
+
+ if (!types.Add(underlyingType)) return; // Already processed
+
+ // Handle collections
+ var elementType = GetCollectionElementType(underlyingType);
+ if (elementType != null)
+ {
+ CollectTypes(elementType, types);
+ return;
+ }
+
+ // Handle dictionaries
+ if (IsDictionaryType(underlyingType, out var keyType, out var valueType))
+ {
+ if (keyType != null) CollectTypes(keyType, types);
+ if (valueType != null) CollectTypes(valueType, types);
+ return;
+ }
+
+ // Handle object properties
+ var metadata = GetTypeMetadata(underlyingType);
+ foreach (var prop in metadata.Properties)
+ {
+ CollectTypes(prop.PropertyType, types);
+ }
+ }
+
+ ///
+ /// Write type definition with property descriptions.
+ ///
+ private static void WriteTypeDefinition(Type type, ToonSerializationContext context)
+ {
+ var metadata = GetTypeMetadata(type);
+
+ // Type description with fallback chain and placeholder resolution
+ var typeDescription = GetFinalTypeDescription(type, metadata);
+ context.WriteIndentedLine($"{type.Name}: \"{typeDescription}\"");
+
+ // Type-level purpose (if enhanced metadata is enabled)
+ if (context.Options.UseEnhancedMetadata)
+ {
+ var typePurpose = GetFinalTypePurpose(type, metadata);
+ if (!string.IsNullOrEmpty(typePurpose))
+ {
+ context.CurrentIndentLevel++;
+ context.WriteIndentedLine($"purpose: \"{typePurpose}\"");
+ context.CurrentIndentLevel--;
+ }
+ }
+
+ if (metadata.Properties.Length == 0) return;
+
+ // Property descriptions
+ context.CurrentIndentLevel++;
+ foreach (var prop in metadata.Properties)
+ {
+ // Get final values with fallback chain + placeholder resolution
+ var propDescription = GetFinalPropertyDescription(prop, type);
+ var purpose = GetFinalPropertyPurpose(prop, type);
+ var constraints = GetFinalPropertyConstraints(prop, type);
+ var examples = GetFinalPropertyExamples(prop);
+
+ var typeHint = prop.TypeDisplayName;
+
+ if (context.Options.UseEnhancedMetadata)
+ {
+ // Enhanced format with constraints and purpose
+ context.WriteIndentedLine($"{prop.Name}: {typeHint}");
+ context.CurrentIndentLevel++;
+ context.WriteIndentedLine($"description: \"{propDescription}\"");
+ if (!string.IsNullOrEmpty(purpose))
+ context.WriteIndentedLine($"purpose: \"{purpose}\"");
+ if (!string.IsNullOrEmpty(constraints))
+ context.WriteIndentedLine($"constraints: \"{constraints}\"");
+ if (!string.IsNullOrEmpty(examples))
+ context.WriteIndentedLine($"examples: \"{examples}\"");
+
+ context.CurrentIndentLevel--;
+ }
+ else
+ {
+ // Simple format
+ context.WriteIndentedLine($"{prop.Name}: {typeHint} \"{propDescription}\"");
+ }
+ }
+ context.CurrentIndentLevel--;
+ context.WriteLine();
+ }
+
+ ///
+ /// Get description for a type (can be extended with XML comments or attributes).
+ ///
+ private static string GetTypeDescription(Type type)
+ {
+ // For now, generate simple descriptions
+ // TODO: In the future, this could read XML documentation comments
+
+ if (type.IsEnum)
+ return $"Enum type with values: {string.Join(", ", Enum.GetNames(type))}";
+
+ var metadata = GetTypeMetadata(type);
+ if (metadata.IsCollection)
+ return $"Collection of {metadata.ElementType?.Name ?? "items"}";
+ if (metadata.IsDictionary)
+ return "Dictionary mapping keys to values";
+
+ return $"Object of type {type.Name}";
+ }
+
+ ///
+ /// Get description for a property (can be extended with XML comments or attributes).
+ ///
+ private static string GetPropertyDescription(Type declaringType, string propertyName, Type propertyType)
+ {
+ // Enhanced description based on common patterns
+ var isNullable = Nullable.GetUnderlyingType(propertyType) != null || !propertyType.IsValueType;
+ var baseType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
+
+ // Common property name patterns
+ if (propertyName.Equals("Id", StringComparison.OrdinalIgnoreCase))
+ return $"Unique identifier for {declaringType.Name}";
+ if (propertyName.Equals("Name", StringComparison.OrdinalIgnoreCase))
+ return $"Name of the {declaringType.Name}";
+ if (propertyName.Contains("Email", StringComparison.OrdinalIgnoreCase))
+ return "Email address";
+ if (propertyName.Contains("Phone", StringComparison.OrdinalIgnoreCase))
+ return "Phone number";
+ if (propertyName.Contains("Address", StringComparison.OrdinalIgnoreCase))
+ return "Physical or mailing address";
+ if (propertyName.Contains("Date", StringComparison.OrdinalIgnoreCase) || baseType == typeof(DateTime))
+ return $"Date/time value for {propertyName}";
+ if (propertyName.StartsWith("Is", StringComparison.OrdinalIgnoreCase) && baseType == typeof(bool))
+ return $"Boolean flag indicating {propertyName.Substring(2)}";
+ if (propertyName.StartsWith("Has", StringComparison.OrdinalIgnoreCase) && baseType == typeof(bool))
+ return $"Boolean flag indicating possession of {propertyName.Substring(3)}";
+ if (propertyName.EndsWith("Count", StringComparison.OrdinalIgnoreCase) && IsIntegerType(baseType))
+ return $"Count of {propertyName.Replace("Count", "")}";
+
+ // Collection types
+ if (GetCollectionElementType(propertyType) != null)
+ return $"Collection of {GetCollectionElementType(propertyType)?.Name ?? "items"} for {declaringType.Name}";
+
+ // Dictionary types
+ if (IsDictionaryType(propertyType, out _, out _))
+ return $"Dictionary mapping for {propertyName} in {declaringType.Name}";
+
+ // Default
+ return $"Property {propertyName} of type {baseType.Name}{(isNullable ? " (nullable)" : "")}";
+ }
+
+ ///
+ /// Get property constraints (nullable, required, etc.).
+ ///
+ private static string GetPropertyConstraints(Type propertyType, string propertyName)
+ {
+ var constraints = new List();
+
+ var isNullable = Nullable.GetUnderlyingType(propertyType) != null || !propertyType.IsValueType;
+ if (isNullable)
+ constraints.Add("nullable");
+ else
+ constraints.Add("required");
+
+ var baseType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
+
+ // Type-specific constraints
+ if (baseType == typeof(string))
+ {
+ if (propertyName.Contains("Email", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("email-format");
+ if (propertyName.Contains("Url", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("url-format");
+ }
+
+ if (IsIntegerType(baseType))
+ {
+ if (propertyName.Contains("Age", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("range: 0-150");
+ else if (propertyName.EndsWith("Count", StringComparison.OrdinalIgnoreCase))
+ constraints.Add("non-negative");
+ }
+
+ return constraints.Count > 0 ? string.Join(", ", constraints) : "";
+ }
+
+ ///
+ /// Get property purpose (what it's used for).
+ ///
+ private static string GetPropertyPurpose(Type declaringType, string propertyName)
+ {
+ // Common purposes based on property patterns
+ if (propertyName.Equals("Id", StringComparison.OrdinalIgnoreCase))
+ return "Primary key / unique identification";
+ if (propertyName.Contains("CreatedAt", StringComparison.OrdinalIgnoreCase))
+ return "Timestamp when entity was created";
+ if (propertyName.Contains("UpdatedAt", StringComparison.OrdinalIgnoreCase) ||
+ propertyName.Contains("ModifiedAt", StringComparison.OrdinalIgnoreCase))
+ return "Timestamp of last update";
+ if (propertyName.Contains("DeletedAt", StringComparison.OrdinalIgnoreCase))
+ return "Soft delete timestamp";
+ if (propertyName.StartsWith("Is", StringComparison.OrdinalIgnoreCase))
+ return "Status flag";
+ if (propertyName.Contains("Version", StringComparison.OrdinalIgnoreCase))
+ return "Version tracking / concurrency control";
+
+ return "";
+ }
+
+ ///
+ /// Check if type is an integer type.
+ ///
+ private static bool IsIntegerType(Type type)
+ {
+ var typeCode = Type.GetTypeCode(type);
+ return typeCode is TypeCode.Byte or TypeCode.SByte or TypeCode.Int16 or
+ TypeCode.UInt16 or TypeCode.Int32 or TypeCode.UInt32 or
+ TypeCode.Int64 or TypeCode.UInt64;
+ }
+
+ ///
+ /// Get final property description with fallback chain and placeholder resolution.
+ /// Priority: ToonDescription (with placeholders) > Microsoft [Description] > Smart inference
+ ///
+ private static string GetFinalPropertyDescription(ToonPropertyAccessor prop, Type declaringType)
+ {
+ // 1. ToonDescription.Description (if not empty)
+ var customDesc = prop.CustomDescription?.Description;
+
+ if (!string.IsNullOrWhiteSpace(customDesc))
+ {
+ // Has [#...] placeholder? → Resolve it
+ if (customDesc.Contains("[#"))
+ {
+ return ResolveDescriptionPlaceholders(customDesc, prop, declaringType);
+ }
+ // No placeholder → use as-is
+ return customDesc;
+ }
+
+ // 2. Microsoft [Description] attribute
+ var msDesc = prop.PropertyInfo.GetCustomAttribute();
+ if (msDesc != null && !string.IsNullOrWhiteSpace(msDesc.Description))
+ return msDesc.Description;
+
+ // 3. Smart inference (fallback)
+ return GetPropertyDescription(declaringType, prop.Name, prop.PropertyType);
+ }
+
+ ///
+ /// Get final property purpose with fallback chain and placeholder resolution.
+ /// Priority: ToonDescription.Purpose (with placeholders) > Smart inference
+ ///
+ private static string GetFinalPropertyPurpose(ToonPropertyAccessor prop, Type declaringType)
+ {
+ var customPurpose = prop.CustomDescription?.Purpose;
+
+ if (!string.IsNullOrWhiteSpace(customPurpose))
+ {
+ // Has [#...] placeholder? → Resolve it
+ if (customPurpose.Contains("[#"))
+ {
+ return ResolvePurposePlaceholders(customPurpose, prop, declaringType);
+ }
+ return customPurpose;
+ }
+
+ // Fallback: Smart inference
+ return GetPropertyPurpose(declaringType, prop.Name);
+ }
+
+ ///
+ /// Get final property constraints with fallback chain and placeholder resolution.
+ /// Priority: ToonDescription.Constraints (with placeholders merged) > Microsoft attributes > Type constraints > Smart inference
+ ///
+ private static string GetFinalPropertyConstraints(ToonPropertyAccessor prop, Type declaringType)
+ {
+ var customConstraints = prop.CustomDescription?.Constraints;
+
+ if (!string.IsNullOrWhiteSpace(customConstraints))
+ {
+ // Has [#...] placeholder? → Resolve and merge
+ if (customConstraints.Contains("[#"))
+ {
+ // Resolve placeholders first
+ var resolved = ResolveConstraintPlaceholders(customConstraints, prop);
+
+ // Merge with type/smart constraints if resolved contains content
+ if (!string.IsNullOrWhiteSpace(resolved))
+ {
+ var typeConstraints = ExtractTypeConstraints(prop.PropertyType);
+ var inferredConstraints = GetInferredConstraints(prop.PropertyType, prop.Name);
+
+ return MergeConstraints(typeConstraints, null, inferredConstraints, resolved);
+ }
+ }
+ else
+ {
+ // No placeholder → custom wins (replace mode)
+ return customConstraints;
+ }
+ }
+
+ // No custom constraint → Fallback chain
+ var msConstraints = ExtractDataAnnotationConstraints(prop.PropertyInfo);
+ var typeConstraints2 = ExtractTypeConstraints(prop.PropertyType);
+ var inferredConstraints2 = GetInferredConstraints(prop.PropertyType, prop.Name);
+
+ return MergeConstraints(typeConstraints2, msConstraints, inferredConstraints2, null);
+ }
+
+ ///
+ /// Get final property examples with placeholder resolution.
+ ///
+ private static string GetFinalPropertyExamples(ToonPropertyAccessor prop)
+ {
+ var customExamples = prop.CustomDescription?.Examples;
+
+ if (!string.IsNullOrWhiteSpace(customExamples))
+ {
+ // Has [#...] placeholder? → Resolve it
+ if (customExamples.Contains("[#"))
+ {
+ return ResolveExamplesPlaceholders(customExamples, prop);
+ }
+ return customExamples;
+ }
+
+ // No examples
+ return string.Empty;
+ }
+
+ ///
+ /// Get final type description with fallback chain and placeholder resolution.
+ /// Priority: ToonDescription (with placeholders) > Microsoft [Description] > Smart inference
+ ///
+ private static string GetFinalTypeDescription(Type type, ToonTypeMetadata metadata)
+ {
+ // 1. ToonDescription.Description (if not empty)
+ var customDesc = metadata.CustomDescription?.Description;
+
+ if (!string.IsNullOrWhiteSpace(customDesc))
+ {
+ // Has [#...] placeholder? → Resolve it
+ if (customDesc.Contains("[#"))
+ {
+ return ResolveTypeDescriptionPlaceholders(customDesc, type);
+ }
+ // No placeholder → use as-is
+ return customDesc;
+ }
+
+ // 2. Microsoft [Description] attribute
+ var msDesc = type.GetCustomAttribute();
+ if (msDesc != null && !string.IsNullOrWhiteSpace(msDesc.Description))
+ return msDesc.Description;
+
+ // 3. Smart inference (fallback)
+ return GetTypeDescription(type);
+ }
+
+ ///
+ /// Resolve [#...] placeholders in type description string.
+ ///
+ private static string ResolveTypeDescriptionPlaceholders(string template, Type type)
+ {
+ var result = template;
+
+ // [#Description] → Microsoft [Description]
+ if (result.Contains("[#Description]"))
+ {
+ var msDesc = type.GetCustomAttribute();
+ var value = msDesc?.Description ?? "";
+ result = result.Replace("[#Description]", value);
+ }
+
+ // [#DisplayName] → Microsoft [DisplayName]
+ if (result.Contains("[#DisplayName]"))
+ {
+ var displayName = type.GetCustomAttribute();
+ var value = displayName?.DisplayName ?? type.Name;
+ result = result.Replace("[#DisplayName]", value);
+ }
+
+ // [#SmartDescription] → Smart inference
+ if (result.Contains("[#SmartDescription]"))
+ {
+ var value = GetTypeDescription(type);
+ result = result.Replace("[#SmartDescription]", value);
+ }
+
+ return CleanupPlaceholders(result);
+ }
+
+ ///
+ /// Get final type purpose with fallback chain and placeholder resolution.
+ /// Priority: ToonDescription.Purpose (with placeholders) > Smart inference (empty for classes)
+ ///
+ private static string GetFinalTypePurpose(Type type, ToonTypeMetadata metadata)
+ {
+ var customPurpose = metadata.CustomDescription?.Purpose;
+
+ if (!string.IsNullOrWhiteSpace(customPurpose))
+ {
+ // Has [#...] placeholder? → Resolve it
+ if (customPurpose.Contains("[#"))
+ {
+ return ResolveTypePurposePlaceholders(customPurpose, type);
+ }
+ return customPurpose;
+ }
+
+ // No smart inference for class-level purpose - return empty
+ return string.Empty;
+ }
+
+ ///
+ /// Resolve [#...] placeholders in type purpose string.
+ ///
+ private static string ResolveTypePurposePlaceholders(string template, Type type)
+ {
+ var result = template;
+
+ // [#SmartPurpose] → Would be empty for classes, so just remove it
+ if (result.Contains("[#SmartPurpose]"))
+ {
+ result = result.Replace("[#SmartPurpose]", "");
+ }
+
+ return CleanupPlaceholders(result);
+ }
+}
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs
new file mode 100644
index 0000000..54c6e97
--- /dev/null
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using ReferenceEqualityComparer = AyCode.Core.Serializers.Jsons.ReferenceEqualityComparer;
+
+namespace AyCode.Core.Serializers.Toons;
+
+public static partial class AcToonSerializer
+{
+ #region Context Pool
+
+ private static class ToonSerializationContextPool
+ {
+ private static readonly ConcurrentQueue Pool = new();
+ private const int MaxPoolSize = 16;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ToonSerializationContext Get(AcToonSerializerOptions options)
+ {
+ if (Pool.TryDequeue(out var context))
+ {
+ context.Reset(options);
+ return context;
+ }
+ return new ToonSerializationContext(options);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Return(ToonSerializationContext context)
+ {
+ if (Pool.Count < MaxPoolSize)
+ {
+ context.Clear();
+ Pool.Enqueue(context);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Serialization Context
+
+ ///
+ /// Pooled context for Toon serialization.
+ /// Handles output building, indentation, and reference tracking.
+ ///
+ private sealed class ToonSerializationContext
+ {
+ private readonly StringBuilder _builder;
+ private Dictionary