Json.NET中如何基于类内其他属性为属性设置默认值?
你提到的直接用[DefaultValue(Start.AddHours(1))]的方式是行不通的——因为DefaultValueAttribute的构造参数必须是编译时常量,而Start.AddHours(1)是需要实例化后才能计算的动态值,这段代码根本过不了编译。
不过要实现「End未赋值时自动设为Start加1小时」的需求,Json.NET提供了几种可行方案,下面给你详细说明:
方法一:使用[OnDeserialized]特性(最简单直接)
这个特性允许你在反序列化完成后执行自定义逻辑,此时所有必填属性(比如你的Start)都已经被正确赋值,你可以在这里检查End是否未被设置,然后补全默认值:
public class JsonsoftExample { [JsonProperty(Required = Required.Always)] public DateTime Start { get; set; } public DateTime End { get; set; } [OnDeserialized] private void OnDeserialized(StreamingContext context) { // 如果End是默认值(未被反序列化赋值),则设置为Start+1小时 if (End == default(DateTime)) { End = Start.AddHours(1); } } }
注意:如果你的业务场景中End有可能被显式设置为default(DateTime)(即0001-01-01),可以把End改成可空类型DateTime?,然后判断是否为null来区分「未赋值」和「显式设为默认值」。
方法二:自定义ContractResolver(更灵活的全局方案)
如果你希望这个默认值逻辑能复用在多个类上,或者不想在实体类里添加额外方法,可以自定义契约解析器,为End属性提供动态默认值:
public class DefaultEndTimeContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 针对JsonsoftExample的End属性设置自定义ValueProvider if (property.DeclaringType == typeof(JsonsoftExample) && property.PropertyName == nameof(JsonsoftExample.End)) { property.ValueProvider = new EndTimeValueProvider(); } return property; } } public class EndTimeValueProvider : IValueProvider { public void SetValue(object target, object value) { // 直接赋值传入的反序列化值 typeof(JsonsoftExample).GetProperty(nameof(JsonsoftExample.End)).SetValue(target, value); } public object GetValue(object target) { var example = (JsonsoftExample)target; var endValue = typeof(JsonsoftExample).GetProperty(nameof(JsonsoftExample.End)).GetValue(target); // 如果End未被赋值,返回Start+1小时,否则返回当前值 return (DateTime)endValue == default(DateTime) ? example.Start.AddHours(1) : endValue; } }
使用时,在序列化/反序列化配置中指定这个契约解析器即可:
var settings = new JsonSerializerSettings { ContractResolver = new DefaultEndTimeContractResolver(), DefaultValueHandling = DefaultValueHandling.Populate }; // 反序列化示例 var json = "{\"Start\":\"2024-05-20T10:00:00\"}"; var example = JsonConvert.DeserializeObject<JsonsoftExample>(json, settings); // example.End 会被自动设置为 2024-05-20T11:00:00
方法三:结合构造函数与ObjectCreationHandling(需注意反序列化顺序)
Json.NET默认先调用无参构造函数,再赋值属性,所以直接在无参构造里初始化End会拿到Start的默认值(不是反序列化后的真实值)。但如果使用有参构造函数,并让Json.NET优先通过构造函数创建实例,就能在构造时直接计算End:
public class JsonsoftExample { public DateTime Start { get; set; } public DateTime End { get; set; } // 有参构造函数,接收Start参数并自动初始化End public JsonsoftExample(DateTime start) { Start = start; End = start.AddHours(1); } // 保留无参构造供Json.NET使用(如果需要) public JsonsoftExample() { } }
反序列化时配置ObjectCreationHandling确保使用有参构造:
var settings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; var json = "{\"Start\":\"2024-05-20T10:00:00\"}"; var example = JsonConvert.DeserializeObject<JsonsoftExample>(json, settings);
这种方法的特点是:如果JSON里显式提供了End值,会覆盖构造函数的初始化值,这通常符合业务需求,但如果需要更精细的控制,前两种方法会更合适。
内容的提问来源于stack exchange,提问作者GrayCat

