Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support deserialization with System.Text.Json (#1928) #1947

Merged
merged 2 commits into from May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.txt
@@ -1,5 +1,6 @@
11.0.2 - 27 May 2022
Child validator contexts should contain a reference to their parent context (#1945)
Support deserialization of ValidationResult using System.Text.Json (#1928)

11.0.1 - 7 May 2022
Fix regression in the Test Helper affecting the With* and Without* assertion methods (#1937)
Expand Down
51 changes: 51 additions & 0 deletions src/FluentValidation.Tests/JsonSerializationTests.cs
@@ -0,0 +1,51 @@
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"),
},
RuleSetsExecuted = new[] { "Test1" }
};

// System.Text.Json
var serialized1 = JsonSerializer.Serialize(validationResult);
var deserialized1 = JsonSerializer.Deserialize<ValidationResult>(serialized1);

// Newtonsoft.Json
var serialized2 = Newtonsoft.Json.JsonConvert.SerializeObject(validationResult);
var deserialized2 = Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationResult>(serialized2);

deserialized1.IsValid.ShouldBeFalse();
deserialized2.IsValid.ShouldBeFalse();

deserialized1.Errors.Count.ShouldEqual(2);
deserialized2.Errors.Count.ShouldEqual(2);

deserialized1.Errors[0].PropertyName.ShouldEqual("MyProperty1");
deserialized2.Errors[0].PropertyName.ShouldEqual("MyProperty1");

deserialized1.Errors[1].PropertyName.ShouldEqual("MyProperty2");
deserialized2.Errors[1].PropertyName.ShouldEqual("MyProperty2");

deserialized1.Errors[0].ErrorMessage.ShouldEqual("Invalid MyProperty1");
deserialized2.Errors[0].ErrorMessage.ShouldEqual("Invalid MyProperty1");

deserialized1.Errors[1].ErrorMessage.ShouldEqual("Invalid MyProperty2");
deserialized2.Errors[1].ErrorMessage.ShouldEqual("Invalid MyProperty2");

deserialized1.RuleSetsExecuted.Length.ShouldEqual(1);
deserialized2.RuleSetsExecuted.Length.ShouldEqual(1);

deserialized1.RuleSetsExecuted[0].ShouldEqual("Test1");
deserialized2.RuleSetsExecuted[0].ShouldEqual("Test1");
}
}
7 changes: 6 additions & 1 deletion src/FluentValidation/Results/ValidationFailure.cs
Expand Up @@ -25,14 +25,19 @@ namespace FluentValidation.Results {
/// </summary>
[Serializable]
public class ValidationFailure {
private ValidationFailure() {

/// <summary>
/// Creates a new validation failure.
/// </summary>
public ValidationFailure() {

}

/// <summary>
/// Creates a new validation failure.
/// </summary>
public ValidationFailure(string propertyName, string errorMessage) : this(propertyName, errorMessage, null) {

}

/// <summary>
Expand Down
27 changes: 21 additions & 6 deletions src/FluentValidation/Results/ValidationResult.cs
Expand Up @@ -26,7 +26,7 @@ namespace FluentValidation.Results {
/// </summary>
[Serializable]
public class ValidationResult {
private readonly List<ValidationFailure> _errors;
private List<ValidationFailure> _errors;

/// <summary>
/// Whether validation succeeded
Expand All @@ -36,9 +36,23 @@ public class ValidationResult {
/// <summary>
/// A collection of errors
/// </summary>
public List<ValidationFailure> Errors => _errors;
public List<ValidationFailure> 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();;
}
}

/// <summary>
/// The RuleSets that were executed during the validation run.
/// </summary>
public string[] RuleSetsExecuted { get; set; }

/// <summary>
/// Creates a new validationResult
Expand All @@ -50,9 +64,10 @@ public class ValidationResult {
/// <summary>
/// Creates a new ValidationResult from a collection of failures
/// </summary>
/// <param name="failures">List of <see cref="ValidationFailure"/> which is later available through <see cref="Errors"/>. This list get's copied.</param>
/// <param name="failures">Collection of <see cref="ValidationFailure"/> instances which is later available through the <see cref="Errors"/> property.</param>
/// <remarks>
/// Every caller is responsible for not adding <c>null</c> to the list.
/// Any nulls will be excluded.
/// The list is copied.
/// </remarks>
public ValidationResult(IEnumerable<ValidationFailure> failures) {
_errors = failures.Where(failure => failure != null).ToList();
Expand All @@ -76,7 +91,7 @@ public class ValidationResult {
/// <param name="separator">The character to separate the error messages.</param>
/// <returns></returns>
public string ToString(string separator) {
return string.Join(separator, _errors.Select(failure => failure.ErrorMessage));
return string.Join(separator, _errors.Select(failure => failure.ErrorMessage));
}
}
}