给定以下可序列化的类结构:
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可以抛出异常,则可能应该将其重新设计为方法。请注意,此规则不适用于索引器,因为我们期望在验证参数后出现异常。