newtonsoft json schema deserialize ValidationError newtonsoft json schema deserialize ValidationError json json

newtonsoft json schema deserialize ValidationError


You are encountering several problems here.

Firstly, ValidationError is publicly immutable (i.e. all properties lack public setters) and only has a single constructor, which is nonparameterized. Thus third party apps (including Json.NET itself) have no way to populate an instance of this type.

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. 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.

However, while this allows most ValidationError properties to be round-tripped successfully, the Message property is not. This second problem arises because, in the current implementation, 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 inheriting from SelectedPrivateSettersContractResolver can be used to do this:

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.

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.

  • You may want to cache and reuse ValidationErrorsContractResolver for best performance as recommended in the documentation.

  • 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. I suspect they did this to prevent the serialized JSON from becoming excessively bloated. If you want Schema to be round-tripped you can force it to be serialized in ValidationErrorsContractResolver.CreateProperty() as follows:

     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.

    Demo fiddle #3 here.