using Newtonsoft.Json; using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer; namespace GFramework.Game.Tests.Serializer; [TestFixture] public sealed class JsonSerializerTests { [Test] public void Serialize_And_Deserialize_Should_RoundTrip_Object() { var serializer = new GameJsonSerializer(); var original = new PlayerStateStub { Name = "Player1", Level = 7 }; var json = serializer.Serialize(original); var restored = serializer.Deserialize(json); Assert.Multiple(() => { Assert.That(restored.Name, Is.EqualTo("Player1")); Assert.That(restored.Level, Is.EqualTo(7)); }); } [Test] public void Serialize_Should_Honor_Injected_Settings() { var serializer = new GameJsonSerializer(new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }); var json = serializer.Serialize(new OptionalStateStub { Name = "Configured", Description = null }); Assert.Multiple(() => { Assert.That(json, Does.Contain(Environment.NewLine)); Assert.That(json, Does.Contain("\"Name\": \"Configured\"")); Assert.That(json, Does.Not.Contain("Description")); }); } [Test] public void Converters_Should_Be_Used_For_Serialization_And_Deserialization() { var serializer = new GameJsonSerializer(); serializer.Converters.Add(new CoordinateStubConverter()); var json = serializer.Serialize(new CoordinateStub { X = 3, Y = 9 }); var restored = serializer.Deserialize(json); Assert.Multiple(() => { Assert.That(json, Is.EqualTo("\"3:9\"")); Assert.That(restored.X, Is.EqualTo(3)); Assert.That(restored.Y, Is.EqualTo(9)); }); } [Test] public void Deserialize_Should_Throw_With_Target_Type_Context_When_Json_Is_Invalid() { var serializer = new GameJsonSerializer(); var exception = Assert.Throws(() => serializer.Deserialize("{invalid json}")); Assert.Multiple(() => { Assert.That(exception, Is.Not.Null); Assert.That(exception!.Message, Does.Contain(typeof(PlayerStateStub).FullName)); Assert.That(exception.InnerException, Is.Not.Null); }); } [Test] public void Deserialize_With_Runtime_Type_Should_Throw_With_Target_Type_Context_When_Json_Is_Invalid() { var serializer = new GameJsonSerializer(); var exception = Assert.Throws(() => serializer.Deserialize("{invalid json}", typeof(PlayerStateStub))); Assert.Multiple(() => { Assert.That(exception, Is.Not.Null); Assert.That(exception!.Message, Does.Contain(typeof(PlayerStateStub).FullName)); Assert.That(exception.InnerException, Is.Not.Null); }); } [Test] public void Deserialize_With_Runtime_Type_Should_Return_Target_Runtime_Type() { var serializer = new GameJsonSerializer(); var restored = serializer.Deserialize("{\"Name\":\"Runtime\",\"Level\":11}", typeof(PlayerStateStub)); Assert.That(restored, Is.TypeOf()); Assert.That(((PlayerStateStub)restored).Level, Is.EqualTo(11)); } [Test] public void Serialize_With_Runtime_Type_Should_Allow_Null_Object() { var serializer = new GameJsonSerializer(); var json = serializer.Serialize(null!, typeof(PlayerStateStub)); Assert.That(json, Is.EqualTo("null")); } [Test] public void Deserialize_Should_Preserve_ArgumentException_For_Invalid_Input_Arguments() { var serializer = new GameJsonSerializer(); var exception = Assert.Throws(() => serializer.Deserialize(string.Empty)); Assert.That(exception, Is.Not.Null); } private sealed class PlayerStateStub { public string Name { get; set; } = string.Empty; public int Level { get; set; } } private sealed class OptionalStateStub { public string Name { get; set; } = string.Empty; public string? Description { get; set; } } private sealed class CoordinateStub { public int X { get; set; } public int Y { get; set; } } private sealed class CoordinateStubConverter : JsonConverter { public override void WriteJson(JsonWriter writer, CoordinateStub? value, JsonSerializer serializer) { writer.WriteValue($"{value?.X}:{value?.Y}"); } public override CoordinateStub ReadJson( JsonReader reader, Type objectType, CoordinateStub? existingValue, bool hasExistingValue, JsonSerializer serializer) { var raw = (string?)reader.Value ?? throw new JsonSerializationException("Coordinate value cannot be null."); var parts = raw.Split(':'); return new CoordinateStub { X = int.Parse(parts[0]), Y = int.Parse(parts[1]) }; } } }