c# - newtonsoft json模式反序列化ValidationError
问题描述
使用 Newtonsoft.Json.Schema。似乎 NewtonsoftJsonConvert
无法反序列化自己的ValidationError
.
具体来说,以下将失败:
var dataFile = System.IO.File.ReadlAllText("data.json");
var schemaFile = System.IO.File.ReadlAllText("schema.json");
... // parsing the files
model.IsValid(schema, out IList<Newtonsoft.Json.Schema.ValidationError> ve);
var errors = ve as List<Newtonsoft.Json.Schema.ValidationError>; // <-- this may be a problem?
var resultStr = Newtonsoft.Json.JsonConvert.SerializeObject(errors); // <-- this works as expected, though
var ReSerializedResult = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Newtonsoft.Json.Schema.ValidationError>>(resultStr);
序列化后,我得到了 resultStr 的有效字符串,例如
[{\"Message\": \"String 'key' does not match regex pattern ... \", ... }]
再次反序列化后,我得到一个类型为 Newtonsoft.Json.Schema.ValidationError 的数组(验证结果有一个错误,所以没关系),但它的所有字段都在它们的默认值中。
有没有其他人也遇到过这种情况?有没有办法使用 Json.NET 往返,或者我应该在其GitHub 问题页面ValidationError
上打开一个问题?
解决方案
您在这里遇到了几个问题。
首先,ValidationError
它是公开不可变的(即所有属性都缺少公共设置器)并且只有一个构造函数,它是非参数化的。因此第三方应用程序(包括 Json.NET 本身)无法填充这种类型的实例。
然而,参考资料显示,大多数属性都有私有设置器。因此,应该可以SisoJsonDefaultContractResolver
从daniel的这个答案适应 Json.Net中的Private setter到 round-trip 。首先定义:ValidationError
public class SelectedPrivateSettersContractResolver : DefaultContractResolver
{
HashSet<Type> privateSetterTypes { get; } = new ();
public SelectedPrivateSettersContractResolver(params Type [] types) : this((IEnumerable<Type>)types) { }
public SelectedPrivateSettersContractResolver(IEnumerable<Type> types) =>
privateSetterTypes.UnionWith(types ?? throw new ArgumentNullException());
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Ignored && prop.Readable && !prop.Writable)
if (privateSetterTypes.Contains(prop.DeclaringType))
if (member is PropertyInfo property)
prop.Writable = property.GetSetMethod(true) != null;
return prop;
}
}
现在你可以这样做:
var settings = new JsonSerializerSettings
{
ContractResolver = new SelectedPrivateSettersContractResolver(typeof(ValidationError)),
};
var resultStr = JsonConvert.SerializeObject(errors, Formatting.Indented, settings);
var ReSerializedResult = JsonConvert.DeserializeObject<List<ValidationError>>(resultStr, settings);
演示小提琴#1在这里。
但是,虽然这允许大多数ValidationError
属性成功往返,但Message
属性不是。之所以会出现第二个问题,是因为在当前的实现中,Message
没有 getter。_message
相反,它返回按需计算的字段值。因此,有必要强制序列化_message
(和相关字段_extendedMessage
)。继承自的自定义合约解析器SelectedPrivateSettersContractResolver
可用于执行此操作:
public class ValidationErrorsContractResolver : SelectedPrivateSettersContractResolver
{
static readonly string [] validationErrorFields = new [] { "_message", "_extendedMessage" };
public ValidationErrorsContractResolver() : base(typeof(ValidationError)) { }
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
var list = base.GetSerializableMembers(objectType);
if (typeof(ValidationError).IsAssignableFrom(objectType))
{
foreach (var fieldName in validationErrorFields)
if (objectType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic) is var f && f != null)
list.Add(f);
}
return list;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (prop.DeclaringType == typeof(ValidationError))
{
if (validationErrorFields.Contains(prop.UnderlyingName))
{
prop.Ignored = false;
prop.Readable = prop.Writable = true;
}
}
return prop;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (typeof(ValidationError).IsAssignableFrom(objectType))
{
// Ensure _message and _extendedMessage are calculated.
contract.OnSerializingCallbacks.Add((o, c) => { var m = ((ValidationError)o).Message; });
}
return contract;
}
}
现在,如果您按以下方式往返:
var settings = new JsonSerializerSettings
{
ContractResolver = new ValidationErrorsContractResolver(),
};
var resultStr = JsonConvert.SerializeObject(errors, Formatting.Indented, settings);
var ReSerializedResult = JsonConvert.DeserializeObject<List<ValidationError>>(resultStr, settings);
消息成功往返。演示小提琴#2在这里。
笔记:
使用反射以这种方式强制序列化私有字段是脆弱的,如果 Newtonsoft 改变
ValidationError
.您可能希望按照文档中的
ValidationErrorsContractResolver
建议缓存和重用以获得最佳性能。您可能会注意到第三个问题,即 的
Schema
属性ValidationError
未序列化或反序列化,因为 Newtonsoft[JsonIgnore]
在源代码中已明确标记为。我怀疑他们这样做是为了防止序列化的 JSON 变得过于臃肿。如果您想Schema
往返,可以强制将其序列化,ValidationErrorsContractResolver.CreateProperty()
如下所示:protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var prop = base.CreateProperty(member, memberSerialization); if (prop.DeclaringType == typeof(ValidationError)) { if (validationErrorFields.Contains(prop.UnderlyingName) || prop.UnderlyingName == "Schema") { prop.Ignored = false; prop.Readable = prop.Writable = true; } } return prop; }
但是,如果您这样做,您的 JSON 将变得更加臃肿,并且如果您序列化多个验证错误,则该
JSchema Schema
值将在反序列化期间重复,因为它不是通过引用保存的。演示小提琴#3在这里。
推荐阅读
- r - 如何使用 lapply 按行号范围对列表中的数据框进行子集化?
- macos - Homebrew:无法通过 brew (macOS Mojave) 安装选项
- python - 从 pandas df 中选择行,其中索引出现在另一个 df 的某处
- php - 检查给定值是否存在于另一个数组中?
- java - 如何使用servlet在特定单元格的excel表中写入数据?
- azure - Azure CLI 任务不适用于 Windows 构建代理
- python - 另存为,每种文件类型都有不同的回调函数
- html - 包含具有边距和填充的正确子级
- php - 如何使用或不使用php将unicode文本数据解码回excel文件?
- php - Laravel 5.8 - 如何保护存储目录免受路由?