JSON.即使' ObjectCreationHandling '被设置为' Replace ',也会调用接口属



给定以下可序列化的类结构:

public class Foo
{
    private IBar bar;
    public IBar Bar
    {
        get 
        { 
            if(this.bar == null)
                throw new InvalidOperationException("bar must be set before it is read"); 
            return this.bar;
        }
        set 
        { 
            if(value == null)
                throw new ArgumentNullException("value");
            this.bar = value;
        }
    }
}
public interface IBar { }
public class Bar : IBar { }

和以下自定义JsonConverter,以确保IBar被转换为Bar:

public class BarConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IBar);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jobject = JObject.Load(reader);
        return jobject.ToObject(typeof(Bar), serializer);
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException("This should not be invoked.");
    }
}

下面的代码失败,因为JSON。. NET尝试调用Foo.Bar getter时,该属性从未设置:

var settings = new JsonSerializerSettings
{
    ObjectCreationHandling = ObjectCreationHandling.Replace,
    Converters = { new BarConverter() }
};
JsonConvert.DeserializeObject<Foo>("{Bar: {}}", settings);

我的理解是ObjectCreationHandling.Replace应该阻止getter被调用。事实上,如果我将Bar属性的类型更改为具体的Bar类而不是IBar接口,那么ObjectCreationHandling.Replace工作得很好。

这是JSON的缺陷吗?. NET,或者是否有理由声明为接口的属性应该忽略ObjectCreationHandling值?

忽略ObjectCreationHandling设置并不是将属性声明为接口的事实;这是因为该属性正在使用转换器,所以它被忽略。当您将属性声明从接口IBar更改为具体的Bar时,转换器中的CanConvert()方法返回false,因为objectType不等于IBar。这将从等式中删除转换器,所以是Json。Net取"正常"。考虑ObjectCreationHandling设置而不调用getter的代码路径。如果将CanConvert()的实现更改为return typeof(IBar).IsAssignableFrom(objectType);然后你会看到getter被调用,不管属性是被声明为IBar还是Bar

我相信这种行为是经过设计的,而不是缺陷。ReadJson方法的契约要求(通过existingValue参数)传递对象的现有值,以便转换器可以决定如何处理它(如果有的话)。Json是没有办法的。在不调用getter的情况下确定现有值是什么,所以它必须调用getter。

如果我是你,我会改变Bar getter实现,如果可以的话,不要抛出异常。根据微软的建议,在属性getter中抛出异常被认为是不好的做法:

AVOID从属性getter抛出异常。

属性getter应该是简单的操作,不应该有任何前提条件。如果getter可以抛出异常,则可能应该将其重新设计为方法。请注意,此规则不适用于索引器,因为我们期望在验证参数后出现异常。

相关内容

  • 没有找到相关文章

最新更新