簡體   English   中英

newtonsoft json 架構反序列化驗證錯誤

[英]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以獲得最佳性能。

  • 您可能會注意到第三個問題,即ValidationErrorSchema屬性未序列化或反序列化,因為 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM