Update enum values, PropertySkip code, and add int tests
- Changed TestStatus enum to use non-sequential values (5, 10, 20, ...) - Updated related tests and deserialization logic for new enum values - Changed PropertySkip marker from 253 to 191 to avoid TinyInt conflicts - PropertySkip now only written for null references, not empty values - Improved handling of skipped enum and nullable properties in deserializer - Enhanced compiled property setter for nullable types - Added comprehensive int serialization tests, including edge cases - Fixed namespace casing and added missing using directive
This commit is contained in:
parent
65a1d25586
commit
fd3487c12b
|
|
@ -1114,7 +1114,7 @@ public sealed class JsonExtensionTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Deserialize_GenericEnum_DirectPath()
|
public void Deserialize_GenericEnum_DirectPath()
|
||||||
{
|
{
|
||||||
var json = "2";
|
var json = "20";
|
||||||
var result = AcJsonDeserializer.Deserialize<TestStatus>(json);
|
var result = AcJsonDeserializer.Deserialize<TestStatus>(json);
|
||||||
Assert.AreEqual(TestStatus.Processing, result);
|
Assert.AreEqual(TestStatus.Processing, result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,4 +127,59 @@ public class AcBinarySerializerObjectTests
|
||||||
Assert.AreEqual(20, target.Child.Id);
|
Assert.AreEqual(20, target.Child.Id);
|
||||||
Assert.AreEqual("UpdatedChild", target.Child.Name);
|
Assert.AreEqual("UpdatedChild", target.Child.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Serialize_SimpleId45_RoundTrip()
|
||||||
|
{
|
||||||
|
// Simple test to debug Id=45 serialization issue
|
||||||
|
var item = new TestHighReuseDto { Id = 45, CategoryCode = "test"};
|
||||||
|
|
||||||
|
var binary = item.ToBinary();
|
||||||
|
var result = binary.BinaryTo<TestHighReuseDto>();
|
||||||
|
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(45, result.Id, "Id should be 45 after deserialization");
|
||||||
|
Assert.AreEqual("test", result.CategoryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Serialize_SimpleId0_RoundTrip()
|
||||||
|
{
|
||||||
|
// Test with Id=0 to see if SKIP marker is used correctly
|
||||||
|
var item = new TestHighReuseDto { Id = 0 };
|
||||||
|
|
||||||
|
var binary = item.ToBinary();
|
||||||
|
var result = binary.BinaryTo<TestHighReuseDto>();
|
||||||
|
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Id, "Id should be 0 after deserialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(-16, DisplayName = "TinyInt min: -16")]
|
||||||
|
[DataRow(-1, DisplayName = "Negative: -1")]
|
||||||
|
[DataRow(0, DisplayName = "Zero: 0")]
|
||||||
|
[DataRow(1, DisplayName = "Small: 1")]
|
||||||
|
[DataRow(45, DisplayName = "Was buggy: 45")]
|
||||||
|
[DataRow(47, DisplayName = "TinyInt max: 47")]
|
||||||
|
[DataRow(48, DisplayName = "Above TinyInt: 48")]
|
||||||
|
[DataRow(66, DisplayName = "Was PropertySkip v2: 66")]
|
||||||
|
[DataRow(100, DisplayName = "Medium: 100")]
|
||||||
|
[DataRow(191, DisplayName = "Current PropertySkip value: 191")]
|
||||||
|
[DataRow(192, DisplayName = "TinyInt code start: 192")]
|
||||||
|
[DataRow(253, DisplayName = "Was PropertySkip v1: 253")]
|
||||||
|
[DataRow(255, DisplayName = "Max byte: 255")]
|
||||||
|
[DataRow(1000, DisplayName = "Large: 1000")]
|
||||||
|
[DataRow(int.MaxValue, DisplayName = "MaxValue")]
|
||||||
|
[DataRow(int.MinValue, DisplayName = "MinValue")]
|
||||||
|
public void Serialize_VariousIntIds_PropertySkipTest(int id)
|
||||||
|
{
|
||||||
|
var item = new TestHighReuseDto { Id = id, CategoryCode = "test" };
|
||||||
|
|
||||||
|
var binary = item.ToBinary();
|
||||||
|
var result = binary.BinaryTo<TestHighReuseDto>();
|
||||||
|
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(id, result.Id, $"Id should be {id} after deserialization");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using AyCode.Core.Tests.TestModels;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using MessagePack.Resolvers;
|
using MessagePack.Resolvers;
|
||||||
|
|
||||||
namespace AyCode.Core.Tests.serialization;
|
namespace AyCode.Core.Tests.Serialization;
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class QuickBenchmark
|
public class QuickBenchmark
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using AyCode.Core.Interfaces;
|
||||||
|
|
||||||
namespace AyCode.Core.Tests.TestModels;
|
namespace AyCode.Core.Tests.TestModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ namespace AyCode.Core.Tests.TestModels;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum TestStatus
|
public enum TestStatus
|
||||||
{
|
{
|
||||||
Pending = 0,
|
Pending = 5,
|
||||||
Active = 1,
|
Active = 10,
|
||||||
Processing = 2,
|
Processing = 20,
|
||||||
Completed = 3,
|
Completed = 30,
|
||||||
Shipped = 4,
|
Shipped = 40,
|
||||||
OnHold = 5
|
OnHold = 50
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ public static class AcSerializerCommon
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a compiled setter for a property using expression trees.
|
/// Creates a compiled setter for a property using expression trees.
|
||||||
/// Handles nullable value types correctly.
|
/// Handles nullable value types correctly, including null values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Action<object, object?> CreateCompiledSetter(Type declaringType, PropertyInfo prop)
|
public static Action<object, object?> CreateCompiledSetter(Type declaringType, PropertyInfo prop)
|
||||||
{
|
{
|
||||||
|
|
@ -496,9 +496,13 @@ public static class AcSerializerCommon
|
||||||
|
|
||||||
if (underlyingType != null)
|
if (underlyingType != null)
|
||||||
{
|
{
|
||||||
// Nullable value type: unbox to underlying type, then convert to nullable
|
// Nullable value type: handle null case with conditional
|
||||||
|
// if (value == null) property = default; else property = (T?)Unbox(value)
|
||||||
|
var nullCheck = LExpression.Equal(valueParam, LExpression.Constant(null, typeof(object)));
|
||||||
|
var nullValue = LExpression.Default(propertyType);
|
||||||
var unboxed = LExpression.Unbox(valueParam, underlyingType);
|
var unboxed = LExpression.Unbox(valueParam, underlyingType);
|
||||||
castValue = LExpression.Convert(unboxed, propertyType);
|
var converted = LExpression.Convert(unboxed, propertyType);
|
||||||
|
castValue = LExpression.Condition(nullCheck, nullValue, converted);
|
||||||
}
|
}
|
||||||
else if (propertyType.IsValueType)
|
else if (propertyType.IsValueType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using static AyCode.Core.Helpers.JsonUtilities;
|
using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
@ -639,21 +642,24 @@ public static partial class AcBinaryDeserializer
|
||||||
case PropertyAccessorType.Guid:
|
case PropertyAccessorType.Guid:
|
||||||
propInfo.SetGuid(target, default);
|
propInfo.SetGuid(target, default);
|
||||||
return;
|
return;
|
||||||
|
case PropertyAccessorType.Enum:
|
||||||
|
propInfo.SetEnumAsInt32(target, 0);
|
||||||
|
return;
|
||||||
case PropertyAccessorType.Object:
|
case PropertyAccessorType.Object:
|
||||||
default:
|
default:
|
||||||
// For collections: clear instead of setting to null
|
// For collections: clear instead of setting to null
|
||||||
if (propInfo.IsCollection)
|
//if (propInfo.IsCollection)
|
||||||
{
|
//{
|
||||||
var collection = propInfo.GetValue(target);
|
// var collection = propInfo.GetValue(target);
|
||||||
if (collection is IList list)
|
// if (collection is IList list)
|
||||||
{
|
// {
|
||||||
list.Clear();
|
// list.Clear();
|
||||||
}
|
// }
|
||||||
// If not IList or null, leave it as-is (readonly collection or null already)
|
// // If not IList or null, leave it as-is (readonly collection or null already)
|
||||||
return;
|
// return;
|
||||||
}
|
//}
|
||||||
|
|
||||||
// For other objects: set to null (strings, nullable types, etc.)
|
// Reference types and nullable value types: set to null
|
||||||
propInfo.SetValue(target, null);
|
propInfo.SetValue(target, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1002,7 +1002,10 @@ public static partial class AcBinarySerializer
|
||||||
{
|
{
|
||||||
// Object type - use regular getter
|
// Object type - use regular getter
|
||||||
var value = prop.GetValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
if (value == null || (prop.PropertyTypeCode == TypeCode.String && string.IsNullOrEmpty((string)value)))
|
|
||||||
|
// SKIP marker only for null (reference types)
|
||||||
|
// Empty string, empty collections, etc. are valid values and must be written!
|
||||||
|
if (value == null)
|
||||||
{
|
{
|
||||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,14 @@ internal static class BinaryTypeCode
|
||||||
public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255)
|
public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255)
|
||||||
|
|
||||||
// Property skip marker (for single-pass serialization optimization)
|
// Property skip marker (for single-pass serialization optimization)
|
||||||
public const byte PropertySkip = 253; // Marks a property with default/null value (skipped during serialization)
|
// CRITICAL: Must be in the "reserved" range 67-191 (after FixStr, before TinyInt)
|
||||||
|
// AND must not conflict with any other type codes.
|
||||||
|
// Using 191 (0xBF) - the highest value before TinyInt range starts at 192.
|
||||||
|
// This ensures it won't be confused with:
|
||||||
|
// - Primitive types (0-31)
|
||||||
|
// - FixStr (34-65)
|
||||||
|
// - TinyInt values (192-255)
|
||||||
|
public const byte PropertySkip = 191; // Marks a property with default/null value (skipped during serialization)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if type code represents a reference (string or object).
|
/// Check if type code represents a reference (string or object).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue