PageRenderTime 229ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

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

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