Mid*_*hew 7 c# json jsonschema json-schema-validator
我正在使用 Json 架构来验证 json 对象。我正确地收到错误消息。但它看起来更像是开发人员友好的错误消息。有什么方法可以自定义错误消息,以便我可以使其更加用户友好。我浏览了很多论坛,但没有找到解决方案。
下面是我使用的代码:
string Json = @"{'Sheet1':[{'Location':'#$','First Name':11,'Last Name':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA','Amount':'A','Date of Birth':'8522/85/25'}]}";
string JSONSchemaValidator = @"{'$schema':'http://json-schema.org/draft-04/schema#','title':'JSON Validation Schema','type':'object','additionalProperties':true,'properties':{'Sheet1':{'type':'array','items':{'type':'object','additionalProperties':true,'properties':{'Location':{'type':['number','string','null'],'pattern':'^[a-zA-Z0-9\\-\\s]+$','maxLength':15},'First Name':{'type':['string','null'],'maxLength':20,'pattern':'^[a-zA-Z\\-\\s]+$'},'Last Name':{'type':['string','null'],'maxLength':10,'pattern':'^[a-zA-Z\\-\\s]+$'},'Amount':{'type':['number','null'],'minimum':-999999999.99,'maximum':999999999.99,'exclusiveMaximum':true,'multipleOf':0.01},'Date of Birth':{'type':['string','null'],'format':'date-time'}}}}}}";
JSchema schema = JSchema.Parse(JSONSchemaValidator);
JObject person = JObject.Parse(Json);
IList<string> iJSONSchemaValidatorErrorList;
bool valid = person.IsValid(schema, out iJSONSchemaValidatorErrorList);
if (iJSONSchemaValidatorErrorList != null && iJSONSchemaValidatorErrorList.Count > 0)
{
foreach (string error in iJSONSchemaValidatorErrorList)
{
Console.WriteLine(error);
}
}
Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)
以下是我收到的错误消息:
1. String '#$' does not match regex pattern '^[a-zA-Z0-9\-\s]+$'. Path 'Sheet1[0].Location', line 1, position 27.
2. Invalid type. Expected String, Null but got Integer. Path 'Sheet1[0]['First Name']', line 1, position 43.
3. String 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA' exceeds maximum length of 10. Path 'Sheet1[0]['Last Name']', line 1, position 87.
4. Invalid type. Expected Number, Null but got String. Path 'Sheet1[0].Amount', line 1, position 100.
5. String '8522/85/25' does not validate against format 'date-time'. Path 'Sheet1[0]['Date of Birth']', line 1, position 129.
Run Code Online (Sandbox Code Playgroud)
我正在寻找类似的东西:
1. 'Location' in Column 1 of Sheet1 should be alphanumeric.
2. 'Name' in Column 1 of Sheet1 should only contain alphabets.
3. 'Last Name' in column 1 exceeds maximum length of 10.
4. 'Amount' in column 1 should contain only numbers.
5. 'Date of Birth' in column 1 is not a valid date.
Run Code Online (Sandbox Code Playgroud)
我有类似的问题。
就我而言,我正在使用Newtonsoft.Json.Schema(是的,我支付了许可证!)JSchema从System.ComponentModel.DataAnnotations属性生成 - 我看到它生成了正确的JSchema,但验证错误消息根本没有使用ErrorMessage属性上的属性。
事实证明,这是因为仅使用数据注释属性的子集JSchemaGenerator生成规则 - 并且它忽略了该属性。JSchemaErrorMessage
它使用的属性集记录在“生成模式”页面上:
JSchema member Data Annotation attribute class Attribute properties used
------------------------------------------------------------------------------------------
required [Required]
minLength [StringLength] .MinimumLength
maxLength [StringLength] .MaximumLength
minLength [MinLength] .Length
maxLength [MaxLength] .Length
minItems [MinLength] .Length
maxItems [MaxLength] .Length
pattern [RegularExpression] .Pattern
enum [EnumDataType] .EnumType
format [DataType] .DataType
format [Url]
format [Phone]
format [EmailAddress]
minimum [Range] .Minimum
maximum [Range] .Maximum
Run Code Online (Sandbox Code Playgroud)
因此,有了这些知识,我们就可以尝试ErrorMessage通过反映反序列化的类型并在适当的成员上查找匹配的数据注释属性来从属性的属性中获取自定义的验证消息PropertyInfo。
ValidationError验证错误作为对象JSchemaValidatingReader公开Newtonsoft.Json.Schema.ValidationError,其中包含:
Path我们需要从目标类型的属性名称或任何匹配的属性反转该属性[JsonProperty]。ErrorType来确定要查找的属性类型。出于性能原因,我们不想每次都反射每种类型和属性,因此下面的代码是静态通用缓存(确保将其标记为internal避免违反 CA1000)。
internal static class JsonPropertiesCache<T>
{
/// <summary>Case-sensitive.</summary>
public static IReadOnlyDictionary<String,JsonPropertyDataAnnotationAttributes> AttributesByJsonPropertyName { get; } = CreateJsonPropertiesDictionary();
private static Dictionary<String,JsonPropertyDataAnnotationAttributes> CreateJsonPropertiesDictionary()
{
return typeof(T)
.GetProperties( BindingFlags.Instance | BindingFlags.Public )
.Select( pi => ( ok: JsonPropertyDataAnnotationAttributes.TryCreate( pi, out JsonPropertyDataAnnotationAttributes attrs ), attrs ) )
.Where( t => t.ok )
.Select( t => t.attrs )
#if DEBUG
.ToDictionary( attrs => attrs.Name ); // This will throw if there's a duplicate JSON property name.
#else
.GroupBy( attrs => attrs.Name )
.ToDictionary( grp => grp.Key, grp => grp.First() ); // This won't throw, but will only use attributes from the first match (whichever match that is).
#endif
}
}
public class JsonPropertyDataAnnotationAttributes
{
public static Boolean TryCreate( PropertyInfo pi, out JsonPropertyDataAnnotationAttributes attrs )
{
// Only create an instance if at least one DataAnnotation validation attribute is present:
if( pi?.GetCustomAttributes<ValidationAttribute>()?.Any() ?? false )
{
attrs = new JsonPropertyDataAnnotationAttributes( pi );
return true;
}
else
{
attrs = default;
return false;
}
}
public JsonPropertyDataAnnotationAttributes( PropertyInfo pi )
{
this.PropertyInfo = pi ?? throw new ArgumentNullException( nameof(pi) );
this.JsonProperty = pi.GetCustomAttribute<JsonPropertyAttribute>();
this.Required = pi.GetCustomAttribute<RequiredAttribute>();
this.MinLength = pi.GetCustomAttribute<MinLengthAttribute>();
this.StringLength = pi.GetCustomAttribute<StringLengthAttribute>();
this.MaxLength = pi.GetCustomAttribute<MaxLengthAttribute>();
this.RegularExpression = pi.GetCustomAttribute<RegularExpressionAttribute>();
this.Url = pi.GetCustomAttribute<UrlAttribute>();
this.Phone = pi.GetCustomAttribute<PhoneAttribute>();
this.Email = pi.GetCustomAttribute<EmailAddressAttribute>();
this.Range = pi.GetCustomAttribute<RangeAttribute>();
this.EnumDataType = pi.GetCustomAttribute<EnumDataTypeAttribute>(); // NOTE: `EnumDataTypeAttribute` is a subclass of `DataTypeAttribute`.
this.DataType = pi.GetCustomAttribute<DataTypeAttribute>();
}
public PropertyInfo PropertyInfo { get; }
public JsonPropertyAttribute JsonProperty { get; }
public String Name => this.JsonProperty?.PropertyName ?? this.PropertyInfo.Name; // TODO: Support custom NamingStrategies.
public RequiredAttribute Required { get; }
public MinLengthAttribute MinLength { get; }
public StringLengthAttribute StringLength { get; }
public MaxLengthAttribute MaxLength { get; }
public RegularExpressionAttribute RegularExpression { get; }
public UrlAttribute Url { get; }
public PhoneAttribute Phone { get; }
public EmailAddressAttribute Email { get; }
public RangeAttribute Range { get; }
public EnumDataTypeAttribute EnumDataType { get; }
public DataTypeAttribute DataType { get; }
public Boolean TryFormatValidationErrorMessage( ErrorType errorType, out String errorMessage )
{
switch( errorType )
{
case ErrorType.MaximumLength:
errorMessage = this.MaxLength?.FormatErrorMessage( this.Name ) ?? this.StringLength?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.MinimumLength:
errorMessage = this.MinLength?.FormatErrorMessage( this.Name ) ?? this.StringLength?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.Pattern:
errorMessage = this.RegularExpression?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.MaximumItems:
errorMessage = this.MaxLength?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.MinimumItems:
errorMessage = this.MinLength?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.Required:
errorMessage = this.Required?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.Enum:
errorMessage = this.EnumDataType?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.Format:
// Same precedence order as used by Newtonsoft.Json.Schema.Infrastructure::GetFormat(JsonProperty):
errorMessage = this.Url?.FormatErrorMessage( this.Name ) ?? this.Phone?.FormatErrorMessage( this.Name ) ?? this.Email?.FormatErrorMessage( this.Name ) ?? this.DataType?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
case ErrorType.Maximum:
case ErrorType.Minimum:
errorMessage = this.Range?.FormatErrorMessage( this.Name );
return !String.IsNullOrEmpty( errorMessage );
default:
errorMessage = default;
return false;
}
}
}
Run Code Online (Sandbox Code Playgroud)
ErrorMessage从SchemaValidationEventArgs因此,给定您的一组SchemaValidationEventArgs,获取人类可读的验证错误是匹配您要反序列化的类型并使用上面的字典的简单情况。
警告:此代码当前不支持本身就是类的 DTO 类 JSON 属性 - 但实现它相当简单,并且对读者来说是一个练习。
private static Result<T> DeserializeAndValidate<T>( String body, JSchema schema )
{
if( body == null ) throw new ArgumentNullException(nameof(body));
//
List<SchemaValidationEventArgs> messages = new List<SchemaValidationEventArgs>();
using( StringReader textReader = new StringReader( body ) )
using( JsonTextReader jsonTextReader = new JsonTextReader( textReader ) )
using( JSchemaValidatingReader validatingReader = new JSchemaValidatingReader( jsonTextReader ) )
{
validatingReader.Schema = schema;
validatingReader.ValidationEventHandler += ( sender, eventArgs ) => messages.Add( eventArgs );
//
JsonSerializer serializer = new JsonSerializer();
T value = serializer.Deserialize<T>( validatingReader );
if( messages.Count == 0 )
{
return value;
}
else
{
IReadOnlyDictionary<String,JsonPropertyDataAnnotationAttributes> dict = JsonPropertiesCache<T>.AttributesByJsonPropertyName;
return messages
.Select( e => e.ValidationError )
.Flatten( err => err.ChildErrors )
.Select( err => ( err, ok: dict.TryGetValue( err.Path, out JsonPropertyDataAnnotationAttributes attr ), attr ) )
.Select( t => t.ok && t.attr.TryFormatValidationErrorMessage( t.err.ErrorType, out String errorMessage ) ? errorMessage : t.err.Message )
.StringJoin( separator: "\r\n" );
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2401 次 |
| 最近记录: |