Last active
March 18, 2018 22:21
-
-
Save rubenknuijver/6db6d8a1f23e37398c5d826c6532c8ef to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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