クラスの特定のプロパティをシリアル化する方法/制御する必要があります。最も単純なケースは[ScriptIgnore]
。しかし、私が取り組んでいるこの特定のシリアル化状況でのみこれらの属性を尊重する必要があります-アプリケーションの他の下流のモジュールもこれらのオブジェクトをシリアル化する場合、これらの属性は邪魔になりません。
したがって、私の考えは、プロパティでMyAttribute
カスタム属性を使用し、その属性を探すことを知っているフックでJsonSerializerの特定のインスタンスを初期化することです。
一見したところ、JSON.NETで利用可能なフックポイントのいずれも、現在のプロパティにPropertyInfo
を提供してこのような検査を行うことはありません。プロパティの値のみです。何か不足していますか?またはこれにアプローチするより良い方法は?
いくつかのオプションがあります。以下を読む前に、Json.Netのドキュメント 主題に関する記事 を読むことをお勧めします。
この記事には2つの方法があります。
bool
値を返すメソッドを作成します。2つのうち、後者の方が好きです。属性をすべてスキップします。すべての形式のシリアル化でプロパティを無視する場合にのみ使用します。代わりに、問題のプロパティを無視するカスタムコントラクトリゾルバーを作成し、プロパティを無視する場合にのみコントラクトリゾルバーを使用します。クラスの他のユーザーは、プロパティをシリアル化するか、気まぐれにしないでください。
Editリンクの腐敗を避けるために、私は記事から問題のコードを投稿しています
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;
}
}
受け入れられた答え に基づいた一般的な再利用可能な「プロパティを無視する」リゾルバを次に示します。
/// <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; }
}
以下は、ラムダ式を使用するdrzausの優れたシリアライザーコントラクトに基づくメソッドです。同じクラスに追加するだけです。結局のところ、コンパイラがそれらのチェックを行うことを好まないのは誰ですか?
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;
}
プロパティを簡単かつ流fluentに無視できるようになりました。
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; }
UnderlyingNameを考慮に入れて問題を解決します。
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 ライブラリを使用すると、シリアル化時に特定のプロパティを含めるかどうかを単純かつ厳密に型指定された方法で制御できます(および、シリアル化解除時にプロパティが含まれているかどうかを判断します)、さらに、null許容性とは別に除外を制御/決定します。 (完全開示:私は図書館の著者です。)