Exclude property from serialization via custom attribute (json.net)

C#Serializationjson.netCustom Attributes

C# Problem Overview


I need to be able to control how/whether certain properties on a class are serialized. The simplest case is [ScriptIgnore]. However, I only want these attributes to be honored for this one specific serialization situation I am working on - if other modules downstream in the application also want to serialize these objects, none of these attributes should get in the way.

So my thought is to use a custom attribute MyAttribute on the properties, and initialize the specific instance of JsonSerializer with a hook that knows to look for that attribute.

At first glance, I don't see any of the available hook points in JSON.NET will provide the PropertyInfo for the current property to do such an inspection - only the property's value. Am I missing something? Or a better way to approach this?

C# Solutions


Solution 1 - C#

Here's a generic reusable "ignore property" resolver based on the accepted answer:

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

And usage:

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

Solution 2 - C#

Use the JsonIgnore attribute.

For example, to exclude Id:

public class Person {
	[JsonIgnore]
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
}

Solution 3 - C#

You have a few options. I recommend you read the Json.Net documentation article on the subject before reading below.

The article presents two methods:

  1. Create a method that returns a bool value based on a naming convention that Json.Net will follow to determine whether or not to serialize the property.
  2. Create a custom contract resolver that ignores the property.

Of the two, I favor the latter. Skip attributes altogether -- only use them to ignore properties across all forms of serialization. Instead, create a custom contract resolver that ignores the property in question, and only use the contract resolver when you want to ignore the property, leaving other users of the class free to serialize the property or not at their own whim.

Edit To avoid link rot, I'm posting the code in question from the article

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

Solution 4 - C#

Here is a method based on drzaus' excellent serializer contract which uses lambda expressions. Simply add it to the same class. After all, who doesn't prefer the compiler to do the checking for them?

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

You can now ignore properties easily and fluently:

contract.Ignore<Node>(node => node.NextNode)
    .Ignore<Node>(node => node.AvailableNodes);

Solution 5 - C#

I don't care to set the property names as strings, in case they ever change it would break my other code.

I had several "view modes" on the objects I needed to serialized, so I ended up doing something like this in the contract resolver (view mode provided by constructor argument):

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

Where my objects look like this:

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

The downside to this would be the bit of reflection in the resolver, but I think it's worth it to have more maintainable code.

Solution 6 - C#

I had good results with the combination of both drzaus and Steve Rukuts answers. However, I face a problem when I set JsonPropertyAttribute with a different name or caps for the property. For example:

[JsonProperty("username")]
public string Username { get; set; }

Include UnderlyingName into consideration solves the problem:

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

Solution 7 - C#

If you are willing to use F# (or simply use an API not optimized for C#), the FSharp.JsonSkippable library allows you to control in a simple and strongly typed manner whether to include a given property when serializing (and determine whether a property was included when deserializing), and moreover, to control/determine exclusion separately of nullability. (Full disclosure: I'm the author of the library.)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRex MView Question on Stackoverflow
Solution 1 - C#drzausView Answer on Stackoverflow
Solution 2 - C#Ramón EstebanView Answer on Stackoverflow
Solution 3 - C#RandolphoView Answer on Stackoverflow
Solution 4 - C#Steve RukutsView Answer on Stackoverflow
Solution 5 - C#frattaroView Answer on Stackoverflow
Solution 6 - C#Baron Ch'ngView Answer on Stackoverflow
Solution 7 - C#cmeerenView Answer on Stackoverflow