我需要能够控制如何/是否序列化类上的某些属性。最简单的情况是 [ScriptIgnore]
.但是,我只希望这些属性适用于我正在处理的这种特定序列化情况 - 如果应用程序中下游的其他模块也想要序列化这些对象,则这些属性都不应妨碍。
所以我的想法是在属性上使用自定义属性MyAttribute
,并使用知道查找该属性的钩子初始化 JsonSerializer 的特定实例。
乍一看,我没有看到任何可用的钩点 JSON.NET 将为当前属性提供执行此类检查的PropertyInfo
- 只有属性的值。我错过了什么吗?还是更好的方法来解决这个问题?
下面是一个基于接受答案的通用可重用"忽略属性"解析器:
/// <summary>
/// Special JsonConvert resolver that allows you to ignore properties. See https://stackoverflow.com/a/13588192/1037948
/// </summary>
public class IgnorableSerializerContractResolver : DefaultContractResolver {
protected readonly Dictionary<Type, HashSet<string>> Ignores;
public IgnorableSerializerContractResolver() {
this.Ignores = new Dictionary<Type, HashSet<string>>();
}
/// <summary>
/// Explicitly ignore the given property(s) for the given type
/// </summary>
/// <param name="type"></param>
/// <param name="propertyName">one or more properties to ignore. Leave empty to ignore the type entirely.</param>
public void Ignore(Type type, params string[] propertyName) {
// start bucket if DNE
if (!this.Ignores.ContainsKey(type)) this.Ignores[type] = new HashSet<string>();
foreach (var prop in propertyName) {
this.Ignores[type].Add(prop);
}
}
/// <summary>
/// Is the given property for the given type ignored?
/// </summary>
/// <param name="type"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
public bool IsIgnored(Type type, string propertyName) {
if (!this.Ignores.ContainsKey(type)) return false;
// if no properties provided, ignore the type entirely
if (this.Ignores[type].Count == 0) return true;
return this.Ignores[type].Contains(propertyName);
}
/// <summary>
/// The decision logic goes here
/// </summary>
/// <param name="member"></param>
/// <param name="memberSerialization"></param>
/// <returns></returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (this.IsIgnored(property.DeclaringType, property.PropertyName)
// need to check basetype as well for EF -- @per comment by user576838
|| this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)) {
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
和用法:
var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
使用 JsonIgnore
属性。
例如,要排除Id
:
public class Person {
[JsonIgnore]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
你有几个选择。我建议您在阅读以下内容之前阅读有关该主题的 Json.Net 文档文章。
本文介绍了两种方法:
- 创建一个方法,该方法基于命名约定返回
bool
值,Json.Net 将遵循该约定来确定是否序列化属性。 - 创建忽略该属性的自定义协定解析程序。
在两者中,我赞成后者。完全跳过属性 -- 仅使用它们来忽略所有形式的序列化中的属性。相反,创建一个忽略相关属性的自定义协定解析程序,并且仅在要忽略该属性时才使用协定解析程序,从而使类的其他用户可以随意序列化该属性或不随心所欲地序列化该属性。
编辑 为避免链接腐烂,我正在发布文章中有问题的代码
public class ShouldSerializeContractResolver : DefaultContractResolver
{
public new static readonly ShouldSerializeContractResolver Instance =
new ShouldSerializeContractResolver();
protected override JsonProperty CreateProperty( MemberInfo member,
MemberSerialization memberSerialization )
{
JsonProperty property = base.CreateProperty( member, memberSerialization );
if( property.DeclaringType == typeof(Employee) &&
property.PropertyName == "Manager" )
{
property.ShouldSerialize = instance =>
{
// replace this logic with your own, probably just
// return false;
Employee e = (Employee)instance;
return e.Manager != e;
};
}
return property;
}
}
这是一个基于 drzaus 优秀的序列化程序合约的方法,它使用 lambda 表达式。只需将其添加到同一类即可。毕竟,谁不喜欢编译器为他们做检查?
public IgnorableSerializerContractResolver Ignore<TModel>(Expression<Func<TModel, object>> selector)
{
MemberExpression body = selector.Body as MemberExpression;
if (body == null)
{
UnaryExpression ubody = (UnaryExpression)selector.Body;
body = ubody.Operand as MemberExpression;
if (body == null)
{
throw new ArgumentException("Could not get property name", "selector");
}
}
string propertyName = body.Member.Name;
this.Ignore(typeof (TModel), propertyName);
return this;
}
现在,您可以轻松流畅地忽略属性:
contract.Ignore<Node>(node => node.NextNode)
.Ignore<Node>(node => node.AvailableNodes);
在乎将属性名称设置为字符串,以防他们更改它会破坏我的其他代码。
我在需要序列化的对象上有几个"视图模式",所以我最终在合约解析器(构造函数参数提供的视图模式)中做了这样的事情:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (viewMode == ViewModeEnum.UnregisteredCustomer && member.GetCustomAttributes(typeof(UnregisteredCustomerAttribute), true).Length == 0)
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
我的对象如下所示:
public interface IStatement
{
[UnregisteredCustomer]
string PolicyNumber { get; set; }
string PlanCode { get; set; }
PlanStatus PlanStatus { get; set; }
[UnregisteredCustomer]
decimal TotalAmount { get; }
[UnregisteredCustomer]
ICollection<IBalance> Balances { get; }
void SetBalances(IBalance[] balances);
}
这样做的缺点是解析器中的一点反射,但我认为拥有更多可维护的代码是值得的。
drzaus和Steve Rukuts的答案结合起来,取得了很好的结果。但是,当我为属性设置具有不同名称或大写字母的 JsonPropertyAttribute 时,我遇到了一个问题。例如:
[JsonProperty("username")]
public string Username { get; set; }
将底层名称包括在内可以解决问题:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (this.IsIgnored(property.DeclaringType, property.PropertyName)
|| this.IsIgnored(property.DeclaringType, property.UnderlyingName)
|| this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)
|| this.IsIgnored(property.DeclaringType.BaseType, property.UnderlyingName))
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
如果您愿意使用 F#(或者只是使用未针对 C# 优化的 API),FSharp.JsonSkippable 库允许您以简单且强类型的方式控制在序列化时是否包含给定属性(并确定反序列化时是否包含属性),此外,还可以单独控制/确定可空性的排除项。(完全披露:我是图书馆的作者。