newtonsoft json 模式反序列化 ValidationError
newtonsoft json schema deserialize ValidationError
使用Newtonsoft.Json.Schema。似乎 Newtonsoft 的 JsonConvert
无法反序列化它自己的 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 往返 ValidationError
,或者我应该在 GitHub issues page 上打开一个问题?
您在这里遇到了几个问题。
首先,ValidationError
is publicly immutable (i.e. all properties lack public setters) and only has a single constructor, which is nonparameterized。因此第三方应用程序(包括 Json.NET 本身)无法填充此类型的实例。
reference source shows, however, that most of the properties have private setters. Thus it should be possible to adapt SisoJsonDefaultContractResolver
from this answer by daniel to Private setters in Json.Net来回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);
演示 fiddle #1 here.
然而,虽然这允许大多数 ValidationError
属性成功往返,但 Message
属性 却不能。出现第二个问题是因为,在当前的实现中,可以使用继承自 SelectedPrivateSettersContractResolver
的 Message
has no getter. Instead it returns the value of a field _message
which is calculated on demand. Thus it will be necessary to force serialization of _message
(and a related field _extendedMessage
). A custom contract resolver 来执行此操作:
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);
消息往返成功。演示 fiddle #2 here.
备注:
以这种方式使用反射强制序列化私有字段是脆弱的,如果 Newtonsoft 更改 ValidationError
.
的实现,可能很容易崩溃
您可能想要缓存和重用 ValidationErrorsContractResolver
以获得 the documentation 中建议的最佳性能。
您可能会注意到第三个问题,即Schema
property of ValidationError
is not serialized or deserialized because Newtonsoft have explicitly marked it with [JsonIgnore]
in the source code。我怀疑他们这样做是为了防止序列化的 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
值将 重复在反序列化期间,因为它没有通过引用保存。
演示 fiddle #3 here.
使用Newtonsoft.Json.Schema。似乎 Newtonsoft 的 JsonConvert
无法反序列化它自己的 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 往返 ValidationError
,或者我应该在 GitHub issues page 上打开一个问题?
您在这里遇到了几个问题。
首先,ValidationError
is publicly immutable (i.e. all properties lack public setters) and only has a single constructor, which is nonparameterized。因此第三方应用程序(包括 Json.NET 本身)无法填充此类型的实例。
reference source shows, however, that most of the properties have private setters. Thus it should be possible to adapt SisoJsonDefaultContractResolver
from this answer by daniel to Private setters in Json.Net来回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);
演示 fiddle #1 here.
然而,虽然这允许大多数 ValidationError
属性成功往返,但 Message
属性 却不能。出现第二个问题是因为,在当前的实现中,可以使用继承自 SelectedPrivateSettersContractResolver
的 Message
has no getter. Instead it returns the value of a field _message
which is calculated on demand. Thus it will be necessary to force serialization of _message
(and a related field _extendedMessage
). A custom contract resolver 来执行此操作:
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);
消息往返成功。演示 fiddle #2 here.
备注:
以这种方式使用反射强制序列化私有字段是脆弱的,如果 Newtonsoft 更改
的实现,可能很容易崩溃ValidationError
.您可能想要缓存和重用
ValidationErrorsContractResolver
以获得 the documentation 中建议的最佳性能。您可能会注意到第三个问题,即
Schema
property ofValidationError
is not serialized or deserialized because Newtonsoft have explicitly marked it with[JsonIgnore]
in the source code。我怀疑他们这样做是为了防止序列化的 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
值将 重复在反序列化期间,因为它没有通过引用保存。演示 fiddle #3 here.