Исключить свойство из сериализации через пользовательский атрибут (json.net)
Мне нужно иметь возможность контролировать, как/или сериализуются ли определенные свойства в классе. Простейшим случаем является [ScriptIgnore]
. Тем не менее, я хочу, чтобы эти атрибуты были выполнены для этой конкретной ситуации сериализации, над которой я работаю, - если другие модули, расположенные ниже по потоку в приложении, также хотят сериализовать эти объекты, ни один из этих атрибутов не должен мешать.
Итак, я думал использовать пользовательский атрибут MyAttribute
для свойств и инициализировать конкретный экземпляр JsonSerializer с помощью крючка, который знает, как искать этот атрибут.
На первый взгляд, я не вижу, чтобы какая-либо из доступных точек привязки в JSON.NET предоставила PropertyInfo
для текущего свойства для такой проверки - только значение свойства. Я что-то упускаю? Или лучший способ приблизиться к этому?
Ответы
Ответ 1
У вас есть несколько вариантов. Я рекомендую вам ознакомиться с документацией 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;
}
}
Ответ 2
Здесь используется универсальный многоразовый "игнорировать свойство" на основе принятого ответа:
/// <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 };
Ответ 3
Используйте атрибут JsonIgnore
.
Например, чтобы исключить Id
:
public class Person {
[JsonIgnore]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Ответ 4
Вот метод, основанный на превосходном контракте на серийный номер 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;
}
Теперь вы можете игнорировать свойства легко и плавно:
contract.Ignore<Node>(node => node.NextNode)
.Ignore<Node>(node => node.AvailableNodes);
Ответ 5
Я не хочу устанавливать имена свойств как строки, в случае, если они когда-либо меняются, это сломает мой другой код.
У меня было несколько "режимов просмотра" для объектов, которые мне нужны для сериализации, поэтому в конечном итоге я делал что-то подобное в разрешении контракта (режим просмотра, предоставляемый аргументом конструктора):
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);
}
Недостатком этого будет бит отражения в преобразователе, но я считаю, что стоит иметь более удобный код.
Ответ 6
У меня были хорошие результаты с сочетанием обоих слов 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;
}