Keywords: JSON Serialization | Property Exclusion | .NET Framework | Newtonsoft.Json | System.Text.Json | Custom Contract Resolver | Runtime Filtering | Conditional Serialization
Abstract: This comprehensive technical article explores sophisticated methods for excluding properties during JSON serialization in .NET environments. Covering both Newtonsoft.Json and System.Text.Json libraries, it details attribute-based exclusion, runtime property filtering, and conditional serialization strategies. The article provides practical code examples for implementing custom contract resolvers, interface-based serialization, and conditional ignore attributes, addressing scenarios where developers need to maintain public property accessibility while controlling JSON output.
Introduction to JSON Property Exclusion
JSON serialization is a fundamental aspect of modern application development, particularly in web APIs and data exchange scenarios. A common requirement involves excluding specific properties from serialization while maintaining their accessibility within the application codebase. This article explores comprehensive strategies for property exclusion across different .NET JSON serialization frameworks.
Attribute-Based Property Exclusion
The most straightforward approach for property exclusion involves using serialization attributes. In Newtonsoft.Json, the [JsonIgnore] attribute provides a declarative way to exclude properties:
public class Vehicle
{
public string Model { get; set; }
public int ManufacturingYear { get; set; }
[JsonIgnore]
public DateTime LastMaintenanceDate { get; set; }
}
When serializing an instance of this class, the LastMaintenanceDate property will be completely omitted from the JSON output, while remaining accessible within the application logic.
Runtime Property Exclusion with Custom Resolvers
For scenarios requiring dynamic property exclusion or when attribute decoration is not feasible, custom contract resolvers offer a powerful solution. The following implementation demonstrates a reusable resolver for Newtonsoft.Json:
public class DynamicPropertyExclusionResolver : DefaultContractResolver
{
private readonly HashSet<string> _excludedProperties;
public DynamicPropertyExclusionResolver(IEnumerable<string> propertiesToExclude)
{
_excludedProperties = new HashSet<string>(propertiesToExclude, StringComparer.OrdinalIgnoreCase);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (_excludedProperties.Contains(property.PropertyName))
{
property.ShouldSerialize = instance => false;
}
return property;
}
}
Implementation example:
var product = new Product
{
Id = 1,
Name = "Laptop",
Price = 999.99m,
InternalCost = 650.00m
};
var settings = new JsonSerializerSettings
{
ContractResolver = new DynamicPropertyExclusionResolver(new[] { "InternalCost" })
};
string json = JsonConvert.SerializeObject(product, settings);
// Result: {"Id":1,"Name":"Laptop","Price":999.99}
System.Text.Json Conditional Exclusion
With the adoption of System.Text.Json in modern .NET applications, conditional property exclusion becomes more nuanced. The [JsonIgnore] attribute supports conditional exclusion through the Condition property:
public class InventoryItem
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int Id { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string Name { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Description { get; set; }
public decimal Price { get; set; }
}
This approach allows fine-grained control over serialization behavior based on property values and types.
Interface-Based Serialization in System.Text.Json
For .NET 6 and earlier versions, interface-based serialization provides an elegant solution for runtime property exclusion:
public interface IPublicProduct
{
int Id { get; }
string Name { get; }
decimal Price { get; }
}
public class Product : IPublicProduct
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public decimal InternalCost { get; set; }
public string SupplierInfo { get; set; }
}
// Serialization using interface
var product = new Product { /* initialization */ };
string json = JsonSerializer.Serialize<IPublicProduct>(product);
Performance Considerations and Best Practices
When implementing custom serialization logic, performance optimization is crucial. For custom contract resolvers, caching resolver instances is essential:
public static class ResolverCache
{
private static readonly ConcurrentDictionary<string, DynamicPropertyExclusionResolver> _resolvers
= new ConcurrentDictionary<string, DynamicPropertyExclusionResolver>();
public static DynamicPropertyExclusionResolver GetResolver(params string[] excludedProperties)
{
string key = string.Join(",", excludedProperties.OrderBy(p => p));
return _resolvers.GetOrAdd(key, k => new DynamicPropertyExclusionResolver(excludedProperties));
}
}
Advanced Exclusion Scenarios
For complex exclusion requirements, consider combining multiple strategies. The following example demonstrates conditional exclusion based on both property names and runtime conditions:
public class AdvancedExclusionResolver : DefaultContractResolver
{
private readonly Func<string, object, bool> _exclusionPredicate;
public AdvancedExclusionResolver(Func<string, object, bool> exclusionPredicate)
{
_exclusionPredicate = exclusionPredicate;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance =>
!_exclusionPredicate(property.PropertyName, instance);
return property;
}
}
Conclusion
Property exclusion in JSON serialization requires careful consideration of framework capabilities, performance implications, and maintainability. While attribute-based approaches provide simplicity, runtime exclusion strategies offer flexibility for dynamic requirements. Understanding the nuances between Newtonsoft.Json and System.Text.Json implementations ensures optimal serialization behavior across different .NET environments.