Skip to content

Instantly share code, notes, and snippets.

@rubenknuijver
Last active March 18, 2018 22:21
Show Gist options
  • Save rubenknuijver/6db6d8a1f23e37398c5d826c6532c8ef to your computer and use it in GitHub Desktop.
Save rubenknuijver/6db6d8a1f23e37398c5d826c6532c8ef to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace Styx.Services.Json
{
public class JMap<T>
{
private IReadOnlyCollection<Func<JToken, T, T>> mapping;
private JMap()
: this(new Func<JToken, T, T>[0])
{
}
internal JMap(Func<JToken, T, T>[] args)
=> this.mapping = args;
internal JMap(IEnumerable<Func<JToken, T, T>> enumerable)
: this(enumerable.ToArray())
{
}
public T Map(T instance, JToken token)
=> this.mapping.Aggregate(instance, (ud, s) => s(token, ud));
public IEnumerable<T> Map(Func<T> factory, JArray array)
=> array.Select(token => this.mapping.Aggregate(factory(), (ud, s) => s(token, ud)));
public JMap<T> WithField<TResult>(Expression<Func<T, TResult>> expression, string alternateName = null)
=> new JMap<T>(new[] { this.MapToProperty(expression, alternateName) }.Union(this.mapping));
public JMap<T> WithCollection<TResult>(Expression<Func<T, ICollection<TResult>>> expression, string propertyName, Func<JMap<TResult>, JMap<TResult>> map)
where TResult : new()
=> WithCollection(expression, propertyName, map(JMap.For<TResult>()));
public JMap<T> WithCollection<TResult>(Expression<Func<T, ICollection<TResult>>> expression, string propertyName, JMap<TResult> childMap)
where TResult : new()
{
var addItem = CreateAddToCollectionAction<TResult>(expression);
Func<JArray, T, Func<TResult>, T> mapArray = (a, t, f) => childMap.Map(f, a).Aggregate(t, (c, d) => { addItem(c, d); return c; });
return Update((token, obj) => mapArray((JArray)token[propertyName], obj, () => new TResult()));
}
public JMap<T> WithElement<TResult>(Expression<Func<T, TResult>> expression, string propertyName, Func<JMap<TResult>, JMap<TResult>> map)
where TResult : class, new()
=> WithElement(expression, propertyName, map(JMap.For<TResult>()));
public JMap<T> WithElement<TResult>(Expression<Func<T, TResult>> expression, string propertyName, JMap<TResult> childMap)
where TResult : class, new()
{
var prop = PropertyInfoFromExpression(expression);
var set = AssignPropertyExpressionFor(expression).Compile();
Func<JToken, T, T> func = (token, obj) =>
{
set(obj, childMap.Map(new TResult(), token[propertyName]));
return obj;
};
return Update(func);
}
public JMap<T> WithConstant<TResult>(
Func<string, TResult> resolve,
Expression<Func<T, TResult>> expression)
=> Update(this.ConstantToProperty(resolve, expression));
public JMap<T> WithLookup<TResult>(
Func<string, TResult> resolve,
Expression<Func<T, TResult>> expression,
string fieldName)
=> Update(this.ResolveToProperty(resolve, expression, fieldName));
public JMap<T> WithLookup<TResult>(
Func<IDictionary<string, string>, TResult> resolve,
Expression<Func<T, TResult>> expression,
params string[] fieldNames)
=> Update(this.ResolveToProperty(resolve, expression, fieldNames));
public JMap<T> WithLookup<TResult>(
Func<object, TResult> resolve,
Expression<Func<T, TResult>> expression)
=> Update(this.ResolveCollectionToProperty(resolve, expression));
public JMap<T> WithExternalTask(Action<JToken, T> action)
=> Update((token, obj) =>
{
action(token, obj);
return obj;
});
JMap<T> Update(Func<JToken, T, T> func)
=> new JMap<T>(new[] { func }.Union(this.mapping));
static Action<T, TResult> CreateAddToCollectionAction<TResult>(Expression<Func<T, ICollection<TResult>>> expression)
{
var item = Expression.Parameter(typeof(TResult), "item");
var body = Expression.Call(expression.Body, "Add", Type.EmptyTypes, item);
var obj = expression.Parameters.Single();
var lambda = Expression.Lambda<Action<T, TResult>>(body, obj, item);
return lambda.Compile();
}
static Expression<Action<TEntity, TProperty>> AssignPropertyExpressionFor<TEntity, TProperty>(
Expression<Func<TEntity, TProperty>> selector
)
{
var valueParam = Expression.Parameter(typeof(TProperty));
var body = Expression.Assign(selector.Body, valueParam);
return Expression.Lambda<Action<TEntity, TProperty>>(
body,
selector.Parameters.Single(),
valueParam);
}
static PropertyInfo PropertyInfoFromExpression<TResult>(Expression<Func<T, TResult>> expression)
=> (PropertyInfo)((MemberExpression)expression.Body).Member;
Func<JToken, T, T> ConstantToProperty<TResult>(
Func<string, TResult> resolve,
Expression<Func<T, TResult>> expression)
{
var prop = PropertyInfoFromExpression(expression);
return AssignPropertyExpressionFor(expression)
.ApplyTokenValueToProperty((_) => resolve(prop.Name));
}
Func<JToken, T, T> MapToProperty<TResult>(
Expression<Func<T, TResult>> expression,
string alternateName = null)
{
var prop = PropertyInfoFromExpression(expression);
var key = alternateName ?? prop.Name;
return AssignPropertyExpressionFor(expression)
.ApplyTokenValueToProperty(
(token) => token is JObject obj
? new[] { obj.GetValue(key) }.Where(w => w != null).Select(s => s.ToObject<TResult>()).SingleOrDefault()
: token is JValue val
? val.ToObject<TResult>()
: default(TResult)
);
}
Func<JToken, T, T> ResolveToProperty<TResult>(
Func<string, TResult> resolve,
Expression<Func<T, TResult>> expression,
string fieldName)
=> AssignPropertyExpressionFor(expression)
.ApplyTokenValueToProperty((token) => resolve(token[fieldName].ToObject<string>()));
Func<JToken, T, T> ResolveToProperty<TResult>(
Func<IDictionary<string, string>, TResult> resolve,
Expression<Func<T, TResult>> expression,
params string[] fieldNames)
=> AssignPropertyExpressionFor(expression)
.ApplyTokenValueToProperty((token) => resolve(fieldNames.ToDictionary(k => k, v => token[v].ToObject<string>())));
Func<JToken, T, T> ResolveCollectionToProperty<TResult>(
Func<object, TResult> resolve,
Expression<Func<T, TResult>> expression)
=> AssignPropertyExpressionFor(expression)
.ApplyTokenValueToProperty((token) => token is JValue key ? resolve(key.Value) : default(TResult));
}
public static class JMap
{
public static JMap<T> For<T>(params Func<JToken, T, T>[] args)
=> new JMap<T>(args);
internal static Func<JToken, T, T> ApplyTokenValueToProperty<T, TResult>(
this Expression<Action<T, TResult>> expression,
Func<JToken, TResult> func)
=> ApplyTokenValueToProperty(expression.Compile(), func);
private static Func<JToken, T, T> ApplyTokenValueToProperty<T, TResult>(
Action<T, TResult> action,
Func<JToken, TResult> func)
=> (token, obj) =>
{
action(obj, func(token));
return obj;
};
}
class SampleCode
{
public SampleCode(IRoleRepository roleRepository, JArray importData)
{
// Example for creating map JToken to Object graph
var roleNameMap = roleRepository.All().ToDictionary(k => k.RoleName, v => v.Id);
var userMap = JMap.For<User>()
.WithField(p => p.UserName)
.WithField(p => p.ChangeDate)
.WithField(p => p.CreateDate)
//.RegisterConstant(n => DateTime.Today, p => p.ChangeDate)
.WithElement(
p => p.UserProfile,
"UserProfile",
map => map
.WithField(t => t.EmailAddress)
.WithField(t => t.GivenName)
.WithField(t => t.Identifier)
.WithField(t => t.Name)
.WithField(t => t.SurName)
)
.WithCollection(
p => p.UserRoles,
"Roles",
map => map
.WithLookup((key) => roleNameMap[key as string], p => p.RoleId)
);
var user = userMap.Map(() => new User(), importData);
}
public interface IRoleRepository
{
IEnumerable<Role> All();
}
public class User
{
public int Id { get; set; } // (Primary key)
public string UserName { get; set; }
public string Password { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ChangeDate { get; set; }
public ICollection<UserRole> UserRoles { get; set; }
public UserProfile UserProfile { get; set; }
public User()
{
CreateDate = DateTime.Now;
ChangeDate = DateTime.Now;
UserRoles = new HashSet<UserRole>();
}
}
public class UserProfile
{
public string EmailAddress { get; set; }
public string GivenName { get; set; }
public string Identifier { get; set; }
public string Name { get; set; }
public string SurName { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
public DateTime CreateDate { get; set; }
}
public class Role
{
public int Id { get; set; } // (Primary key)
public string RoleName { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment