如果 JSON 中的 JavaScript 对象不会反序列化为我想要的 C# 对象,我如何询问它以提供一条错误消息来解释输入有什么问题? (假设JSON格式正确,只是数据无效。)
我的 C# 课程:(简化)
public class Dependent
{
public Dependent()
{
}
public string FirstName { get; set; }
public DateTime DateOfBirth { get; set; }
}
反序列化测试代码:
string dependents = @"[
{
""FirstName"": ""Kenneth"",
""DateOfBirth"": ""02-08-2013""
},
{
""FirstName"": ""Ronald"",
""DateOfBirth"": ""08-07-2011""
}
]";
JavaScriptSerializer jss = new JavaScriptSerializer();
List<Dependent> deps = new List<Dependent>();
deps = jss.Deserialize<List<Dependent>>(dependents);
这一切都有效。除非传入非日期作为生日,否则反序列化将失败。
我想提供一条错误消息,例如“家属 2 的出生日期不是有效日期”。或“受抚养人 2 必须未满 18 岁。”
如果 JSON 无法反序列化到我的对象中,我如何验证它的详细信息?
可能的解决方案:
public class SerializableDependent
{
public SerializableDependent()
{
}
public string FirstName { get; set; }
public string DateOfBirth { get; set; }
}
然后我不应该收到任何将所有内容都作为字符串的错误,并且我可以循环遍历对象并进行验证。但这似乎是错误的。
JavaScriptSerializer
不支持广泛的错误处理。我建议您使用 Json.NET 库。您可以使用 JsonSerializerSettings
对象的 Error
事件处理程序来捕获有关出错原因的更多详细信息。有关使用此成员的信息位于文档中。
对于上面的代码片段,可以按如下方式编写填充错误消息数组的处理程序:
public class Dependent
{
public Dependent()
{
}
public string FirstName { get; set; }
public DateTime? DateOfBirth { get; set; } // Use a nullable object to hold the error value
}
void DeserializeTest()
{
string dependents = @"[
{
""FirstName"": ""Kenneth"",
""DateOfBirth"": ""02-08-2013""
},
{
""FirstName"": ""Ronald"",
""DateOfBirth"": ""asdf""
}
]";
var messages = new List<string>();
var settings = new JsonSerializerSettings(){
Error = (s,e)=>{
var depObj = e.CurrentObject as Dependent;
if(depObj != null)
{
messages.Add(string.Format("Obj:{0} Message:{1}",depObj.FirstName, e.ErrorContext.Error.Message));
}
else
{
messages.Add(e.ErrorContext.Error.Message);
}
e.ErrorContext.Handled = true; // Set the datetime to a default value if not Nullable
}
};
var ndeps = JsonConvert.DeserializeObject<Dependent[]>(dependents, settings);
//ndeps contains the serialized objects, messages contains the errors
}
您可以使用
JSON Schema(Newtonsoft 提供的框架)验证JSON
中的 C#
。它允许验证 JSON
数据。下面的代码说明了它的样子。有关更多详细信息,您可以阅读文章在 C# 中使用 JSON 模式验证 JSON
string myschemaJson = @"{
'description': 'An employee', 'type': 'object',
'properties':
{
'name': {'type':'string'},
'id': {'type':'string'},
'company': {'type':'string'},
'role': {'type':'string'},
'skill': {'type': 'array',
'items': {'type':'string'}
}
}";
JsonSchema schema = JsonSchema.Parse(myschemaJson);
JObject employee = JObject.Parse(@"{
'name': 'Tapas', 'id': '12345', 'company': 'TCS',
'role': 'developer',
'skill': ['.NET', 'JavaScript', 'C#', 'Angular',
'HTML']
}");
bool valid = employee.IsValid(schema);
// True
JsonSchema schema1 = JsonSchema.Parse(myschemaJson);
JObject employee1 = JObject.Parse(@"{
'name': null, 'id': '12345', 'company': 'TCS',
'role': 'developer',
'skill': ['.NET', 'JavaScript', 'C#', 'Angular',
'HTML']
}");
IList<string> messages;
bool valid1 = employee1.IsValid(schema1, out messages);
// False
// "Invalid type. Expected String but got Null. Line 2,
// position 24."
JsonSchema schema2 = new JsonSchema();
schema2.Type = JsonSchemaType.Object;
schema2.Properties = new Dictionary<string, JsonSchema>
{
{ "name", new JsonSchema
{ Type = JsonSchemaType.String } },
{ "id", new JsonSchema
{ Type = JsonSchemaType.String } },
{ "company", new JsonSchema
{ Type = JsonSchemaType.String } },
{ "role", new JsonSchema
{ Type = JsonSchemaType.String } },
{
"skill", new JsonSchema
{
Type = JsonSchemaType.Array,
Items = new List<JsonSchema>
{ new JsonSchema
{ Type = JsonSchemaType.String } }
}
},
};
JObject employee2 = JObject.Parse(@"{
'name': 'Tapas', 'id': '12345',
'company': 'TCS', 'role': 'developer',
'skill': ['.NET', 'JavaScript', 'C#', 'Angular',
'HTML']
}");
bool valid2 = employee2.IsValid(schema2);
// True
NewtonSoft 每小时有 1000 个。所以这里有另一种方法,没有 newtonSoft。
我已经在控制器上的操作过滤器中实现了它作为工作示例。 这没有 1000 的限制。您可以使用错误构建或自定义一个,就像我在 StringToShort 中所做的那样
这将验证模型,在模型到达控制器之前,响应是一个很好的消息,因此接收者可以将其用于任何用途。 (我们实际上在产品版本中使用了不同的语言。这个使用“硬编码”测试,但很容易设置您的偏好。
这是动作过滤器
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using NJsonSchema.Validation;
namespace CoreApiFunctions.Filter
{
public class PersonModelValidationFilter3 : ActionFilterAttribute
{
private readonly string _rulebookPath;
private HttpValidationProblemDetails problemDetails = new HttpValidationProblemDetails();
public PersonModelValidationFilter3(string rulebookPath)
{
_rulebookPath = rulebookPath;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine($"Executing action {context.ActionDescriptor.DisplayName}");
//Activity.Current.add.AddTag("httpContext", context);
string schemaFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _rulebookPath);
var rulebookJson = System.IO.File.ReadAllText(schemaFilePath);
JsonSchema schema = JsonSchema.FromJsonAsync(rulebookJson).Result;
foreach (var argument in context.ActionArguments)
{
var model = argument.Value;
if (model != null)
{
JObject incomingModel = JObject.FromObject(model);
var errors = schema.Validate(incomingModel);
foreach (var error in errors)
{
string actualValue = incomingModel.SelectToken(error.Property)?.ToString();
AddValidationErrorToOutput(error, actualValue);
}
}
}
if (problemDetails.Errors.Count > 0)
{
problemDetails.Status = 400;
problemDetails.Title = "One or more validation errors occurred.";
problemDetails.Type = "https://www.example.com/validation-error";
problemDetails.Detail = "Please correct the validation errors and try again.";
context.Result = new BadRequestObjectResult(problemDetails);
}
}
private void AddValidationErrorToOutput(ValidationError error, string actualValue)
{
string customMessage = error.Kind.ToString();
if (error.Kind == ValidationErrorKind.StringTooShort)
{
//int actualLength = error.Value?.ToString().Length ?? 0;
int actualLength = actualValue?.Length ?? 0;
customMessage = $"String {error.Property} is too short, length is {actualLength}, minimum is {error.Schema.MinLength}.";
}
problemDetails.Errors.Add(error.Property, new string[] { customMessage });
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine($"Executed action {context.ActionDescriptor.DisplayName}");
}
public override void OnResultExecuting(ResultExecutingContext context)
{
Console.WriteLine($"Executed action {context.ActionDescriptor.DisplayName}");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine($"Executed action {context.ActionDescriptor.DisplayName}");
}
}
}
这是一个用于验证模型的 Json 模式。 (有点复杂,但只是为了展示可能性)
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 5
},
"vatNumber": {
"type": "string"
}
},
"required": [
"vatNumber",
"name"
],
"allOf": [
{
"if": {
"properties": {
"country": {
"properties": {
"id": { "const": "GB" }
}
}
},
"required": [ "country" ]
},
"then": {
"properties": {
"vatNumber": {
"pattern": "^GB([\\d]{9}|[\\d]{12}|GD[\\d]{3}|HA[\\d]{3})$",
"errorMessage": "Can be GB000000000(000), GBGD000 or GBHA000"
}
}
}
},
{
"if": {
"properties": {
"country": {
"properties": {
"id": { "const": "RU" }
}
}
},
"required": [ "country" ]
},
"then": {
"properties": {
"vatNumber": {
"pattern": "^[0-9]{9}$",
"errorMessage": "Can be only 9 digits"
}
}
}
}
]
}
这是控制器。它使用“人”模型。如下所示。 我发现这是直接在代码中添加新过滤器和模式的最简单方法。可以在代码外部修改架构。 (更容易部署)
[HttpPost("TestValidation3")]
[TypeFilter(typeof(PersonModelValidationFilter3), Arguments = new object[] { "conditional-fields-validaiton-schema.json" })]
[ProducesDefaultResponseType]
[ProducesErrorResponseType(typeof(HttpValidationProblemDetails))]
public async Task<IActionResult> TestValidation3(Person3 person)
{
// Code goes here
// Normal response.
return Ok();
}
这是型号
public class Person3
{
public string name { get; set; }
public string vatNumber { get; set; }
public Country country { get; set; }
}
public class Country
{
public string id { get; set; }
public string name { get; set; }
}