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]
|
||||
public void Deserialize_GenericEnum_DirectPath()
|
||||
{
|
||||
var json = "2";
|
||||
var json = "20";
|
||||
var result = AcJsonDeserializer.Deserialize<TestStatus>(json);
|
||||
Assert.AreEqual(TestStatus.Processing, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,4 +127,59 @@ public class AcBinarySerializerObjectTests
|
|||
Assert.AreEqual(20, target.Child.Id);
|
||||
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.Resolvers;
|
||||
|
||||
namespace AyCode.Core.Tests.serialization;
|
||||
namespace AyCode.Core.Tests.Serialization;
|
||||
|
||||
[TestClass]
|
||||
public class QuickBenchmark
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using AyCode.Core.Interfaces;
|
||||
|
||||
namespace AyCode.Core.Tests.TestModels;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ namespace AyCode.Core.Tests.TestModels;
|
|||
/// </summary>
|
||||
public enum TestStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Active = 1,
|
||||
Processing = 2,
|
||||
Completed = 3,
|
||||
Shipped = 4,
|
||||
OnHold = 5
|
||||
Pending = 5,
|
||||
Active = 10,
|
||||
Processing = 20,
|
||||
Completed = 30,
|
||||
Shipped = 40,
|
||||
OnHold = 50
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ public static class AcSerializerCommon
|
|||
|
||||
/// <summary>
|
||||
/// Creates a compiled setter for a property using expression trees.
|
||||
/// Handles nullable value types correctly.
|
||||
/// Handles nullable value types correctly, including null values.
|
||||
/// </summary>
|
||||
public static Action<object, object?> CreateCompiledSetter(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
|
|
@ -496,9 +496,13 @@ public static class AcSerializerCommon
|
|||
|
||||
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);
|
||||
castValue = LExpression.Convert(unboxed, propertyType);
|
||||
var converted = LExpression.Convert(unboxed, propertyType);
|
||||
castValue = LExpression.Condition(nullCheck, nullValue, converted);
|
||||
}
|
||||
else if (propertyType.IsValueType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AyCode.Core.Helpers;
|
||||
using static AyCode.Core.Helpers.JsonUtilities;
|
||||
|
|
@ -639,21 +642,24 @@ public static partial class AcBinaryDeserializer
|
|||
case PropertyAccessorType.Guid:
|
||||
propInfo.SetGuid(target, default);
|
||||
return;
|
||||
case PropertyAccessorType.Enum:
|
||||
propInfo.SetEnumAsInt32(target, 0);
|
||||
return;
|
||||
case PropertyAccessorType.Object:
|
||||
default:
|
||||
// For collections: clear instead of setting to null
|
||||
if (propInfo.IsCollection)
|
||||
{
|
||||
var collection = propInfo.GetValue(target);
|
||||
if (collection is IList list)
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
// If not IList or null, leave it as-is (readonly collection or null already)
|
||||
return;
|
||||
}
|
||||
//if (propInfo.IsCollection)
|
||||
//{
|
||||
// var collection = propInfo.GetValue(target);
|
||||
// if (collection is IList list)
|
||||
// {
|
||||
// list.Clear();
|
||||
// }
|
||||
// // If not IList or null, leave it as-is (readonly collection or null already)
|
||||
// return;
|
||||
//}
|
||||
|
||||
// For other objects: set to null (strings, nullable types, etc.)
|
||||
// Reference types and nullable value types: set to null
|
||||
propInfo.SetValue(target, null);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1002,7 +1002,10 @@ public static partial class AcBinarySerializer
|
|||
{
|
||||
// Object type - use regular getter
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,7 +215,14 @@ internal static class BinaryTypeCode
|
|||
public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255)
|
||||
|
||||
// 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>
|
||||
/// Check if type code represents a reference (string or object).
|
||||
|
|
|
|||
Loading…
Reference in New Issue