2017-09-05 24 views
0

我想添加一个元数据属性到我的json,以便客户端可以知道什么属性是日期。如何添加元数据来描述哪些属性是日期在JSON.Net

例如,如果我有这样的对象:

{ 
    "notADate": "a value", 
    "aDate": "2017-04-23T18:25:43.511Z", 
    "anotherDate": "2017-04-23T18:25:43.511Z" 
} 

我想补充一个元数据属性来告诉消费者其属性当作日期是这样的:

{ 
    "_date_properties_": ["aDate", "anotherDate"], 
    "notADate": "a value", 
    "aDate": "2017-04-23T18:25:43.511Z", 
    "anotherDate": "2017-04-23T18:25:43.511Z" 
} 

任何帮助将是伟大的,谢谢!

+1

而不是像这样添加配置信息,你有没有考虑使用约定。例如,所有日期属性都以'Date'结尾。然后,除了命名之外,服务器上不需要任何特别的考虑,使阵列保持最新状态等。 – gcaton

+0

很难记住使用约定并避免意外使用它们。即'DateTime StartTime'会“失败”,并且'布尔HasDate'会“通过” – nzjoel

回答

0

您可以创建一个custom ContractResolver,将合成的"_date_properties_"属性插入到序列化的每个对象的合约中。

要做到这一点,首先子类DefaultContractResolver允许合同,他们已被应用程序添加的事件处理程序创建后,将流畅定制:

public class ConfigurableContractResolver : DefaultContractResolver 
{ 
    readonly object contractCreatedPadlock = new object(); 
    event EventHandler<ContractCreatedEventArgs> contractCreated; 
    int contractCount = 0; 

    void OnContractCreated(JsonContract contract, Type objectType) 
    { 
     EventHandler<ContractCreatedEventArgs> created; 
     lock (contractCreatedPadlock) 
     { 
      contractCount++; 
      created = contractCreated; 
     } 
     if (created != null) 
     { 
      created(this, new ContractCreatedEventArgs(contract, objectType)); 
     } 
    } 

    public event EventHandler<ContractCreatedEventArgs> ContractCreated 
    { 
     add 
     { 
      lock (contractCreatedPadlock) 
      { 
       if (contractCount > 0) 
       { 
        throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated."); 
       } 
       contractCreated += value; 
      } 
     } 
     remove 
     { 
      lock (contractCreatedPadlock) 
      { 
       if (contractCount > 0) 
       { 
        throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated."); 
       } 
       contractCreated -= value; 
      } 
     } 
    } 

    protected override JsonContract CreateContract(Type objectType) 
    { 
     var contract = base.CreateContract(objectType); 
     OnContractCreated(contract, objectType); 
     return contract; 
    } 
} 

public class ContractCreatedEventArgs : EventArgs 
{ 
    public JsonContract Contract { get; private set; } 
    public Type ObjectType { get; private set; } 

    public ContractCreatedEventArgs(JsonContract contract, Type objectType) 
    { 
     this.Contract = contract; 
     this.ObjectType = objectType; 
    } 
} 

public static class ConfigurableContractResolverExtensions 
{ 
    public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler) 
    { 
     if (resolver == null || handler == null) 
      throw new ArgumentNullException(); 
     resolver.ContractCreated += handler; 
     return resolver; 
    } 
} 

接下来,创建一个扩展方法所需的属性添加到JsonObjectContract

public static class JsonContractExtensions 
{ 
    const string DatePropertiesName = "_date_properties_"; 

    public static void AddDateProperties(this JsonContract contract) 
    { 
     var objectContract = contract as JsonObjectContract; 
     if (objectContract == null) 
      return; 
     var properties = objectContract.Properties.Where(p => p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(DateTime?)).ToList(); 
     if (properties.Count > 0) 
     { 
      var property = new JsonProperty 
      { 
       DeclaringType = contract.UnderlyingType, 
       PropertyName = DatePropertiesName, 
       UnderlyingName = DatePropertiesName, 
       PropertyType = typeof(string[]), 
       ValueProvider = new FixedValueProvider(properties.Select(p => p.PropertyName).ToArray()), 
       AttributeProvider = new NoAttributeProvider(), 
       Readable = true, 
       Writable = false, 
       // Ensure // Ensure PreserveReferencesHandling and TypeNameHandling do not apply to the synthetic property. 
       ItemIsReference = false, 
       TypeNameHandling = TypeNameHandling.None, 
      }; 
      objectContract.Properties.Insert(0, property); 
     } 
    } 

    class FixedValueProvider : IValueProvider 
    { 
     readonly object properties; 

     public FixedValueProvider(object value) 
     { 
      this.properties = value; 
     } 

     #region IValueProvider Members 

     public object GetValue(object target) 
     { 
      return properties; 
     } 

     public void SetValue(object target, object value) 
     { 
      throw new NotImplementedException("SetValue not implemented for fixed properties; set JsonProperty.Writable = false."); 
     } 

     #endregion 
    } 

    class NoAttributeProvider : IAttributeProvider 
    { 
     #region IAttributeProvider Members 

     public IList<Attribute> GetAttributes(Type attributeType, bool inherit) { return new Attribute[0]; } 

     public IList<Attribute> GetAttributes(bool inherit) { return new Attribute[0]; } 

     #endregion 
    } 
} 

最后,系列化你的例子类型,如下所示:

var settings = new JsonSerializerSettings 
{ 
    ContractResolver = new ConfigurableContractResolver 
    { 
     // Here I am using CamelCaseNamingStrategy as is shown in your JSON. 
     // If you don't want camel case, leave NamingStrategy null. 
     NamingStrategy = new CamelCaseNamingStrategy(), 
    }.Configure((s, e) => { e.Contract.AddDateProperties(); }), 
}; 

var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings); 

此解决方案仅处理静态类型DateTimeDateTime?属性。如果您有object值 - 有时包含DateTime值的值的值,或Dictionary<string, DateTime>extension data值包含DateTime值,则需要更复杂的解决方案。 (作为替代实现,您可以替代子类DefaultContractResolver.CreateObjectContract并使用JsonContractExtensions.AddDateProperties()对所需属性进行硬编码,但是我认为创建一个通用,流畅的可配置合约解析器以防万一需要插入时会更有趣稍后在不同的定制中)。

您可能想要cache the contract resolver以获得最佳性能。

样品.Net fiddle