using AyCode.Core.Extensions; using AyCode.Core.Tests.TestModels; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Reports; using Newtonsoft.Json; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using JsonSerializer = System.Text.Json.JsonSerializer; namespace AyCode.Core.Benchmarks; /// /// Serialization benchmarks comparing AyCode, Newtonsoft.Json, and System.Text.Json. /// Tests small, medium, and large data with and without reference handling. /// [MemoryDiagnoser] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [CategoriesColumn] public class SerializationBenchmarks { // Test data - small, medium, large private TestOrder _smallOrder = null!; private TestOrder _mediumOrder = null!; private TestOrder _largeOrder = null!; // Pre-serialized JSON for deserialization benchmarks private string _smallAyCodeJson = null!; private string _smallAyCodeNoRefJson = null!; private string _smallStjJson = null!; private string _smallStjNoRefJson = null!; private string _smallNewtonsoftJson = null!; private string _mediumAyCodeJson = null!; private string _mediumAyCodeNoRefJson = null!; private string _mediumStjJson = null!; private string _mediumStjNoRefJson = null!; private string _mediumNewtonsoftJson = null!; private string _largeAyCodeJson = null!; private string _largeAyCodeNoRefJson = null!; private string _largeStjJson = null!; private string _largeStjNoRefJson = null!; private string _largeNewtonsoftJson = null!; // STJ options private JsonSerializerOptions _stjWithRefs = null!; private JsonSerializerOptions _stjNoRefs = null!; // AyCode options private AcJsonSerializerOptions _ayCodeWithRefs = null!; private AcJsonSerializerOptions _ayCodeNoRefs = null!; // Newtonsoft settings private JsonSerializerSettings _newtonsoftSettings = null!; [GlobalSetup] public void Setup() { // Small: ~20 objects (1 item × 1 pallet × 2 measurements × 3 points) _smallOrder = TestDataFactory.CreateBenchmarkOrder( itemCount: 1, palletsPerItem: 1, measurementsPerPallet: 2, pointsPerMeasurement: 3); // Medium: ~300 objects (3 items × 2 pallets × 2 measurements × 5 points) _mediumOrder = TestDataFactory.CreateBenchmarkOrder( itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 5); // Large: ~1500 objects (5 items × 4 pallets × 3 measurements × 5 points) _largeOrder = TestDataFactory.CreateBenchmarkOrder( itemCount: 5, palletsPerItem: 4, measurementsPerPallet: 3, pointsPerMeasurement: 5); // STJ options with reference handling _stjWithRefs = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy = null, WriteIndented = false, ReferenceHandler = ReferenceHandler.Preserve, MaxDepth = 256 }; // STJ options without reference handling _stjNoRefs = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy = null, WriteIndented = false, ReferenceHandler = ReferenceHandler.IgnoreCycles, MaxDepth = 256 }; // AyCode options _ayCodeWithRefs = AcJsonSerializerOptions.Default; _ayCodeNoRefs = AcJsonSerializerOptions.WithoutReferenceHandling(); // Newtonsoft settings _newtonsoftSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }; // Pre-serialize for deserialization benchmarks _smallAyCodeJson = AcJsonSerializer.Serialize(_smallOrder, _ayCodeWithRefs); _smallAyCodeNoRefJson = AcJsonSerializer.Serialize(_smallOrder, _ayCodeNoRefs); _smallStjJson = JsonSerializer.Serialize(_smallOrder, _stjWithRefs); _smallStjNoRefJson = JsonSerializer.Serialize(_smallOrder, _stjNoRefs); _smallNewtonsoftJson = JsonConvert.SerializeObject(_smallOrder, _newtonsoftSettings); _mediumAyCodeJson = AcJsonSerializer.Serialize(_mediumOrder, _ayCodeWithRefs); _mediumAyCodeNoRefJson = AcJsonSerializer.Serialize(_mediumOrder, _ayCodeNoRefs); _mediumStjJson = JsonSerializer.Serialize(_mediumOrder, _stjWithRefs); _mediumStjNoRefJson = JsonSerializer.Serialize(_mediumOrder, _stjNoRefs); _mediumNewtonsoftJson = JsonConvert.SerializeObject(_mediumOrder, _newtonsoftSettings); _largeAyCodeJson = AcJsonSerializer.Serialize(_largeOrder, _ayCodeWithRefs); _largeAyCodeNoRefJson = AcJsonSerializer.Serialize(_largeOrder, _ayCodeNoRefs); _largeStjJson = JsonSerializer.Serialize(_largeOrder, _stjWithRefs); _largeStjNoRefJson = JsonSerializer.Serialize(_largeOrder, _stjNoRefs); _largeNewtonsoftJson = JsonConvert.SerializeObject(_largeOrder, _newtonsoftSettings); // Output sizes for comparison Console.WriteLine("=== JSON Size Comparison ==="); Console.WriteLine($"Small: AyCode(refs)={_smallAyCodeJson.Length:N0}, AyCode(noRef)={_smallAyCodeNoRefJson.Length:N0}, STJ(refs)={_smallStjJson.Length:N0}, STJ(noRef)={_smallStjNoRefJson.Length:N0}"); Console.WriteLine($"Medium: AyCode(refs)={_mediumAyCodeJson.Length:N0}, AyCode(noRef)={_mediumAyCodeNoRefJson.Length:N0}, STJ(refs)={_mediumStjJson.Length:N0}, STJ(noRef)={_mediumStjNoRefJson.Length:N0}"); Console.WriteLine($"Large: AyCode(refs)={_largeAyCodeJson.Length:N0}, AyCode(noRef)={_largeAyCodeNoRefJson.Length:N0}, STJ(refs)={_largeStjJson.Length:N0}, STJ(noRef)={_largeStjNoRefJson.Length:N0}"); } #region Serialize Large - With Refs [Benchmark(Description = "AyCode Serialize")] [BenchmarkCategory("Serialize-Large-WithRefs")] public string Serialize_Large_AyCode_WithRefs() => AcJsonSerializer.Serialize(_largeOrder, _ayCodeWithRefs); [Benchmark(Description = "STJ Serialize", Baseline = true)] [BenchmarkCategory("Serialize-Large-WithRefs")] public string Serialize_Large_STJ_WithRefs() => JsonSerializer.Serialize(_largeOrder, _stjWithRefs); #endregion #region Serialize Large - No Refs [Benchmark(Description = "AyCode Serialize")] [BenchmarkCategory("Serialize-Large-NoRefs")] public string Serialize_Large_AyCode_NoRefs() => AcJsonSerializer.Serialize(_largeOrder, _ayCodeNoRefs); [Benchmark(Description = "STJ Serialize", Baseline = true)] [BenchmarkCategory("Serialize-Large-NoRefs")] public string Serialize_Large_STJ_NoRefs() => JsonSerializer.Serialize(_largeOrder, _stjNoRefs); #endregion #region Serialize Medium - With Refs [Benchmark(Description = "AyCode Serialize")] [BenchmarkCategory("Serialize-Medium-WithRefs")] public string Serialize_Medium_AyCode_WithRefs() => AcJsonSerializer.Serialize(_mediumOrder, _ayCodeWithRefs); [Benchmark(Description = "STJ Serialize", Baseline = true)] [BenchmarkCategory("Serialize-Medium-WithRefs")] public string Serialize_Medium_STJ_WithRefs() => JsonSerializer.Serialize(_mediumOrder, _stjWithRefs); #endregion #region Serialize Medium - No Refs [Benchmark(Description = "AyCode Serialize")] [BenchmarkCategory("Serialize-Medium-NoRefs")] public string Serialize_Medium_AyCode_NoRefs() => AcJsonSerializer.Serialize(_mediumOrder, _ayCodeNoRefs); [Benchmark(Description = "STJ Serialize", Baseline = true)] [BenchmarkCategory("Serialize-Medium-NoRefs")] public string Serialize_Medium_STJ_NoRefs() => JsonSerializer.Serialize(_mediumOrder, _stjNoRefs); #endregion #region Small Data Deserialization - With Refs [Benchmark(Description = "AyCode WithRefs")] [BenchmarkCategory("Deserialize-Small-WithRefs")] public TestOrder? Deserialize_Small_AyCode_WithRefs() => AcJsonDeserializer.Deserialize(_smallAyCodeJson, _ayCodeWithRefs); [Benchmark(Description = "STJ WithRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Small-WithRefs")] public TestOrder? Deserialize_Small_STJ_WithRefs() => JsonSerializer.Deserialize(_smallStjJson, _stjWithRefs); #endregion #region Small Data Deserialization - No Refs [Benchmark(Description = "AyCode NoRefs")] [BenchmarkCategory("Deserialize-Small-NoRefs")] public TestOrder? Deserialize_Small_AyCode_NoRefs() => AcJsonDeserializer.Deserialize(_smallAyCodeNoRefJson, _ayCodeNoRefs); [Benchmark(Description = "STJ NoRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Small-NoRefs")] public TestOrder? Deserialize_Small_STJ_NoRefs() => JsonSerializer.Deserialize(_smallStjNoRefJson, _stjNoRefs); #endregion #region Medium Data Deserialization - With Refs [Benchmark(Description = "AyCode WithRefs")] [BenchmarkCategory("Deserialize-Medium-WithRefs")] public TestOrder? Deserialize_Medium_AyCode_WithRefs() => AcJsonDeserializer.Deserialize(_mediumAyCodeJson, _ayCodeWithRefs); [Benchmark(Description = "STJ WithRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Medium-WithRefs")] public TestOrder? Deserialize_Medium_STJ_WithRefs() => JsonSerializer.Deserialize(_mediumStjJson, _stjWithRefs); #endregion #region Medium Data Deserialization - No Refs [Benchmark(Description = "AyCode NoRefs")] [BenchmarkCategory("Deserialize-Medium-NoRefs")] public TestOrder? Deserialize_Medium_AyCode_NoRefs() => AcJsonDeserializer.Deserialize(_mediumAyCodeNoRefJson, _ayCodeNoRefs); [Benchmark(Description = "STJ NoRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Medium-NoRefs")] public TestOrder? Deserialize_Medium_STJ_NoRefs() => JsonSerializer.Deserialize(_mediumStjNoRefJson, _stjNoRefs); #endregion #region Large Data Deserialization - With Refs [Benchmark(Description = "AyCode WithRefs")] [BenchmarkCategory("Deserialize-Large-WithRefs")] public TestOrder? Deserialize_Large_AyCode_WithRefs() => AcJsonDeserializer.Deserialize(_largeAyCodeJson, _ayCodeWithRefs); [Benchmark(Description = "STJ WithRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Large-WithRefs")] public TestOrder? Deserialize_Large_STJ_WithRefs() => JsonSerializer.Deserialize(_largeStjJson, _stjWithRefs); #endregion #region Large Data Deserialization - No Refs [Benchmark(Description = "AyCode NoRefs")] [BenchmarkCategory("Deserialize-Large-NoRefs")] public TestOrder? Deserialize_Large_AyCode_NoRefs() => AcJsonDeserializer.Deserialize(_largeAyCodeNoRefJson, _ayCodeNoRefs); [Benchmark(Description = "STJ NoRefs", Baseline = true)] [BenchmarkCategory("Deserialize-Large-NoRefs")] public TestOrder? Deserialize_Large_STJ_NoRefs() => JsonSerializer.Deserialize(_largeStjNoRefJson, _stjNoRefs); #endregion #region Populate Benchmarks - Small [Benchmark(Description = "AyCode Populate")] [BenchmarkCategory("Populate-Small")] public void Populate_Small_AyCode() { var target = new TestOrder(); AcJsonDeserializer.Populate(_smallAyCodeJson, target); } [Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)] [BenchmarkCategory("Populate-Small")] public void Populate_Small_Newtonsoft() { var target = new TestOrder(); JsonConvert.PopulateObject(_smallNewtonsoftJson, target, _newtonsoftSettings); } #endregion #region Populate Benchmarks - Medium [Benchmark(Description = "AyCode Populate")] [BenchmarkCategory("Populate-Medium")] public void Populate_Medium_AyCode() { var target = new TestOrder(); AcJsonDeserializer.Populate(_mediumAyCodeJson, target); } [Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)] [BenchmarkCategory("Populate-Medium")] public void Populate_Medium_Newtonsoft() { var target = new TestOrder(); JsonConvert.PopulateObject(_mediumNewtonsoftJson, target, _newtonsoftSettings); } #endregion #region Populate Benchmarks - Large [Benchmark(Description = "AyCode Populate")] [BenchmarkCategory("Populate-Large")] public void Populate_Large_AyCode() { var target = new TestOrder(); AcJsonDeserializer.Populate(_largeAyCodeJson, target); } [Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)] [BenchmarkCategory("Populate-Large")] public void Populate_Large_Newtonsoft() { var target = new TestOrder(); JsonConvert.PopulateObject(_largeNewtonsoftJson, target, _newtonsoftSettings); } #endregion }