简体   繁体   English

newtonsoft json 架构反序列化验证错误

[英]newtonsoft json schema deserialize ValidationError

Using Newtonsoft.Json.Schema.使用 Newtonsoft.Json.Schema。 It seems Newtonsoft's JsonConvert cannnot deserialize its own ValidationError .似乎 Newtonsoft 的JsonConvert无法反序列化自己的ValidationError

Specifically, the following will fail:具体来说,以下将失败:

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);

I am getting a valid string for resultStr after the serialization, something like序列化后,我得到了 resultStr 的有效字符串,例如

[{\"Message\": \"String 'key' does not match regex pattern ... \", ... }] 

After Deserializing again, I am getting an array of one item of type Newtonsoft.Json.Schema.ValidationError (the validation result had one error so it's okay), but all its fields are in their default values.再次反序列化后,我得到一个类型为 Newtonsoft.Json.Schema.ValidationError 的数组(验证结果有一个错误,所以没关系),但它的所有字段都是默认值。

Did anybody else also encounter this?有没有其他人也遇到过这种情况? Is there a way to round-trip a ValidationError using Json.NET, or should I open an issue on its GitHub issues page ?有没有办法使用 Json.NET 往返ValidationError ,或者我应该在其GitHub 问题页面上打开一个问题?

You are encountering several problems here.您在这里遇到了几个问题。

Firstly, ValidationError is publicly immutable (ie all properties lack public setters) and only has a single constructor, which is nonparameterized .首先, ValidationError是公共不可变的(即所有属性都缺少公共设置器)并且只有一个构造函数,它是非参数化的。 Thus third party apps (including Json.NET itself) have no way to populate an instance of this type.因此第三方应用程序(包括 Json.NET 本身)无法填充这种类型的实例。

The 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 to round-trip ValidationError .因此,应该可以将daniel此答案中的 SisoJsonDefaultContractResolver 调整为SisoJsonDefaultContractResolver中的Private setter到往返ValidationError First define:首先定义:

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;
    }       
}

And now you can do:现在你可以这样做:

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);

Demo fiddle #1 here .演示小提琴#1在这里

However, while this allows most ValidationError properties to be round-tripped successfully, the Message property is not.但是,虽然这允许大多数ValidationError属性成功往返,但Message属性却不是。 This second problem arises because, in the current implementation, Message has no getter.之所以会出现第二个问题,是因为在当前实现中, Message没有 getter。 Instead it returns the value of a field _message which is calculated on demand.相反,它返回按需计算的字段_message的值。 Thus it will be necessary to force serialization of _message (and a related field _extendedMessage ).因此,有必要强制序列化_message (以及相关字段_extendedMessage )。 A custom contract resolver inheriting from SelectedPrivateSettersContractResolver can be used to do this: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;
    }
}

And now if you round-trip as follows:现在,如果您按以下方式往返:

var settings = new JsonSerializerSettings
{
    ContractResolver = new ValidationErrorsContractResolver(),
};
var resultStr = JsonConvert.SerializeObject(errors, Formatting.Indented, settings);
var ReSerializedResult = JsonConvert.DeserializeObject<List<ValidationError>>(resultStr, settings);

The message is round-tripped successfully.消息成功往返。 Demo fiddle #2 here .演示小提琴#2在这里

Notes:笔记:

  • Using reflection to force serialization of private fields in this manner is fragile, and might easily break if Newtonsoft were to change the implementation of ValidationError .以这种方式使用反射来强制序列化私有字段是脆弱的,如果 Newtonsoft 要更改ValidationError的实现,可能很容易中断。

  • You may want to cache and reuse ValidationErrorsContractResolver for best performance as recommended in the documentation .您可能希望按照文档中的建议缓存和重用ValidationErrorsContractResolver以获得最佳性能。

  • You may notice a third problem, namely that the Schema property of ValidationError is not serialized or deserialized because Newtonsoft have explicitly marked it with [JsonIgnore] in the source code .您可能会注意到第三个问题,即ValidationErrorSchema属性未序列化或反序列化,因为 Newtonsoft 在源代码中已明确将其标记为[JsonIgnore] I suspect they did this to prevent the serialized JSON from becoming excessively bloated.我怀疑他们这样做是为了防止序列化的 JSON 变得过于臃肿。 If you want Schema to be round-tripped you can force it to be serialized in ValidationErrorsContractResolver.CreateProperty() as follows:如果您希望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; }

    However, if you do your JSON will become much more bloated, and if you serialize multiple validation errors, the JSchema Schema value will get duplicated during deserialization, as it was not saved by reference.但是,如果您这样做,您的 JSON 将变得更加臃肿,并且如果您序列化多个验证错误,则JSchema Schema值将在反序列化期间重复,因为它不是通过引用保存的。

    Demo fiddle #3 here .演示小提琴#3在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM