PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Server/DeviceHive.API/Mapping/JsonMapper.cs

https://github.com/oryol/devicehive-.net
C# | 375 lines | 300 code | 66 blank | 9 comment | 41 complexity | 591ae1058ce54b82cee31caa501eefac MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. using Newtonsoft.Json.Linq;
  7. using DeviceHive.Data.Repositories;
  8. namespace DeviceHive.API.Mapping
  9. {
  10. public class JsonMapper<T> : IJsonMapper, IJsonMapper<T>
  11. {
  12. private JsonMapperConfiguration<T> _configuration;
  13. #region Constructor
  14. public JsonMapper(JsonMapperConfiguration<T> configuration)
  15. {
  16. _configuration = configuration;
  17. }
  18. #endregion
  19. #region IJsonMapper Members
  20. Type IJsonMapper.EntityType
  21. {
  22. get { return typeof(T); }
  23. }
  24. IList<IJsonMapperEntry> IJsonMapper.Entries
  25. {
  26. get { return _configuration.Entries.Cast<IJsonMapperEntry>().ToList(); }
  27. }
  28. JObject IJsonMapper.Map(object entity)
  29. {
  30. if (!typeof(T).IsInstanceOfType(entity))
  31. throw new ArgumentException(string.Format("Entity type is invalid! Expected: {0}, Actual: {1}", typeof(T), entity.GetType()), "entity");
  32. return Map((T)entity);
  33. }
  34. object IJsonMapper.Map(JObject json)
  35. {
  36. return Map(json);
  37. }
  38. void IJsonMapper.Apply(object entity, JObject json)
  39. {
  40. if (!typeof(T).IsInstanceOfType(entity))
  41. throw new ArgumentException(string.Format("Entity type is invalid! Expected: {0}, Actual: {1}", typeof(T), entity.GetType()), "entity");
  42. Apply((T)entity, json);
  43. }
  44. #endregion
  45. #region IJsonMapper<T> Members
  46. public JObject Map(T entity)
  47. {
  48. if (entity == null)
  49. throw new ArgumentNullException("entity");
  50. var json = new JObject();
  51. foreach (var entry in _configuration.Entries)
  52. {
  53. if (entry.Mode == JsonMapperEntryMode.OneWay || entry.Mode == JsonMapperEntryMode.TwoWay)
  54. entry.MapToJson(entity, json);
  55. }
  56. return json;
  57. }
  58. public T Map(JObject json)
  59. {
  60. var entity = (T)Activator.CreateInstance(typeof(T));
  61. Apply(entity, json);
  62. return entity;
  63. }
  64. public void Apply(T entity, JObject json)
  65. {
  66. if (entity == null)
  67. throw new ArgumentNullException("entity");
  68. if (json == null)
  69. throw new ArgumentNullException("json");
  70. foreach (var entry in _configuration.Entries)
  71. {
  72. if (entry.Mode == JsonMapperEntryMode.OneWayToSource || entry.Mode == JsonMapperEntryMode.TwoWay)
  73. entry.MapToEntity(json, entity);
  74. }
  75. }
  76. #endregion
  77. }
  78. public class JsonMapperConfiguration<T>
  79. {
  80. private JsonMapperManager _manager;
  81. private DataContext _dataContext;
  82. #region Public Properties
  83. public List<JsonMapperEntry<T>> Entries { get; private set; }
  84. #endregion
  85. #region Constructor
  86. public JsonMapperConfiguration(JsonMapperManager manager)
  87. {
  88. if (manager == null)
  89. throw new ArgumentNullException("manager");
  90. _manager = manager;
  91. _dataContext = manager.DataContext;
  92. Entries = new List<JsonMapperEntry<T>>();
  93. }
  94. #endregion
  95. #region Public Methods
  96. public JsonMapperConfiguration<T> Property(Expression<Func<T, object>> entityPropertyExpression, string jsonProperty, JsonMapperEntryMode mode = JsonMapperEntryMode.TwoWay)
  97. {
  98. if (entityPropertyExpression == null)
  99. throw new ArgumentNullException("entityPropertyExpression");
  100. if (string.IsNullOrEmpty(jsonProperty))
  101. throw new ArgumentException("jsonProperty is null or empty!", "jsonProperty");
  102. var entity = Expression.Parameter(typeof(T), "entity");
  103. var json = Expression.Parameter(typeof(JObject), "json");
  104. var entityProperty = GetProperty(entityPropertyExpression);
  105. // create MapToJson action
  106. var jsonAddProperty = Expression.Call(json,
  107. typeof(JObject).GetMethod("Add", new[] { typeof(JProperty) }),
  108. Expression.New(typeof(JProperty).GetConstructor(new[] { typeof(string), typeof(object) }),
  109. Expression.Constant(jsonProperty),
  110. Expression.Invoke(entityPropertyExpression, entity)));
  111. var mapToJsonLabmda = Expression.Lambda<Action<T, JObject>>(jsonAddProperty, entity, json);
  112. // create MapToEntity action
  113. var jsonPropertyCall = Expression.Call(json, typeof(JObject).GetMethod("Property"), Expression.Constant(jsonProperty));
  114. var entityAssign = Expression.IfThen(
  115. Expression.NotEqual(jsonPropertyCall, Expression.Constant(null, typeof(JProperty))),
  116. Expression.Assign(
  117. Expression.Property(entity, entityProperty),
  118. Expression.Convert(Expression.Property(jsonPropertyCall, "Value"), entityProperty.PropertyType)));
  119. var mapToEntityLabmda = Expression.Lambda<Action<JObject, T>>(entityAssign, json, entity);
  120. Entries.Add(new JsonMapperEntry<T>(mode, jsonProperty, entityProperty, mapToJsonLabmda.Compile(), mapToEntityLabmda.Compile()));
  121. return this;
  122. }
  123. public JsonMapperConfiguration<T> Property(Expression<Func<T, Guid>> entityPropertyExpression, string jsonProperty, JsonMapperEntryMode mode = JsonMapperEntryMode.TwoWay)
  124. {
  125. if (entityPropertyExpression == null)
  126. throw new ArgumentNullException("entityPropertyExpression");
  127. if (string.IsNullOrEmpty(jsonProperty))
  128. throw new ArgumentException("jsonProperty is null or empty!", "jsonProperty");
  129. var entity = Expression.Parameter(typeof(T), "entity");
  130. var json = Expression.Parameter(typeof(JObject), "json");
  131. var entityProperty = GetProperty(entityPropertyExpression);
  132. // create MapToJson action
  133. var jsonAddProperty = Expression.Call(json,
  134. typeof(JObject).GetMethod("Add", new[] { typeof(JProperty) }),
  135. Expression.New(typeof(JProperty).GetConstructor(new[] { typeof(string), typeof(object) }),
  136. Expression.Constant(jsonProperty),
  137. Expression.Call(Expression.Invoke(entityPropertyExpression, entity), typeof(Guid).GetMethod("ToString", new Type[] { }))));
  138. var mapToJsonLabmda = Expression.Lambda<Action<T, JObject>>(jsonAddProperty, entity, json);
  139. // create MapToEntity action
  140. var jsonPropertyCall = Expression.Call(json, typeof(JObject).GetMethod("Property"), Expression.Constant(jsonProperty));
  141. var entityAssign = Expression.IfThen(
  142. Expression.NotEqual(jsonPropertyCall, Expression.Constant(null, typeof(JProperty))),
  143. Expression.Assign(
  144. Expression.Property(entity, entityProperty),
  145. Expression.Call(null, typeof(Guid).GetMethod("Parse", BindingFlags.Public | BindingFlags.Static),
  146. Expression.Convert(Expression.Property(jsonPropertyCall, "Value"), typeof(string)))));
  147. var mapToEntityLabmda = Expression.Lambda<Action<JObject, T>>(entityAssign, json, entity);
  148. Entries.Add(new JsonMapperEntry<T>(mode, jsonProperty, entityProperty, mapToJsonLabmda.Compile(), mapToEntityLabmda.Compile()));
  149. return this;
  150. }
  151. public JsonMapperConfiguration<T> RawJsonProperty(Expression<Func<T, string>> entityPropertyExpression, string jsonProperty, JsonMapperEntryMode mode = JsonMapperEntryMode.TwoWay)
  152. {
  153. if (entityPropertyExpression == null)
  154. throw new ArgumentNullException("entityPropertyExpression");
  155. if (string.IsNullOrEmpty(jsonProperty))
  156. throw new ArgumentException("jsonProperty is null or empty!", "jsonProperty");
  157. var entity = Expression.Parameter(typeof(T), "entity");
  158. var json = Expression.Parameter(typeof(JObject), "json");
  159. var entityProperty = GetProperty(entityPropertyExpression);
  160. // create MapToJson action
  161. var jsonAddProperty = Expression.Call(json,
  162. typeof(JObject).GetMethod("Add", new[] { typeof(JProperty) }),
  163. Expression.New(typeof(JProperty).GetConstructor(new[] { typeof(string), typeof(object) }),
  164. Expression.Constant(jsonProperty),
  165. Expression.Condition(
  166. Expression.NotEqual(Expression.Invoke(entityPropertyExpression, entity), Expression.Constant(null, typeof(string))),
  167. Expression.Call(null, typeof(JToken).GetMethod("Parse"), Expression.Invoke(entityPropertyExpression, entity)),
  168. Expression.Constant(null, typeof(JToken)))));
  169. var mapToJsonLabmda = Expression.Lambda<Action<T, JObject>>(jsonAddProperty, entity, json);
  170. // create MapToEntity action
  171. var jsonPropertyCall = Expression.Call(json, typeof(JObject).GetMethod("Property"), Expression.Constant(jsonProperty));
  172. var entityAssign = Expression.IfThen(
  173. Expression.NotEqual(jsonPropertyCall, Expression.Constant(null, typeof(JProperty))),
  174. Expression.Assign(
  175. Expression.Property(entity, entityProperty),
  176. Expression.Condition(
  177. Expression.AndAlso(
  178. Expression.TypeIs(Expression.Property(jsonPropertyCall, "Value"), typeof(JValue)),
  179. Expression.Equal(
  180. Expression.Property(Expression.Convert(Expression.Property(jsonPropertyCall, "Value"), typeof(JValue)), "Value"),
  181. Expression.Constant(null, typeof(object)))),
  182. Expression.Constant(null, typeof(string)),
  183. Expression.Call(Expression.Property(jsonPropertyCall, "Value"), typeof(JToken).GetMethod("ToString", new Type[] { })))));
  184. var mapToEntityLabmda = Expression.Lambda<Action<JObject, T>>(entityAssign, json, entity);
  185. Entries.Add(new JsonMapperEntry<T>(mode, jsonProperty, entityProperty, mapToJsonLabmda.Compile(), mapToEntityLabmda.Compile()));
  186. return this;
  187. }
  188. public JsonMapperConfiguration<T> ReferenceProperty<TRef>(Expression<Func<T, TRef>> entityPropertyExpression, string jsonProperty, JsonMapperEntryMode mode = JsonMapperEntryMode.TwoWay)
  189. {
  190. if (entityPropertyExpression == null)
  191. throw new ArgumentNullException("entityPropertyExpression");
  192. if (string.IsNullOrEmpty(jsonProperty))
  193. throw new ArgumentException("jsonProperty is null or empty!", "jsonProperty");
  194. var entity = Expression.Parameter(typeof(T), "entity");
  195. var json = Expression.Parameter(typeof(JObject), "json");
  196. var entityProperty = GetProperty(entityPropertyExpression);
  197. var mapper = _manager.GetMapper<TRef>();
  198. var repository = _dataContext.Get<TRef>();
  199. // create MapToJson action
  200. var jsonAddProperty = Expression.Call(json,
  201. typeof(JObject).GetMethod("Add", new[] { typeof(JProperty) }),
  202. Expression.New(typeof(JProperty).GetConstructor(new[] { typeof(string), typeof(object) }),
  203. Expression.Constant(jsonProperty),
  204. Expression.Condition(
  205. Expression.NotEqual(Expression.Invoke(entityPropertyExpression, entity), Expression.Constant(null, typeof(TRef))),
  206. Expression.Call(Expression.Constant(mapper), typeof(IJsonMapper<>).MakeGenericType(typeof(TRef))
  207. .GetMethod("Map", new[] { typeof(TRef) }), Expression.Invoke(entityPropertyExpression, entity)),
  208. Expression.Constant(null, typeof(JObject)))));
  209. var mapToJsonLabmda = Expression.Lambda<Action<T, JObject>>(jsonAddProperty, entity, json);
  210. // create MapToEntity action - use delegate for simplicity
  211. Action<JObject, T> mapToEntityLabmda = (json2, entity2) =>
  212. {
  213. var jProperty = json2.Property(jsonProperty);
  214. if (jProperty == null)
  215. return;
  216. TRef refValue = default(TRef);
  217. var jValue = jProperty.Value as JValue;
  218. if (jValue != null && (jValue.Value is long))
  219. {
  220. // search object by ID
  221. refValue = repository.Get((int)jValue);
  222. if (refValue == null)
  223. {
  224. throw new JsonMapperException(string.Format("ID of the reference property is not found! " +
  225. "Property: {0}, ID: {1}", jsonProperty, jValue));
  226. }
  227. entityProperty.SetValue(entity2, refValue, null);
  228. }
  229. else
  230. {
  231. throw new JsonMapperException(string.Format("The required reference property has invalid format! " +
  232. "Property: {0}", jsonProperty));
  233. }
  234. entityProperty.SetValue(entity2, refValue, null);
  235. };
  236. Entries.Add(new JsonMapperEntry<T>(mode, jsonProperty, entityProperty, mapToJsonLabmda.Compile(), mapToEntityLabmda));
  237. return this;
  238. }
  239. #endregion
  240. #region Private Methods
  241. private PropertyInfo GetProperty<TRef>(Expression<Func<T, TRef>> entityProperty)
  242. {
  243. var body = ((LambdaExpression)entityProperty).Body;
  244. if (body.NodeType == ExpressionType.Convert)
  245. {
  246. body = ((UnaryExpression)body).Operand;
  247. }
  248. return (PropertyInfo)((MemberExpression)body).Member;
  249. }
  250. #endregion
  251. }
  252. public class JsonMapperEntry<T> : IJsonMapperEntry
  253. {
  254. private Action<T, JObject> _mapToJsonAction;
  255. private Action<JObject, T> _mapToEntityAction;
  256. #region IJsonMapperEntry Members
  257. public JsonMapperEntryMode Mode { get; private set; }
  258. public string JsonProperty { get; private set; }
  259. public PropertyInfo EntityProperty { get; private set; }
  260. #endregion
  261. #region Constructor
  262. public JsonMapperEntry(JsonMapperEntryMode mode, string jsonProperty, PropertyInfo entityProperty,
  263. Action<T, JObject> mapToJsonAction, Action<JObject, T> mapToEntityAction)
  264. {
  265. Mode = mode;
  266. JsonProperty = jsonProperty;
  267. EntityProperty = entityProperty;
  268. _mapToJsonAction = mapToJsonAction;
  269. _mapToEntityAction = mapToEntityAction;
  270. }
  271. #endregion
  272. #region Public Methods
  273. public virtual void MapToJson(T entity, JObject json)
  274. {
  275. _mapToJsonAction(entity, json);
  276. }
  277. public virtual void MapToEntity(JObject json, T entity)
  278. {
  279. _mapToEntityAction(json, entity);
  280. }
  281. #endregion
  282. }
  283. public static class JsonMapperManagerExtensions
  284. {
  285. #region Public Methods
  286. public static JsonMapperConfiguration<T> Configure<T>(this JsonMapperManager manager)
  287. {
  288. if (manager == null)
  289. throw new ArgumentNullException("manager");
  290. var configuration = new JsonMapperConfiguration<T>(manager);
  291. var mapper = new JsonMapper<T>(configuration);
  292. manager.Configure(mapper);
  293. return configuration;
  294. }
  295. #endregion
  296. }
  297. public class JsonMapperException : Exception
  298. {
  299. #region Constructor
  300. public JsonMapperException()
  301. {
  302. }
  303. public JsonMapperException(string message)
  304. : base(message)
  305. {
  306. }
  307. #endregion
  308. }
  309. }