diff --git a/src/FluentValidation.Tests/JsonSerializationTests.cs b/src/FluentValidation.Tests/JsonSerializationTests.cs new file mode 100644 index 000000000..717c5950e --- /dev/null +++ b/src/FluentValidation.Tests/JsonSerializationTests.cs @@ -0,0 +1,35 @@ +namespace FluentValidation.Tests; + +using System.Text.Json; +using Results; +using Xunit; + +public class JsonSerializationTests { + + [Fact] + public void SystemTextJson_deserialization_should_be_consistent_with_newtonsoft() { + var validationResult = new ValidationResult { + Errors = { + new ValidationFailure("MyProperty1", "Invalid MyProperty1"), + new ValidationFailure("MyProperty2", "Invalid MyProperty2"), + new ValidationFailure("MyProperty3", "Invalid MyProperty3") + } + }; + // System.Text.Json + var serialized1 = JsonSerializer.Serialize(validationResult); + var deserialized1 = JsonSerializer.Deserialize(serialized1); + + // Newtonsoft.Json + var serialized2 = Newtonsoft.Json.JsonConvert.SerializeObject(validationResult); + var deserialized2 = Newtonsoft.Json.JsonConvert.DeserializeObject(serialized2); + + Assert.NotNull(deserialized1); + Assert.Equal(deserialized1.IsValid, deserialized2.IsValid); + Assert.Equal(deserialized1.Errors.Count, deserialized2.Errors.Count); + + for (var i = 0; i < deserialized1.Errors.Count; i++) { + Assert.Equal(deserialized1.Errors[i].PropertyName, deserialized2.Errors[i].PropertyName); + Assert.Equal(deserialized1.Errors[i].ErrorMessage, deserialized2.Errors[i].ErrorMessage); + } + } +} diff --git a/src/FluentValidation/Results/ValidationFailure.cs b/src/FluentValidation/Results/ValidationFailure.cs index c45efc40c..e49359310 100644 --- a/src/FluentValidation/Results/ValidationFailure.cs +++ b/src/FluentValidation/Results/ValidationFailure.cs @@ -25,7 +25,11 @@ namespace FluentValidation.Results { /// [Serializable] public class ValidationFailure { - private ValidationFailure() { + + /// + /// Creates a new validation failure. + /// + public ValidationFailure() { } @@ -33,6 +37,7 @@ public class ValidationFailure { /// Creates a new validation failure. /// public ValidationFailure(string propertyName, string errorMessage) : this(propertyName, errorMessage, null) { + } /// diff --git a/src/FluentValidation/Results/ValidationResult.cs b/src/FluentValidation/Results/ValidationResult.cs index 526e907e9..f310a37a5 100644 --- a/src/FluentValidation/Results/ValidationResult.cs +++ b/src/FluentValidation/Results/ValidationResult.cs @@ -26,7 +26,7 @@ namespace FluentValidation.Results { /// [Serializable] public class ValidationResult { - private readonly List _errors; + private List _errors; /// /// Whether validation succeeded @@ -36,9 +36,23 @@ public class ValidationResult { /// /// A collection of errors /// - public List Errors => _errors; + public List Errors { + get => _errors; + set { + if (value == null) { + throw new ArgumentNullException(nameof(value)); + } - public string[] RuleSetsExecuted { get; internal set; } + // Ensure any nulls are removed and the list is copied + // to be consistent with the constructor below. + _errors = value.Where(failure => failure != null).ToList();; + } + } + + /// + /// The RuleSets that were executed during the validation run. + /// + public string[] RuleSetsExecuted { get; set; } /// /// Creates a new validationResult @@ -50,9 +64,10 @@ public class ValidationResult { /// /// Creates a new ValidationResult from a collection of failures /// - /// List of which is later available through . This list get's copied. + /// Collection of instances which is later available through the property. /// - /// Every caller is responsible for not adding null to the list. + /// Any nulls will be excluded. + /// The list is copied. /// public ValidationResult(IEnumerable failures) { _errors = failures.Where(failure => failure != null).ToList(); @@ -76,7 +91,7 @@ public class ValidationResult { /// The character to separate the error messages. /// public string ToString(string separator) { - return string.Join(separator, _errors.Select(failure => failure.ErrorMessage)); + return string.Join(separator, _errors.Select(failure => failure.ErrorMessage)); } } }