1540 lines
49 KiB
C#
1540 lines
49 KiB
C#
using System.Runtime.Serialization;
|
|
using AyCode.Core.Enums;
|
|
using AyCode.Core.Extensions;
|
|
using AyCode.Core.Interfaces;
|
|
using AyCode.Core.Loggers;
|
|
using Newtonsoft.Json;
|
|
using AyCode.Core.Tests.TestModels;
|
|
|
|
namespace AyCode.Core.Tests;
|
|
|
|
[TestClass]
|
|
public sealed class JsonExtensionTests
|
|
{
|
|
[TestInitialize]
|
|
public void TestInit()
|
|
{
|
|
TestDataFactory.ResetIdCounter();
|
|
}
|
|
|
|
#region Deep Hierarchy Tests (5 Levels)
|
|
|
|
[TestMethod]
|
|
public void DeepHierarchy_5Levels_MergePreservesAllReferences()
|
|
{
|
|
// Arrange: Create 5-level deep hierarchy
|
|
var order = TestDataFactory.CreateOrder(itemCount: 2, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 3);
|
|
|
|
// Store original references at all levels
|
|
var originalItem = order.Items[0];
|
|
var originalPallet = order.Items[0].Pallets[0];
|
|
var originalMeasurement = order.Items[0].Pallets[0].Measurements[0];
|
|
var originalPoint = order.Items[0].Pallets[0].Measurements[0].Points[0];
|
|
|
|
var updateJson = $@"{{
|
|
""Id"": {order.Id},
|
|
""OrderNumber"": ""ORD-UPDATED"",
|
|
""Items"": [{{
|
|
""Id"": {originalItem.Id},
|
|
""ProductName"": ""Updated-Product"",
|
|
""Pallets"": [{{
|
|
""Id"": {originalPallet.Id},
|
|
""PalletCode"": ""PLT-UPDATED"",
|
|
""Measurements"": [{{
|
|
""Id"": {originalMeasurement.Id},
|
|
""Name"": ""Measurement-UPDATED"",
|
|
""Points"": [{{
|
|
""Id"": {originalPoint.Id},
|
|
""Label"": ""Point-UPDATED"",
|
|
""Value"": 999.99
|
|
}}]
|
|
}}]
|
|
}}]
|
|
}}]
|
|
}}";
|
|
|
|
// Act
|
|
updateJson.JsonTo(order);
|
|
|
|
// Assert: All references preserved
|
|
Assert.AreSame(originalItem, order.Items[0], "Level 2: Item reference must be preserved");
|
|
Assert.AreSame(originalPallet, order.Items[0].Pallets[0], "Level 3: Pallet reference must be preserved");
|
|
Assert.AreSame(originalMeasurement, order.Items[0].Pallets[0].Measurements[0], "Level 4: Measurement reference must be preserved");
|
|
Assert.AreSame(originalPoint, order.Items[0].Pallets[0].Measurements[0].Points[0], "Level 5: Point reference must be preserved");
|
|
|
|
// Assert: Values updated
|
|
Assert.AreEqual("ORD-UPDATED", order.OrderNumber);
|
|
Assert.AreEqual("Updated-Product", order.Items[0].ProductName);
|
|
Assert.AreEqual("PLT-UPDATED", order.Items[0].Pallets[0].PalletCode);
|
|
Assert.AreEqual("Measurement-UPDATED", order.Items[0].Pallets[0].Measurements[0].Name);
|
|
Assert.AreEqual("Point-UPDATED", order.Items[0].Pallets[0].Measurements[0].Points[0].Label);
|
|
Assert.AreEqual(999.99, order.Items[0].Pallets[0].Measurements[0].Points[0].Value);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void DeepHierarchy_5Levels_InsertAndKeepLogic()
|
|
{
|
|
// Arrange
|
|
var order = TestDataFactory.CreateOrder(itemCount: 2, palletsPerItem: 1, measurementsPerPallet: 1, pointsPerMeasurement: 2);
|
|
|
|
var originalItemCount = order.Items.Count;
|
|
var originalItem2 = order.Items[1];
|
|
var existingPointId = order.Items[0].Pallets[0].Measurements[0].Points[0].Id;
|
|
|
|
var updateJson = $@"{{
|
|
""Id"": {order.Id},
|
|
""Items"": [{{
|
|
""Id"": {order.Items[0].Id},
|
|
""Pallets"": [{{
|
|
""Id"": {order.Items[0].Pallets[0].Id},
|
|
""Measurements"": [{{
|
|
""Id"": {order.Items[0].Pallets[0].Measurements[0].Id},
|
|
""Points"": [
|
|
{{ ""Id"": {existingPointId}, ""Label"": ""Updated-Point"" }},
|
|
{{ ""Id"": 9999, ""Label"": ""NEW-Point"", ""Value"": 123.45 }}
|
|
]
|
|
}}]
|
|
}}]
|
|
}}]
|
|
}}";
|
|
|
|
// Act
|
|
updateJson.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.AreEqual(originalItemCount, order.Items.Count, "Items count should be preserved (KEEP logic)");
|
|
Assert.AreSame(originalItem2, order.Items[1], "Second item reference should be preserved");
|
|
Assert.IsTrue(order.Items[0].Pallets[0].Measurements[0].Points.Any(p => p.Id == 9999), "New point should be inserted");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Semantic Reference Tests (IId types with $id/$ref)
|
|
|
|
[TestMethod]
|
|
public void SemanticReference_SharedTag_SerializesWithSemanticId()
|
|
{
|
|
// Arrange
|
|
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
|
var order = TestDataFactory.CreateOrder(itemCount: 2, sharedTag: sharedTag);
|
|
|
|
// Act
|
|
var json = order.ToJson();
|
|
Console.WriteLine($"Semantic Reference JSON:\n{json}");
|
|
|
|
// Assert
|
|
Assert.IsTrue(json.Contains("\"$id\""), "Should contain $id for IId types");
|
|
Assert.IsTrue(json.Contains("\"$ref\""), "Should contain $ref for shared references");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SemanticReference_DeserializeAndMerge_PreservesSharedReferences()
|
|
{
|
|
// Arrange
|
|
var sharedTag = TestDataFactory.CreateTag("OriginalKey");
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "ORD-001",
|
|
PrimaryTag = sharedTag,
|
|
SecondaryTag = sharedTag,
|
|
Tags = [sharedTag]
|
|
};
|
|
|
|
var originalTagRef = order.PrimaryTag;
|
|
|
|
var updateJson = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-UPDATED"",
|
|
""PrimaryTag"": { ""Id"": 1, ""Name"": ""UpdatedKey"" }
|
|
}";
|
|
|
|
// Act
|
|
updateJson.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.AreEqual("ORD-UPDATED", order.OrderNumber);
|
|
Assert.AreEqual("UpdatedKey", order.PrimaryTag?.Name);
|
|
Assert.AreSame(originalTagRef, order.SecondaryTag, "SecondaryTag reference should be preserved");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Newtonsoft Reference Tests (Non-IId types)
|
|
|
|
[TestMethod]
|
|
public void NewtonsoftReference_SharedMetadata_SerializesWithNumericId()
|
|
{
|
|
// Arrange
|
|
var sharedMeta = TestDataFactory.CreateMetadata(withChild: true);
|
|
var order = TestDataFactory.CreateOrder(itemCount: 2, sharedMetadata: sharedMeta);
|
|
|
|
// Act
|
|
var json = order.ToJson();
|
|
Console.WriteLine($"Newtonsoft Reference JSON:\n{json}");
|
|
|
|
// Assert
|
|
Assert.IsTrue(json.Contains("\"$id\""), "Should contain $id");
|
|
Assert.IsTrue(json.Contains("\"$ref\""), "Should contain $ref for shared references");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NewtonsoftReference_DeepNestedNonId_HandlesCorrectly()
|
|
{
|
|
// Arrange
|
|
var rootMeta = new MetadataInfo
|
|
{
|
|
Key = "Root",
|
|
Value = "RootValue",
|
|
ChildMetadata = new MetadataInfo
|
|
{
|
|
Key = "Child",
|
|
Value = "ChildValue",
|
|
ChildMetadata = new MetadataInfo { Key = "GrandChild", Value = "GrandChildValue" }
|
|
}
|
|
};
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "ORD-001",
|
|
OrderMetadata = rootMeta,
|
|
AuditMetadata = rootMeta
|
|
};
|
|
|
|
// Act
|
|
var json = order.ToJson();
|
|
|
|
// Assert
|
|
Assert.IsTrue(json.Contains("Root"));
|
|
Assert.IsTrue(json.Contains("Child"));
|
|
Assert.IsTrue(json.Contains("GrandChild"));
|
|
Assert.IsTrue(json.Contains("\"$ref\""), "Should contain $ref for duplicate reference");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Hybrid Reference Tests (Mixed IId and Non-IId)
|
|
|
|
[TestMethod]
|
|
public void HybridReference_MixedTypes_BothRefSystemsWork()
|
|
{
|
|
// Arrange
|
|
var sharedTag = TestDataFactory.CreateTag();
|
|
var sharedMeta = TestDataFactory.CreateMetadata();
|
|
sharedTag.Description = sharedMeta.Key; // Link them
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "ORD-001",
|
|
PrimaryTag = sharedTag,
|
|
SecondaryTag = sharedTag,
|
|
OrderMetadata = sharedMeta,
|
|
AuditMetadata = sharedMeta,
|
|
Tags = [sharedTag],
|
|
Items = [new TestOrderItem { Id = 10, ProductName = "A", Tag = sharedTag, ItemMetadata = sharedMeta }]
|
|
};
|
|
|
|
var json = order.ToJson();
|
|
|
|
// Assert
|
|
var refCount = json.Split("\"$ref\"").Length - 1;
|
|
Assert.IsTrue(refCount >= 2, $"Should have multiple $ref tokens. Found: {refCount}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region NoMerge Collection Tests
|
|
|
|
[TestMethod]
|
|
public void NoMergeCollection_ReplacesEntireCollection()
|
|
{
|
|
// Arrange
|
|
var order = TestDataFactory.CreateOrder(itemCount: 1);
|
|
order.NoMergeItems = [
|
|
new TestOrderItem { Id = 100, ProductName = "NoMerge-A" },
|
|
new TestOrderItem { Id = 101, ProductName = "NoMerge-B" }
|
|
];
|
|
|
|
var originalRef = order.NoMergeItems;
|
|
|
|
var updateJson = $@"{{
|
|
""Id"": {order.Id},
|
|
""NoMergeItems"": [
|
|
{{ ""Id"": 200, ""ProductName"": ""NoMerge-NEW"" }}
|
|
]
|
|
}}";
|
|
|
|
// Act
|
|
order.DeepPopulateWithMerge(updateJson);
|
|
|
|
// Assert
|
|
Assert.AreNotSame(originalRef, order.NoMergeItems);
|
|
Assert.AreEqual(1, order.NoMergeItems.Count);
|
|
Assert.AreEqual(200, order.NoMergeItems[0].Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Non-IId Collection Tests
|
|
|
|
[TestMethod]
|
|
public void NonIdCollection_ReplacesContent()
|
|
{
|
|
// Arrange
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "ORD-001",
|
|
MetadataList = [
|
|
new MetadataInfo { Key = "Old-A" },
|
|
new MetadataInfo { Key = "Old-B" }
|
|
]
|
|
};
|
|
|
|
var updateJson = @"{
|
|
""Id"": 1,
|
|
""MetadataList"": [
|
|
{ ""Key"": ""New-X"" },
|
|
{ ""Key"": ""New-Y"" }
|
|
]
|
|
}";
|
|
|
|
// Act
|
|
order.DeepPopulateWithMerge(updateJson);
|
|
|
|
// Assert
|
|
Assert.AreEqual(2, order.MetadataList.Count);
|
|
Assert.IsTrue(order.MetadataList.Any(m => m.Key == "New-X"));
|
|
Assert.IsFalse(order.MetadataList.Any(m => m.Key == "Old-A"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Guid IId Tests
|
|
|
|
[TestMethod]
|
|
public void GuidId_DeepPopulate_ReferencePreserved()
|
|
{
|
|
var order = new TestGuidOrder
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Code = "ORD-001",
|
|
Items = [
|
|
new TestGuidItem { Id = Guid.NewGuid(), Name = "Apple", Qty = 5 },
|
|
new TestGuidItem { Id = Guid.NewGuid(), Name = "Orange", Qty = 3 }
|
|
]
|
|
};
|
|
|
|
var originalItemsRef = order.Items;
|
|
var originalAppleRef = order.Items[0];
|
|
var appleId = order.Items[0].Id;
|
|
|
|
var json = new {
|
|
Id = order.Id,
|
|
Code = "ORD-UPDATED",
|
|
Items = new[] {
|
|
new { Id = appleId, Name = "Apple", Qty = 7 },
|
|
new { Id = Guid.NewGuid(), Name = "Banana", Qty = 4 }
|
|
}
|
|
}.ToJson();
|
|
|
|
json.JsonTo(order);
|
|
|
|
// List reference preserved
|
|
Assert.AreSame(originalItemsRef, order.Items, "List reference must be preserved");
|
|
// Apple reference preserved (updated in-place)
|
|
Assert.AreSame(originalAppleRef, order.Items[0], "Apple reference must be preserved");
|
|
Assert.AreEqual(7, order.Items[0].Qty, "Apple Qty should be updated");
|
|
// Count: Apple (updated) + Orange (kept) + Banana (new)
|
|
Assert.AreEqual(3, order.Items.Count, "Should have 3 items: Apple updated, Orange kept, Banana added");
|
|
Assert.AreEqual("ORD-UPDATED", order.Code);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Round-Trip Serialization Tests
|
|
|
|
[TestMethod]
|
|
public void RoundTrip_DeepHierarchy_PreservesData()
|
|
{
|
|
// Arrange
|
|
var sharedTag = TestDataFactory.CreateTag();
|
|
var sharedMeta = TestDataFactory.CreateMetadata(withChild: true);
|
|
var order = TestDataFactory.CreateOrder(itemCount: 2, palletsPerItem: 2, sharedTag: sharedTag, sharedMetadata: sharedMeta);
|
|
|
|
// Act
|
|
var json = order.ToJson();
|
|
var deserialized = json.JsonTo<TestOrder>();
|
|
|
|
// Assert
|
|
Assert.IsNotNull(deserialized);
|
|
Assert.AreEqual(order.Id, deserialized.Id);
|
|
Assert.AreEqual(order.OrderNumber, deserialized.OrderNumber);
|
|
Assert.AreEqual(order.Items.Count, deserialized.Items.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Primitive Array Tests (SignalR IdMessage pattern)
|
|
|
|
[TestMethod]
|
|
public void PrimitiveArray_BooleanTrue_RoundTrips()
|
|
{
|
|
var jsonString = (new[] { true }).ToJson();
|
|
|
|
var result = jsonString.JsonTo(typeof(bool[])) as bool[];
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.IsTrue(result[0], "Boolean true should deserialize as true!");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void PrimitiveArray_AllTypes_RoundTrip()
|
|
{
|
|
var testCases = new (Type type, object value)[]
|
|
{
|
|
(typeof(bool), true),
|
|
(typeof(int), 42),
|
|
(typeof(long), 123456789L),
|
|
(typeof(double), 3.14159),
|
|
(typeof(decimal), 99.99m),
|
|
(typeof(string), "test"),
|
|
(typeof(Guid), Guid.NewGuid()),
|
|
(typeof(DateTime), DateTime.UtcNow),
|
|
(typeof(TestStatus), TestStatus.Processing)
|
|
};
|
|
|
|
foreach (var (type, value) in testCases)
|
|
{
|
|
var wrapped = Array.CreateInstance(type, 1);
|
|
wrapped.SetValue(value, 0);
|
|
var json = wrapped.ToJson();
|
|
|
|
var result = json.JsonTo(type.MakeArrayType()) as Array;
|
|
|
|
Assert.IsNotNull(result, $"Failed for {type.Name}");
|
|
Assert.AreEqual(value, result.GetValue(0), $"Value mismatch for {type.Name}");
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IdMessage_MultipleParameters_SimulateSignalR()
|
|
{
|
|
var @params = new (Type t, object v)[] { (typeof(bool), true), (typeof(string), "filter"), (typeof(int), 100) };
|
|
|
|
foreach (var (type, value) in @params)
|
|
{
|
|
var wrapped = Array.CreateInstance(type, 1);
|
|
wrapped.SetValue(value, 0);
|
|
var json = wrapped.ToJson();
|
|
var arr = json.JsonTo(type.MakeArrayType()) as Array;
|
|
Assert.AreEqual(value, arr?.GetValue(0));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WASM Compatibility Tests
|
|
|
|
[TestMethod]
|
|
public void WasmCompat_AcJsonSerializer_SimpleObject()
|
|
{
|
|
var item = new TestOrderItem { Id = 1, ProductName = "Test", Quantity = 10, UnitPrice = 99.99m, Status = TestStatus.Processing };
|
|
|
|
var json = AcJsonSerializer.Serialize(item);
|
|
|
|
Assert.IsTrue(json.Contains("\"Id\":1"));
|
|
Assert.IsTrue(json.Contains("\"ProductName\":\"Test\""));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WasmCompat_AcJsonDeserializer_RoundTrip()
|
|
{
|
|
var original = new TestOrderItem { Id = 42, ProductName = "WASM Test", Quantity = 5, UnitPrice = 25.50m, Status = TestStatus.Shipped };
|
|
var json = AcJsonSerializer.Serialize(original);
|
|
|
|
var deserialized = AcJsonDeserializer.Deserialize<TestOrderItem>(json);
|
|
|
|
Assert.IsNotNull(deserialized);
|
|
Assert.AreEqual(42, deserialized.Id);
|
|
Assert.AreEqual("WASM Test", deserialized.ProductName);
|
|
Assert.AreEqual(TestStatus.Shipped, deserialized.Status);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WasmCompat_AllPrimitiveTypes()
|
|
{
|
|
var testData = TestDataFactory.CreatePrimitiveTestData();
|
|
|
|
var json = AcJsonSerializer.Serialize(testData);
|
|
var deserialized = AcJsonDeserializer.Deserialize<PrimitiveTestClass>(json);
|
|
|
|
Assert.IsNotNull(deserialized);
|
|
Assert.AreEqual(testData.IntValue, deserialized.IntValue);
|
|
Assert.AreEqual(testData.LongValue, deserialized.LongValue);
|
|
Assert.AreEqual(testData.BoolValue, deserialized.BoolValue);
|
|
Assert.AreEqual(testData.StringValue, deserialized.StringValue);
|
|
Assert.AreEqual(testData.GuidValue, deserialized.GuidValue);
|
|
Assert.AreEqual(testData.EnumValue, deserialized.EnumValue);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WasmCompat_EmptyCollections_HandleCorrectly()
|
|
{
|
|
var order = new TestOrder { Id = 1, OrderNumber = "EMPTY-TEST", Items = [], Tags = [] };
|
|
|
|
var json = AcJsonSerializer.Serialize(order);
|
|
|
|
Assert.IsTrue(json.Contains("\"Items\":[]"), "Empty Items should serialize as []");
|
|
Assert.IsTrue(json.Contains("\"Tags\":[]"), "Empty Tags should serialize as []");
|
|
|
|
var deserialized = AcJsonDeserializer.Deserialize<TestOrder>(json);
|
|
|
|
Assert.IsNotNull(deserialized?.Items);
|
|
Assert.AreEqual(0, deserialized.Items.Count);
|
|
Assert.IsNotNull(deserialized?.Tags);
|
|
Assert.AreEqual(0, deserialized.Tags.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_NullCollection_IsOmitted()
|
|
{
|
|
var order = new TestOrderWithNullableCollections { Id = 1, OrderNumber = "TEST", Items = null, Tags = null };
|
|
|
|
var json = AcJsonSerializer.Serialize(order);
|
|
|
|
Assert.IsFalse(json.Contains("\"Items\""), "Null Items should not be serialized");
|
|
Assert.IsFalse(json.Contains("\"Tags\""), "Null Tags should not be serialized");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void WasmCompat_SharedReferences_IdRefResolution()
|
|
{
|
|
var sharedTag = new SharedTag { Id = 999, Name = "SharedKey" };
|
|
var order = new TestOrder { Id = 1, OrderNumber = "REF-TEST", PrimaryTag = sharedTag, SecondaryTag = sharedTag, Tags = [sharedTag] };
|
|
|
|
var json = AcJsonSerializer.Serialize(order);
|
|
|
|
Assert.IsTrue(json.Contains("\"$id\""));
|
|
Assert.IsTrue(json.Contains("\"$ref\""));
|
|
|
|
var nativeSettings = new JsonSerializerSettings
|
|
{
|
|
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
};
|
|
|
|
var deserialized = JsonConvert.DeserializeObject<TestOrder>(json, nativeSettings);
|
|
|
|
Assert.IsNotNull(deserialized);
|
|
Assert.AreSame(deserialized.PrimaryTag, deserialized.SecondaryTag);
|
|
Assert.AreSame(deserialized.PrimaryTag, deserialized.Tags[0]);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cross-Serializer Compatibility Tests
|
|
|
|
[TestMethod]
|
|
public void CrossSerializer_MixedReferences_CompatibleWithNewtonsoft()
|
|
{
|
|
// Arrange
|
|
var sharedTag = new SharedTag { Id = 100, Name = "SharedKey", CreatedAt = DateTime.UtcNow };
|
|
var sharedMeta = new MetadataInfo { Key = "SharedMeta", Value = "MetaValue", ChildMetadata = new MetadataInfo { Key = "Child" } };
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "ORD-001",
|
|
Status = TestStatus.Processing,
|
|
PrimaryTag = sharedTag,
|
|
SecondaryTag = sharedTag,
|
|
OrderMetadata = sharedMeta,
|
|
AuditMetadata = sharedMeta,
|
|
Tags = [sharedTag],
|
|
Items = [new TestOrderItem { Id = 10, ProductName = "Product-A", Tag = sharedTag, ItemMetadata = sharedMeta }]
|
|
};
|
|
|
|
// Act - Serialize with AyCode
|
|
var json = order.ToJson();
|
|
|
|
// Deserialize with native Newtonsoft
|
|
var nativeSettings = new JsonSerializerSettings
|
|
{
|
|
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
};
|
|
|
|
var deserialized = JsonConvert.DeserializeObject<TestOrder>(json, nativeSettings);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(deserialized);
|
|
Assert.AreSame(deserialized.PrimaryTag, deserialized.SecondaryTag);
|
|
Assert.AreSame(deserialized.OrderMetadata, deserialized.AuditMetadata);
|
|
Assert.AreSame(deserialized.PrimaryTag, deserialized.Items[0].Tag);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Populate $ref Handling Tests
|
|
|
|
[TestMethod]
|
|
public void Populate_RefNode_ShouldSetPropertyToReferencedObject()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""SharedTag"" },
|
|
""SecondaryTag"": { ""$ref"": ""1"" }
|
|
}";
|
|
|
|
var order = new TestOrder { Id = 1, OrderNumber = "OLD" };
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag, "PrimaryTag should be set");
|
|
Assert.IsNotNull(order.SecondaryTag, "SecondaryTag should be set from $ref");
|
|
Assert.AreEqual(100, order.PrimaryTag.Id);
|
|
Assert.AreEqual("SharedTag", order.PrimaryTag.Name);
|
|
Assert.AreSame(order.PrimaryTag, order.SecondaryTag,
|
|
"SecondaryTag should reference the same object as PrimaryTag via $ref");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_RefNodeInCollection_ShouldSetPropertyToReferencedObject()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""SharedTag"" },
|
|
""Tags"": [
|
|
{ ""$ref"": ""1"" },
|
|
{ ""$id"": ""2"", ""Id"": 200, ""Name"": ""OtherTag"" }
|
|
]
|
|
}";
|
|
|
|
var order = new TestOrder { Id = 1, OrderNumber = "OLD", Tags = new List<SharedTag>() };
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag, "PrimaryTag should be set");
|
|
Assert.AreEqual(2, order.Tags.Count, "Tags should have 2 items");
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[0],
|
|
"Tags[0] should reference the same object as PrimaryTag via $ref");
|
|
Assert.AreEqual(200, order.Tags[1].Id);
|
|
Assert.AreNotSame(order.PrimaryTag, order.Tags[1]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_NestedRefNode_ShouldResolveCorrectly()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""Items"": [{
|
|
""Id"": 10,
|
|
""ProductName"": ""Product-A"",
|
|
""Tag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""ItemTag"" }
|
|
}],
|
|
""PrimaryTag"": { ""$ref"": ""1"" }
|
|
}";
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
OrderNumber = "OLD",
|
|
Items = new List<TestOrderItem> { new TestOrderItem { Id = 10, ProductName = "OLD" } }
|
|
};
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.Items[0].Tag, "Item's Tag should be set");
|
|
Assert.IsNotNull(order.PrimaryTag, "PrimaryTag should be set from $ref");
|
|
Assert.AreSame(order.Items[0].Tag, order.PrimaryTag,
|
|
"PrimaryTag should reference the same object as Items[0].Tag via $ref");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_ForwardRef_ShouldResolveDeferredReference()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""SecondaryTag"": { ""$ref"": ""1"" },
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""SharedTag"" }
|
|
}";
|
|
|
|
var order = new TestOrder { Id = 1, OrderNumber = "OLD" };
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag, "PrimaryTag should be set");
|
|
Assert.IsNotNull(order.SecondaryTag, "SecondaryTag should be set from forward $ref");
|
|
Assert.AreSame(order.PrimaryTag, order.SecondaryTag,
|
|
"Forward $ref should resolve to the same object");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_MultipleRefsToSameId_AllShouldResolveToSameObject()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""SharedTag"" },
|
|
""SecondaryTag"": { ""$ref"": ""1"" },
|
|
""Tags"": [
|
|
{ ""$ref"": ""1"" },
|
|
{ ""$ref"": ""1"" },
|
|
{ ""$ref"": ""1"" }
|
|
]
|
|
}";
|
|
|
|
var order = new TestOrder { Id = 1, OrderNumber = "OLD", Tags = new List<SharedTag>() };
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag);
|
|
Assert.AreSame(order.PrimaryTag, order.SecondaryTag);
|
|
Assert.AreEqual(3, order.Tags.Count);
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[0]);
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[1]);
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_DeeplyNestedRef_ShouldResolveAcrossLevels()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""Items"": [{
|
|
""Id"": 10,
|
|
""ProductName"": ""Product-A"",
|
|
""Tag"": { ""$id"": ""deep1"", ""Id"": 999, ""Name"": ""DeepTag"" }
|
|
}],
|
|
""PrimaryTag"": { ""$ref"": ""deep1"" }
|
|
}";
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
Items = new List<TestOrderItem> { new TestOrderItem { Id = 10 } }
|
|
};
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
var deepTag = order.Items[0].Tag;
|
|
Assert.IsNotNull(deepTag, "Item's Tag should be set");
|
|
Assert.IsNotNull(order.PrimaryTag, "PrimaryTag should be set from deep $ref");
|
|
Assert.AreSame(deepTag, order.PrimaryTag,
|
|
"Root PrimaryTag should reference the nested Item's Tag via $ref");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_RefInNestedObject_ShouldResolveFromParentContext()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""RootTag"" },
|
|
""Items"": [{
|
|
""Id"": 10,
|
|
""ProductName"": ""Product-A"",
|
|
""Tag"": { ""$ref"": ""1"" }
|
|
}]
|
|
}";
|
|
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
Items = new List<TestOrderItem> { new TestOrderItem { Id = 10 } }
|
|
};
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag);
|
|
Assert.IsNotNull(order.Items[0].Tag);
|
|
Assert.AreSame(order.PrimaryTag, order.Items[0].Tag,
|
|
"Nested Tag should reference root PrimaryTag via $ref");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_RefOnly_ShouldCreateDeferredAndResolve()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""SecondaryTag"": { ""$ref"": ""1"" },
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""Tag"" }
|
|
}";
|
|
|
|
// Act
|
|
var order = AcJsonDeserializer.Deserialize<TestOrder>(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order);
|
|
Assert.IsNotNull(order.PrimaryTag);
|
|
Assert.IsNotNull(order.SecondaryTag);
|
|
Assert.AreSame(order.PrimaryTag, order.SecondaryTag);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_MultipleIdRefs_ComplexGraph()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""OrderNumber"": ""ORD-001"",
|
|
""PrimaryTag"": { ""$id"": ""tag1"", ""Id"": 100, ""Name"": ""Tag1"" },
|
|
""SecondaryTag"": { ""$id"": ""tag2"", ""Id"": 200, ""Name"": ""Tag2"" },
|
|
""Tags"": [
|
|
{ ""$ref"": ""tag1"" },
|
|
{ ""$ref"": ""tag2"" },
|
|
{ ""$ref"": ""tag1"" }
|
|
],
|
|
""Items"": [{
|
|
""Id"": 10,
|
|
""ProductName"": ""Product-A"",
|
|
""Tag"": { ""$ref"": ""tag2"" }
|
|
}]
|
|
}";
|
|
|
|
// Act
|
|
var order = AcJsonDeserializer.Deserialize<TestOrder>(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order);
|
|
Assert.AreEqual(3, order.Tags.Count);
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[0]);
|
|
Assert.AreSame(order.PrimaryTag, order.Tags[2]);
|
|
Assert.AreSame(order.SecondaryTag, order.Tags[1]);
|
|
Assert.AreSame(order.SecondaryTag, order.Items[0].Tag);
|
|
Assert.AreNotSame(order.PrimaryTag, order.SecondaryTag);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_RefWithExistingTarget_ShouldOverwriteWithReference()
|
|
{
|
|
var json = @"{
|
|
""Id"": 1,
|
|
""PrimaryTag"": { ""$id"": ""1"", ""Id"": 100, ""Name"": ""NewTag"" },
|
|
""SecondaryTag"": { ""$ref"": ""1"" }
|
|
}";
|
|
|
|
var existingTag = new SharedTag { Id = 999, Name = "ExistingTag" };
|
|
var order = new TestOrder
|
|
{
|
|
Id = 1,
|
|
SecondaryTag = existingTag
|
|
};
|
|
|
|
// Act
|
|
json.JsonTo(order);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(order.PrimaryTag);
|
|
Assert.AreSame(order.PrimaryTag, order.SecondaryTag,
|
|
"SecondaryTag should be overwritten with $ref reference");
|
|
Assert.AreNotSame(existingTag, order.SecondaryTag,
|
|
"Original SecondaryTag should be replaced");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AcJsonSerializer Complex Object Tests
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithDictionaryProperty_SerializesDictionaryCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
Counts = new Dictionary<string, int>
|
|
{
|
|
{ "apple", 5 },
|
|
{ "banana", 3 }
|
|
}
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"Counts\":{"));
|
|
Assert.IsTrue(json.Contains("\"apple\":5"));
|
|
Assert.IsTrue(json.Contains("\"banana\":3"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithDateTimeOffsetProperty_SerializesCorrectly()
|
|
{
|
|
var dto = new DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.FromHours(2));
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
DateTimeOffsetValue = dto
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"DateTimeOffsetValue\":\"2024-06-15"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithTimeSpanProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
TimeSpanValue = new TimeSpan(2, 30, 45)
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"TimeSpanValue\":\"02:30:45\""));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithNullProperties_SkipsNullProperties()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
NullString = null,
|
|
NullObject = null,
|
|
Counts = null
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsFalse(json.Contains("\"NullString\""));
|
|
Assert.IsFalse(json.Contains("\"NullObject\""));
|
|
Assert.IsFalse(json.Contains("\"Counts\""));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithUIntProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
UIntValue = 4000000000
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"UIntValue\":4000000000"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithULongProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
ULongValue = 18000000000000000000
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"ULongValue\":18000000000000000000"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithSByteProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
SByteValue = -100
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"SByteValue\":-100"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithCharProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
CharValue = 'X'
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"CharValue\":\"X\""));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithUShortProperty_SerializesCorrectly()
|
|
{
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
UShortValue = 60000
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"UShortValue\":60000"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ArrayWithNullItems_SerializesNullCorrectly()
|
|
{
|
|
var obj = new ObjectWithNullItems
|
|
{
|
|
Id = 1,
|
|
MixedItems = [1, null, "text", null, 3]
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("[1,null,\"text\",null,3]"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_DictionaryDirect_SerializesAsObject()
|
|
{
|
|
var dict = new Dictionary<string, object>
|
|
{
|
|
{ "name", "Test" },
|
|
{ "value", 42 }
|
|
};
|
|
|
|
var json = AcJsonSerializer.Serialize(dict);
|
|
|
|
Assert.IsTrue(json.StartsWith("{"));
|
|
Assert.IsTrue(json.Contains("\"name\":\"Test\""));
|
|
Assert.IsTrue(json.Contains("\"value\":42"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ObjectWithGuidProperty_SerializesCorrectly()
|
|
{
|
|
var guid = Guid.NewGuid();
|
|
var obj = new ExtendedPrimitiveTestClass
|
|
{
|
|
Id = 1,
|
|
Name = "Test",
|
|
Tag = new SharedTag { Id = 1, Name = "Tag" }
|
|
};
|
|
// Using existing Tag property with Guid in SharedTag's CreatedAt
|
|
|
|
var json = AcJsonSerializer.Serialize(obj);
|
|
|
|
Assert.IsTrue(json.Contains("\"Tag\":{"));
|
|
Assert.IsTrue(json.Contains("\"Name\":\"Tag\""));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AcJsonDeserializer Extended Tests
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericString_DirectPath()
|
|
{
|
|
var json = "\"Hello World\"";
|
|
var result = AcJsonDeserializer.Deserialize<string>(json);
|
|
Assert.AreEqual("Hello World", result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericInt_DirectPath()
|
|
{
|
|
var json = "42";
|
|
var result = AcJsonDeserializer.Deserialize<int>(json);
|
|
Assert.AreEqual(42, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericBool_DirectPath()
|
|
{
|
|
var json = "true";
|
|
var result = AcJsonDeserializer.Deserialize<bool>(json);
|
|
Assert.IsTrue(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericDouble_DirectPath()
|
|
{
|
|
var json = "3.14159";
|
|
var result = AcJsonDeserializer.Deserialize<double>(json);
|
|
Assert.AreEqual(3.14159, result, 0.00001);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericGuid_DirectPath()
|
|
{
|
|
var guid = Guid.NewGuid();
|
|
var json = $"\"{guid}\"";
|
|
var result = AcJsonDeserializer.Deserialize<Guid>(json);
|
|
Assert.AreEqual(guid, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericDateTime_DirectPath()
|
|
{
|
|
var dt = new DateTime(2024, 12, 25, 12, 30, 45, DateTimeKind.Utc);
|
|
var json = $"\"{dt:O}\"";
|
|
var result = AcJsonDeserializer.Deserialize<DateTime>(json);
|
|
Assert.AreEqual(dt, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericEnum_DirectPath()
|
|
{
|
|
var json = "2";
|
|
var result = AcJsonDeserializer.Deserialize<TestStatus>(json);
|
|
Assert.AreEqual(TestStatus.Processing, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericDecimal_DirectPath()
|
|
{
|
|
var json = "123.456";
|
|
var result = AcJsonDeserializer.Deserialize<decimal>(json);
|
|
Assert.AreEqual(123.456m, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericFloat_DirectPath()
|
|
{
|
|
var json = "3.14";
|
|
var result = AcJsonDeserializer.Deserialize<float>(json);
|
|
Assert.AreEqual(3.14f, result, 0.001f);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericLong_DirectPath()
|
|
{
|
|
var json = "9223372036854775807";
|
|
var result = AcJsonDeserializer.Deserialize<long>(json);
|
|
Assert.AreEqual(9223372036854775807L, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericByte_DirectPath()
|
|
{
|
|
var json = "255";
|
|
var result = AcJsonDeserializer.Deserialize<byte>(json);
|
|
Assert.AreEqual((byte)255, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericShort_DirectPath()
|
|
{
|
|
var json = "32767";
|
|
var result = AcJsonDeserializer.Deserialize<short>(json);
|
|
Assert.AreEqual((short)32767, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericUShort_DirectPath()
|
|
{
|
|
var json = "65535";
|
|
var result = AcJsonDeserializer.Deserialize<ushort>(json);
|
|
Assert.AreEqual((ushort)65535, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericUInt_DirectPath()
|
|
{
|
|
var json = "4294967295";
|
|
var result = AcJsonDeserializer.Deserialize<uint>(json);
|
|
Assert.AreEqual(4294967295U, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericULong_DirectPath()
|
|
{
|
|
var json = "18446744073709551615";
|
|
var result = AcJsonDeserializer.Deserialize<ulong>(json);
|
|
Assert.AreEqual(18446744073709551615UL, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericSByte_DirectPath()
|
|
{
|
|
var json = "-128";
|
|
var result = AcJsonDeserializer.Deserialize<sbyte>(json);
|
|
Assert.AreEqual((sbyte)-128, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericChar_DirectPath()
|
|
{
|
|
var json = "\"A\"";
|
|
var result = AcJsonDeserializer.Deserialize<char>(json);
|
|
Assert.AreEqual('A', result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericDateTimeOffset_DirectPath()
|
|
{
|
|
var dto = new DateTimeOffset(2024, 12, 25, 12, 30, 45, TimeSpan.FromHours(2));
|
|
var json = $"\"{dto:O}\"";
|
|
var result = AcJsonDeserializer.Deserialize<DateTimeOffset>(json);
|
|
Assert.AreEqual(dto, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericTimeSpan_DirectPath()
|
|
{
|
|
var ts = new TimeSpan(1, 2, 3, 4, 5);
|
|
var json = $"\"{ts:c}\"";
|
|
var result = AcJsonDeserializer.Deserialize<TimeSpan>(json);
|
|
Assert.AreEqual(ts, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericEnumFromString_DirectPath()
|
|
{
|
|
var json = "\"Processing\"";
|
|
var result = AcJsonDeserializer.Deserialize<TestStatus>(json);
|
|
Assert.AreEqual(TestStatus.Processing, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_Array_PopulatesList()
|
|
{
|
|
var json = "[1, 2, 3]";
|
|
var list = new List<int>();
|
|
AcJsonDeserializer.Populate(json, list);
|
|
Assert.AreEqual(3, list.Count);
|
|
Assert.AreEqual(1, list[0]);
|
|
Assert.AreEqual(3, list[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_ObjectToObject_PopulatesProperties()
|
|
{
|
|
var json = "{\"Name\": \"Updated\", \"Id\": 99}";
|
|
var obj = new SharedTag { Id = 1, Name = "Original" };
|
|
AcJsonDeserializer.Populate(json, obj, typeof(SharedTag));
|
|
Assert.AreEqual(99, obj.Id);
|
|
Assert.AreEqual("Updated", obj.Name);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_NullJson_ReturnsDefault()
|
|
{
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>("null");
|
|
Assert.IsNull(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_EmptyJson_ReturnsDefault()
|
|
{
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>("");
|
|
Assert.IsNull(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_GenericNullJson_ReturnsDefaultInt()
|
|
{
|
|
var result = AcJsonDeserializer.Deserialize<int>("null");
|
|
Assert.AreEqual(0, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_RuntimeType_WithDateTimeOffset()
|
|
{
|
|
var dto = new DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.FromHours(2));
|
|
var json = $"\"{dto:O}\"";
|
|
var result = AcJsonDeserializer.Deserialize(json, typeof(DateTimeOffset));
|
|
Assert.AreEqual(dto, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_RuntimeType_WithTimeSpan()
|
|
{
|
|
var ts = new TimeSpan(2, 30, 45);
|
|
var json = $"\"{ts:c}\"";
|
|
var result = AcJsonDeserializer.Deserialize(json, typeof(TimeSpan));
|
|
Assert.AreEqual(ts, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_RuntimeType_WithChar()
|
|
{
|
|
var json = "\"X\"";
|
|
var result = AcJsonDeserializer.Deserialize(json, typeof(char));
|
|
Assert.AreEqual('X', result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_RuntimeType_WithEnumString()
|
|
{
|
|
var json = "\"Active\"";
|
|
var result = AcJsonDeserializer.Deserialize(json, typeof(TestStatus));
|
|
Assert.AreEqual(TestStatus.Active, result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AcJsonDeserializer Error Handling Tests
|
|
|
|
[TestMethod]
|
|
public void Deserialize_InvalidJson_ThrowsException()
|
|
{
|
|
var invalidJson = "{ this is not valid json }";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Deserialize<TestOrderItem>(invalidJson);
|
|
Assert.Fail("Expected AcJsonDeserializationException");
|
|
}
|
|
catch (AcJsonDeserializationException)
|
|
{
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_DoubleQuotedJson_ThrowsException()
|
|
{
|
|
// This is what double-serialized JSON looks like: a JSON string containing escaped JSON
|
|
var doubleQuotedJson = "\"{\\\"Id\\\":1,\\\"Name\\\":\\\"Test\\\"}\"";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Deserialize<TestOrderItem>(doubleQuotedJson);
|
|
Assert.Fail("Expected AcJsonDeserializationException for double-serialized JSON");
|
|
}
|
|
catch (AcJsonDeserializationException ex)
|
|
{
|
|
Assert.IsTrue(ex.Message.Contains("double-serialized"));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_ArrayToObject_ThrowsException()
|
|
{
|
|
// Trying to deserialize an array JSON to a single object
|
|
var arrayJson = "[{\"Id\":1},{\"Id\":2}]";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Deserialize<TestOrderItem>(arrayJson);
|
|
Assert.Fail("Expected AcJsonDeserializationException");
|
|
}
|
|
catch (AcJsonDeserializationException ex)
|
|
{
|
|
Assert.IsTrue(ex.Message.Contains("array") || ex.Message.Contains("collection"));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_ObjectToArray_ThrowsException()
|
|
{
|
|
// Trying to deserialize an object JSON to a collection
|
|
var objectJson = "{\"Id\":1,\"ProductName\":\"Test\"}";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Deserialize<List<TestOrderItem>>(objectJson);
|
|
Assert.Fail("Expected AcJsonDeserializationException");
|
|
}
|
|
catch (AcJsonDeserializationException ex)
|
|
{
|
|
Assert.IsTrue(ex.Message.Contains("object") || ex.Message.Contains("collection"));
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_NullTarget_ThrowsArgumentNullException()
|
|
{
|
|
var json = "{\"Id\":1}";
|
|
TestOrderItem? target = null;
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Populate(json, target!);
|
|
Assert.Fail("Expected ArgumentNullException");
|
|
}
|
|
catch (ArgumentNullException)
|
|
{
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_InvalidJson_ThrowsException()
|
|
{
|
|
var target = new TestOrderItem();
|
|
var invalidJson = "{ not valid }";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Populate(invalidJson, target);
|
|
Assert.Fail("Expected AcJsonDeserializationException");
|
|
}
|
|
catch (AcJsonDeserializationException)
|
|
{
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Populate_ArrayToNonList_ThrowsException()
|
|
{
|
|
var target = new TestOrderItem();
|
|
var arrayJson = "[1,2,3]";
|
|
|
|
try
|
|
{
|
|
AcJsonDeserializer.Populate(arrayJson, target);
|
|
Assert.Fail("Expected AcJsonDeserializationException");
|
|
}
|
|
catch (AcJsonDeserializationException)
|
|
{
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Case Tests
|
|
|
|
[TestMethod]
|
|
public void Deserialize_SpecialCharactersInStrings_HandledCorrectly()
|
|
{
|
|
var json = "{\"Id\":1,\"ProductName\":\"Test \\\"quoted\\\" and \\\\backslash\"}";
|
|
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>(json);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("Test \"quoted\" and \\backslash", result.ProductName);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_UnicodeCharacters_HandledCorrectly()
|
|
{
|
|
var json = "{\"Id\":1,\"ProductName\":\"中文日本語한국어🎉\"}";
|
|
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>(json);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("中文日本語한국어🎉", result.ProductName);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_LargeNumbers_HandledCorrectly()
|
|
{
|
|
var json = "{\"Id\":999999999,\"ProductName\":\"Big\",\"Quantity\":2147483647}";
|
|
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>(json);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(999999999, result.Id);
|
|
Assert.AreEqual(int.MaxValue, result.Quantity);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Serialize_ThenDeserialize_RoundTripPreservesData()
|
|
{
|
|
var original = new TestOrderItem
|
|
{
|
|
Id = 42,
|
|
ProductName = "Test with \"quotes\" and \\backslash",
|
|
Quantity = 100,
|
|
UnitPrice = 99.99m,
|
|
Status = TestStatus.Processing
|
|
};
|
|
|
|
var json = original.ToJson();
|
|
var restored = AcJsonDeserializer.Deserialize<TestOrderItem>(json);
|
|
|
|
Assert.IsNotNull(restored);
|
|
Assert.AreEqual(original.Id, restored.Id);
|
|
Assert.AreEqual(original.ProductName, restored.ProductName);
|
|
Assert.AreEqual(original.Quantity, restored.Quantity);
|
|
Assert.AreEqual(original.UnitPrice, restored.UnitPrice);
|
|
Assert.AreEqual(original.Status, restored.Status);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Task-like JSON Wrapper Tests
|
|
|
|
[TestMethod]
|
|
public void Deserialize_TaskWrappedJson_DirectDeserialization_OnlyGetsRootProperties()
|
|
{
|
|
// This JSON represents a serialized Task<TestOrderItem> - the actual data is in "Result"
|
|
// This happens when someone forgets to await an async method before serializing
|
|
var taskWrappedJson = "{\"Result\":{\"Id\":1,\"ProductName\":\"Processed: TestProduct\",\"Quantity\":10,\"UnitPrice\":20,\"TotalPrice\":200},\"Id\":1,\"Status\":5,\"IsCompleted\":true,\"IsCompletedSuccessfully\":true}";
|
|
|
|
// Direct deserialization to TestOrderItem only gets root-level properties
|
|
var result = AcJsonDeserializer.Deserialize<TestOrderItem>(taskWrappedJson);
|
|
|
|
Assert.IsNotNull(result);
|
|
// Id=1 is at root level and matches
|
|
Assert.AreEqual(1, result.Id);
|
|
// These values are inside "Result" object, not at root - they remain default
|
|
Assert.AreEqual(0, result.Quantity, "Quantity should be 0 because it's inside Result, not at root");
|
|
Assert.AreEqual(0m, result.UnitPrice, "UnitPrice should be 0 because it's inside Result, not at root");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Deserialize_TaskWrappedJson_UseWrapperClass_ExtractsCorrectly()
|
|
{
|
|
// This JSON represents a serialized Task<TestOrderItem> - the actual data is in "Result"
|
|
var taskWrappedJson = "{\"Result\":{\"Id\":1,\"ProductName\":\"Processed: TestProduct\",\"Quantity\":10,\"UnitPrice\":20,\"TotalPrice\":200},\"Id\":1,\"Status\":5,\"IsCompleted\":true,\"IsCompletedSuccessfully\":true}";
|
|
|
|
// Proper approach: deserialize to a wrapper type and extract Result
|
|
var wrapper = AcJsonDeserializer.Deserialize<TaskResultWrapper<TestOrderItem>>(taskWrappedJson);
|
|
|
|
Assert.IsNotNull(wrapper);
|
|
Assert.IsNotNull(wrapper.Result);
|
|
Assert.AreEqual(1, wrapper.Result.Id);
|
|
Assert.AreEqual("Processed: TestProduct", wrapper.Result.ProductName);
|
|
Assert.AreEqual(10, wrapper.Result.Quantity);
|
|
Assert.AreEqual(20m, wrapper.Result.UnitPrice);
|
|
Assert.IsTrue(wrapper.IsCompleted);
|
|
Assert.IsTrue(wrapper.IsCompletedSuccessfully);
|
|
Assert.AreEqual(5, wrapper.Status);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper class to deserialize Task-like JSON structures.
|
|
/// This is what you get when you accidentally serialize a Task object instead of awaiting it.
|
|
/// </summary>
|
|
private class TaskResultWrapper<T>
|
|
{
|
|
public T? Result { get; set; }
|
|
public int Id { get; set; }
|
|
public int Status { get; set; }
|
|
public bool IsCompleted { get; set; }
|
|
public bool IsCompletedSuccessfully { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
} |