diff --git a/ReflectorNet.Tests/src/SchemaTests/SerializedMemberSchemaTests.cs b/ReflectorNet.Tests/src/SchemaTests/SerializedMemberSchemaTests.cs new file mode 100644 index 00000000..5b60a1fc --- /dev/null +++ b/ReflectorNet.Tests/src/SchemaTests/SerializedMemberSchemaTests.cs @@ -0,0 +1,225 @@ +using System.Text.Json.Nodes; +using com.IvanMurzak.ReflectorNet.Model; +using com.IvanMurzak.ReflectorNet.Utils; +using Xunit.Abstractions; + +namespace com.IvanMurzak.ReflectorNet.Tests.SchemaTests +{ + /// + /// Tests to validate that SerializedMember JSON schema correctly represents + /// the actual serialized JSON for various value types. + /// + public class SerializedMemberSchemaTests : BaseTest + { + public SerializedMemberSchemaTests(ITestOutputHelper output) : base(output) { } + + [Fact] + public void SerializedMember_Schema_Value_Should_Accept_Any_JsonType() + { + // Arrange + var reflector = new Reflector(); + var schema = reflector.GetSchema(); + + // Assert - Check that schema was generated + Assert.NotNull(schema); + + // Get the value property schema + var schemaObj = schema.AsObject(); + Assert.True(schemaObj.TryGetPropertyValue(JsonSchema.Properties, out var propertiesNode)); + var properties = propertiesNode!.AsObject(); + Assert.True(properties.TryGetPropertyValue(SerializedMember.ValueName, out var valueSchemaNode)); + var valueSchema = valueSchemaNode!.AsObject(); + + // The value schema should NOT have a "type" constraint + // This allows it to accept any JSON value type + Assert.False(valueSchema.TryGetPropertyValue(JsonSchema.Type, out _), + "The 'value' property schema should not have a 'type' constraint to allow any JSON value type"); + + // It should have a description + Assert.True(valueSchema.TryGetPropertyValue(JsonSchema.Description, out var descriptionNode)); + Assert.NotNull(descriptionNode); + + _output.WriteLine($"✓ SerializedMember schema allows 'value' to be any JSON type"); + _output.WriteLine($"Schema:\n{schema.ToJsonString()}"); + } + + [Theory] + [InlineData("test string", "string")] + [InlineData(42, "number")] + [InlineData(3.14, "number")] + [InlineData(true, "boolean")] + [InlineData(false, "boolean")] + public void SerializedMember_Should_Serialize_PrimitiveTypes_Correctly(object value, string expectedJsonType) + { + // Arrange + var reflector = new Reflector(); + + // Act + var serialized = reflector.Serialize(value, name: "testValue"); + var json = serialized.ToJson(reflector); + + // Parse and validate JSON structure + var jsonNode = JsonNode.Parse(json); + Assert.NotNull(jsonNode); + + var jsonObj = jsonNode!.AsObject(); + Assert.True(jsonObj.TryGetPropertyValue(SerializedMember.ValueName, out var valueNode)); + Assert.NotNull(valueNode); + + // Validate the value type matches expected + var actualJsonType = GetJsonValueType(valueNode); + _output.WriteLine($"Value: {value}, Expected type: {expectedJsonType}, Actual type: {actualJsonType}"); + _output.WriteLine($"Serialized JSON: {json}"); + + Assert.Equal(expectedJsonType, actualJsonType); + } + + [Fact] + public void SerializedMember_Should_Serialize_Null_Correctly() + { + // Arrange + var reflector = new Reflector(); + + // Act + var serialized = reflector.Serialize(null, typeof(string), name: "testValue"); + var json = serialized.ToJson(reflector); + + // Parse and validate JSON structure + var jsonNode = JsonNode.Parse(json); + Assert.NotNull(jsonNode); + + var jsonObj = jsonNode!.AsObject(); + + // For null values, the value property should be absent or null + if (jsonObj.TryGetPropertyValue(SerializedMember.ValueName, out var valueNode)) + { + Assert.Null(valueNode); + } + + _output.WriteLine($"Serialized null value JSON: {json}"); + } + + [Fact] + public void SerializedMember_Should_Serialize_Array_Correctly() + { + // Arrange + var reflector = new Reflector(); + var array = new[] { 1, 2, 3 }; + + // Act + var serialized = reflector.Serialize(array, name: "testArray"); + var json = serialized.ToJson(reflector); + + // Parse and validate JSON structure + var jsonNode = JsonNode.Parse(json); + Assert.NotNull(jsonNode); + + var jsonObj = jsonNode!.AsObject(); + Assert.True(jsonObj.TryGetPropertyValue(SerializedMember.ValueName, out var valueNode)); + Assert.NotNull(valueNode); + + // The value should be a JSON array + Assert.IsType(valueNode); + + _output.WriteLine($"Serialized array JSON: {json}"); + } + + [Fact] + public void SerializedMember_Should_Serialize_Object_Correctly() + { + // Arrange + var reflector = new Reflector(); + var obj = new TestClass { Name = "Test", Value = 123 }; + + // Act + var serialized = reflector.Serialize(obj, name: "testObject"); + var json = serialized.ToJson(reflector); + + // Parse and validate JSON structure + var jsonNode = JsonNode.Parse(json); + Assert.NotNull(jsonNode); + + var jsonObj = jsonNode!.AsObject(); + Assert.True(jsonObj.TryGetPropertyValue(SerializedMember.ValueName, out var valueNode)); + Assert.NotNull(valueNode); + + // For complex objects, the value is typically an empty object {} + // and the actual data is in props/fields + Assert.IsType(valueNode); + + _output.WriteLine($"Serialized object JSON: {json}"); + } + + [Fact] + public void SerializedMember_RoundTrip_Should_Preserve_AllValueTypes() + { + // Arrange + var reflector = new Reflector(); + var testValues = new object[] + { + "string value", + 42, + 3.14159, + true, + false + }; + + foreach (var originalValue in testValues) + { + // Act + var serialized = reflector.Serialize(originalValue, name: "test"); + var deserialized = reflector.Deserialize(serialized); + + // Assert + var originalJson = originalValue.ToJson(reflector); + var deserializedJson = deserialized.ToJson(reflector); + + _output.WriteLine($"Original: {originalValue} ({originalValue.GetType().Name})"); + _output.WriteLine($"Original JSON: {originalJson}"); + _output.WriteLine($"Deserialized JSON: {deserializedJson}"); + + Assert.Equal(originalJson, deserializedJson); + } + } + + /// + /// Helper method to determine the JSON value type from a JsonNode + /// + private string GetJsonValueType(JsonNode node) + { + return node switch + { + JsonObject => "object", + JsonArray => "array", + JsonValue value when value.TryGetValue(out _) => "boolean", + JsonValue value when IsNumericJsonValue(value) => "number", + JsonValue value when value.TryGetValue(out _) => "string", + _ => "unknown" + }; + } + + /// + /// Helper method to check if a JsonValue represents a numeric type + /// + private bool IsNumericJsonValue(JsonValue value) + { + return value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _) || + value.TryGetValue(out _); + } + + private class TestClass + { + public string Name { get; set; } = string.Empty; + public int Value { get; set; } + } + } +} diff --git a/ReflectorNet/src/Convertor/Json/SerializedMemberConverter.cs b/ReflectorNet/src/Convertor/Json/SerializedMemberConverter.cs index 4f818b8d..1365c046 100644 --- a/ReflectorNet/src/Convertor/Json/SerializedMemberConverter.cs +++ b/ReflectorNet/src/Convertor/Json/SerializedMemberConverter.cs @@ -40,7 +40,6 @@ public class SerializedMemberConverter : JsonSchemaConverter, }, [SerializedMember.ValueName] = new JsonObject { - [JsonSchema.Type] = JsonSchema.Object, [JsonSchema.Description] = TypeUtils.GetDescription( typeof(SerializedMember) .GetMember(nameof(SerializedMember.valueJsonElement)) @@ -73,7 +72,7 @@ public class SerializedMemberConverter : JsonSchemaConverter, .First()) } }, - [JsonSchema.Required] = new JsonArray { nameof(SerializedMember.typeName), SerializedMember.ValueName }, + [JsonSchema.Required] = new JsonArray { nameof(SerializedMember.typeName) }, [JsonSchema.AdditionalProperties] = false }; public static JsonNode SchemaRef => new JsonObject diff --git a/ReflectorNet/src/Utils/Json/JsonSerializer.cs b/ReflectorNet/src/Utils/Json/JsonSerializer.cs index 5342d411..611e55a6 100644 --- a/ReflectorNet/src/Utils/Json/JsonSerializer.cs +++ b/ReflectorNet/src/Utils/Json/JsonSerializer.cs @@ -80,6 +80,7 @@ public JsonSerializer(Reflector reflector) // ReferenceHandler = ReferenceHandler.Preserve, PropertyNamingPolicy = null, PropertyNameCaseInsensitive = true, + IncludeFields = true, // Include public fields in serialization WriteIndented = true, TypeInfoResolver = JsonTypeInfoResolver.Combine( new DefaultJsonTypeInfoResolver()