Исключить свойство из сериализации через пользовательский атрибут (json.net)

мне нужно иметь возможность контролировать, как / сериализуются ли определенные свойства класса. Самый простой случай -[ScriptIgnore]. Однако я хочу, чтобы эти атрибуты соблюдались только для этой конкретной ситуации сериализации, над которой я работаю, - если другие модули в приложении также хотят сериализовать эти объекты, ни один из этих атрибутов не должен мешать.

поэтому моя мысль заключается в использовании пользовательского атрибута MyAttribute на свойствах и инициализировать определенный экземпляр JsonSerializer с крючком, который знает, как искать этот атрибут.

на первый взгляд, я не вижу ни одной из доступных точек крючка в JSON.NET обеспечит PropertyInfo для текущего свойства, чтобы сделать такую проверку-только значение свойства. Я что-то упускаю? Или лучший способ подойти к этому?

6 ответов


у вас есть несколько вариантов. Я рекомендую вам прочитать Json.Net документация статья на эту тему прежде чем читать ниже.

в статье представлены два метода:

  1. создать метод, который возвращает bool значение на основе конвенции именования, которые Json.Net последуют ли сериализовать свойство.
  2. создайте пользовательский сопоставитель контрактов, который игнорирует свойство.

из двух, я пользуйся последним. Пропустить атрибуты вообще-используйте их только для игнорирования свойств во всех формах сериализации. Вместо этого создайте пользовательский сопоставитель контрактов, который игнорирует данное свойство, и используйте его только в том случае, если необходимо игнорировать свойство, оставляя другим пользователям класса возможность сериализовать свойство или нет по их собственной прихоти.

редактировать чтобы избежать гниения ссылок, я публикую код, о котором идет речь, из статьи

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 };

использовать


вот метод, основанный на превосходном контракте сериализатора 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);

Я не хочу устанавливать имена свойств в виде строк, если они когда-либо изменятся, это нарушит мой другой код.

у меня было несколько "режимов просмотра" на объектах, которые мне нужно было сериализовать, поэтому я закончил делать что-то вроде этого в распознавателе контрактов (режим просмотра, предоставляемый аргументом конструктора):

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;
}