我正在使用Json Schema来验证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();
以下是我收到的错误消息:
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.
我正在寻找类似的东西:
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.
答案 0 :(得分:0)
我有类似的问题。
就我而言,我正在使用Newtonsoft.Json.Schema
(是的,我付了许可证费用!)正在根据JSchema
属性生成一个System.ComponentModel.DataAnnotations
-我看到它正在生成正确的JSchema
,但是验证错误消息根本没有在属性上使用ErrorMessage
属性。
事实证明,这是因为JSchemaGenerator
仅使用Data-Annotation属性的子集生成JSchema
规则-而忽略了ErrorMessage
属性。
它确实使用are documented on the "Generating schemas" page的一组属性:
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
因此,有了这些知识,我们可以尝试通过反映要反序列化的类型并在适当的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;
}
}
}
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" );
}