在反序列化为 C# 对象之前验证 JSON

问题描述 投票:0回答:3

如果 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; }
}

然后我不应该收到任何将所有内容都作为字符串的错误,并且我可以循环遍历对象并进行验证。但这似乎是错误的。

c# json serialization javascriptserializer
3个回答
3
投票

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
}

2
投票

您可以使用

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

0
投票

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; }

}
© www.soinside.com 2019 - 2024. All rights reserved.