[英]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 問題頁面上打開一個問題?
您在這里遇到了幾個問題。
首先, ValidationError
是公共不可變的(即所有屬性都缺少公共設置器)並且只有一個構造函數,它是非參數化的。 因此第三方應用程序(包括 Json.NET 本身)無法填充這種類型的實例。
然而, 參考資料顯示,大多數屬性都有私有設置器。 因此,應該可以將daniel的此答案中的 SisoJsonDefaultContractResolver 調整為SisoJsonDefaultContractResolver
中的Private setter到往返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
以獲得最佳性能。
您可能會注意到第三個問題,即ValidationError
的Schema
屬性未序列化或反序列化,因為 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在這里。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.