Skip to content

Instantly share code, notes, and snippets.

@weitzhandler
Last active May 21, 2017 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weitzhandler/9dd5575bea2393a8bff3165a0c73a8eb to your computer and use it in GitHub Desktop.
Save weitzhandler/9dd5575bea2393a8bff3165a0c73a8eb to your computer and use it in GitHub Desktop.
Json.NET property map filter resolver
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Newtonsoft.Json.Serialization
{
public class PropertyFilterResolver : DefaultContractResolver
{
const string _Err = "A type can be either in the include list or the ignore list.";
readonly PropertyMap _IgnorePropertiesMap = new PropertyMap();
readonly PropertyMap _IncludePropertiesMap = new PropertyMap();
readonly Dictionary<string, bool> GlobalProperties = new Dictionary<string, bool>();
/// <summary>
/// This list overrides properties in <see cref="GlobalIncludedProperties"/>.
/// </summary>
public PropertyFilterResolver SetIgnoredProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null) return this;
if (_IncludePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
_IgnorePropertiesMap[typeof(T)] = propertyAccessors.Select(GetPropertyName);
return this;
}
/// <summary>
/// This list overrides properties in <see cref="GlobalIgnoredProperties"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyAccessors"></param>
/// <returns></returns>
public PropertyFilterResolver SetIncludedProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null) return this;
if (_IgnorePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
_IncludePropertiesMap[typeof(T)] = propertyAccessors.Select(GetPropertyName);
return this;
}
public PropertyFilterResolver Ignore(string globalIgnore)
{
GlobalProperties[globalIgnore] = false;
return this;
}
/// <summary>
/// Will act as global black list for all types, excecpt for those in the property maps,
/// via <see cref="SetIncludedProperties"/>
/// or <see cref="SetIgnoredProperties"/>.
/// </summary>
public PropertyFilterResolver Include(string globalInclude)
{
GlobalProperties[globalInclude] = true;
return this;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
IEnumerable<string> map;
var isIgnoreList = (map = _IgnorePropertiesMap[type]) != null;
if (isIgnoreList || (map = _IncludePropertiesMap[type]) != null)
{
var globals = GlobalProperties.Where(kv => kv.Value != isIgnoreList);
map = map
.Concat(globals.Select(kv => kv.Key))
.Distinct();
return properties
.Where(jp => map.Contains(jp.PropertyName) != isIgnoreList)
.ToArray();
}
else
{
return properties
.Where(p =>
{
var include = true;
var globalIncludes = GlobalProperties
.Where(kv => kv.Value == include);
var globalProperties = globalIncludes
.Select(kv => kv.Key);
if (globalIncludes.Any())
return globalProperties.Contains(p.PropertyName);
else
{
include = false;
return !p.Ignored && !globalProperties.Contains(p.PropertyName);
}
})
.ToArray();
}
}
string GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
var body = propertyLambda.Body;
if (!(body is MemberExpression member) &&
!(body is UnaryExpression unary && (member = unary.Operand as MemberExpression) != null))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");
var type = typeof(TSource);
if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' refers to a property that is not from type '{type}'.");
return propInfo.Name;
}
private class PropertyMap
{
readonly Dictionary<Type, HashSet<string>> Map = new Dictionary<Type, HashSet<string>>();
public IEnumerable<string> this[Type type]
{
get
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (Map.TryGetValue(type, out HashSet<string> value))
return value;
var result = Map
.Where(kv => kv.Key.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
.SelectMany(kv => kv.Value ?? Enumerable.Empty<string>());
return Map[type] = result.Any() ? new HashSet<string>(result) : null;
}
set
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (value == null) throw new ArgumentNullException(nameof(value));
if (Map.TryGetValue(type, out var existing))
foreach (var property in value)
existing.Add(property);
else
Map[type] = new HashSet<string>(value);
}
}
public bool ContainsKey(Type type) => Map.ContainsKey(type);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment