PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Client.Lightweight/Document/EntityToJson.cs

http://github.com/ayende/ravendb
C# | 212 lines | 168 code | 30 blank | 14 comment | 46 complexity | 550ad80fb2b623ca4fa1e19a453fb640 MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Dynamic;
  4. using System.Linq;
  5. using System.Text.RegularExpressions;
  6. using Raven.Abstractions.Data;
  7. using Raven.Abstractions.Extensions;
  8. using Raven.Abstractions.Linq;
  9. using Raven.Client.Util;
  10. using Raven.Imports.Newtonsoft.Json;
  11. using Raven.Imports.Newtonsoft.Json.Linq;
  12. using Raven.Imports.Newtonsoft.Json.Serialization;
  13. using Raven.Json.Linq;
  14. namespace Raven.Client.Document
  15. {
  16. public class EntityToJson
  17. {
  18. private readonly IDocumentStore documentStore;
  19. /// <summary>
  20. /// All the listeners for this session
  21. /// </summary>
  22. public DocumentSessionListeners Listeners { get; private set; }
  23. public EntityToJson(IDocumentStore documentStore, DocumentSessionListeners listeners)
  24. {
  25. this.documentStore = documentStore;
  26. Listeners = listeners;
  27. }
  28. public readonly Dictionary<object, Dictionary<string, JToken>> MissingDictionary = new Dictionary<object, Dictionary<string, JToken>>(ObjectReferenceEqualityComparer<object>.Default);
  29. public RavenJObject ConvertEntityToJson(string key, object entity, RavenJObject metadata)
  30. {
  31. foreach (var extendedDocumentConversionListener in Listeners.ConversionListeners)
  32. {
  33. extendedDocumentConversionListener.BeforeConversionToDocument(key, entity, metadata);
  34. }
  35. var entityType = entity.GetType();
  36. var identityProperty = documentStore.Conventions.GetIdentityProperty(entityType);
  37. var objectAsJson = GetObjectAsJson(entity);
  38. if (identityProperty != null)
  39. {
  40. objectAsJson.Remove(identityProperty.Name);
  41. }
  42. SetClrType(entityType, metadata);
  43. foreach (var extendedDocumentConversionListener in Listeners.ConversionListeners)
  44. {
  45. extendedDocumentConversionListener.AfterConversionToDocument(key, entity, objectAsJson, metadata);
  46. }
  47. return objectAsJson;
  48. }
  49. public IDictionary<object, RavenJObject> CachedJsonDocs { get; private set; }
  50. private RavenJObject GetObjectAsJson(object entity)
  51. {
  52. var jObject = entity as RavenJObject;
  53. if (jObject != null)
  54. return (RavenJObject)jObject.CloneToken();
  55. if (CachedJsonDocs != null && CachedJsonDocs.TryGetValue(entity, out jObject))
  56. return (RavenJObject)jObject.CreateSnapshot();
  57. var jsonSerializer = documentStore.Conventions.CreateSerializer();
  58. jsonSerializer.BeforeClosingObject += (o, writer) =>
  59. {
  60. var ravenJTokenWriter = (RavenJTokenWriter)writer;
  61. ravenJTokenWriter.AssociateCurrentOBjectWith(o);
  62. Dictionary<string, JToken> value;
  63. if (MissingDictionary.TryGetValue(o, out value) == false)
  64. return;
  65. foreach (var item in value)
  66. {
  67. writer.WritePropertyName(item.Key);
  68. if (item.Value == null)
  69. writer.WriteNull();
  70. else
  71. //See issue http://issues.hibernatingrhinos.com/issue/RavenDB-4729
  72. //writer.WriteValue(item.Value);
  73. item.Value.WriteTo(writer);
  74. }
  75. };
  76. jObject = RavenJObject.FromObject(entity, jsonSerializer);
  77. if (jsonSerializer.TypeNameHandling == TypeNameHandling.Auto)// remove the default types
  78. {
  79. TrySimplifyingJson(jObject, jsonSerializer);
  80. }
  81. if (CachedJsonDocs != null)
  82. {
  83. jObject.EnsureCannotBeChangeAndEnableSnapshotting();
  84. CachedJsonDocs[entity] = jObject;
  85. return (RavenJObject)jObject.CreateSnapshot();
  86. }
  87. return jObject;
  88. }
  89. private void SetClrType(Type entityType, RavenJObject metadata)
  90. {
  91. if (entityType == typeof(ExpandoObject) ||
  92. entityType == typeof(DynamicJsonObject) ||
  93. entityType == typeof(RavenJObject)) // dynamic types
  94. {
  95. return;// do not overwrite the value
  96. }
  97. metadata[Constants.RavenClrType] = documentStore.Conventions.GetClrTypeName(entityType);
  98. }
  99. /// <summary>
  100. /// All calls to convert an entity to a json object would be cache
  101. /// This is used inside the SaveChanges() action, where we need to access the entities json
  102. /// in several disparate places.
  103. ///
  104. /// Note: This assumes that no modifications can happen during the SaveChanges. This is naturally true
  105. /// Note: for SaveChanges (and multi threaded access will cause undefined behavior anyway).
  106. /// Note: For SaveChangesAsync, the same holds true as well.
  107. /// </summary>
  108. public IDisposable EntitiesToJsonCachingScope()
  109. {
  110. CachedJsonDocs = new Dictionary<object, RavenJObject>(ObjectReferenceEqualityComparer<object>.Default);
  111. return new DisposableAction(() => CachedJsonDocs = null);
  112. }
  113. private static void TrySimplifyingJson(RavenJObject jObject, JsonSerializer jsonSerializer)
  114. {
  115. if (jObject.Tag == null)
  116. return;
  117. var resolveContract = jsonSerializer.ContractResolver.ResolveContract(jObject.Tag.GetType());
  118. var objectContract = resolveContract as JsonObjectContract;
  119. if (objectContract == null)
  120. return;
  121. var deferredActions = new List<Action>();
  122. foreach (var kvp in jObject)
  123. {
  124. var prop = kvp;
  125. if (prop.Value == null)
  126. continue;
  127. var obj = prop.Value as RavenJObject;
  128. if (obj == null)
  129. continue;
  130. var jsonProperty = objectContract.Properties.GetClosestMatchProperty(prop.Key);
  131. if (ShouldSimplifyJsonBasedOnType(obj.Value<string>("$type"), jsonProperty) == false)
  132. continue;
  133. if (obj.ContainsKey("$values") == false)
  134. {
  135. deferredActions.Add(() => obj.Remove("$type"));
  136. }
  137. else
  138. {
  139. deferredActions.Add(() => jObject[prop.Key] = obj["$values"]);
  140. }
  141. }
  142. foreach (var deferredAction in deferredActions)
  143. {
  144. deferredAction();
  145. }
  146. foreach (var prop in jObject.Where(prop => prop.Value != null))
  147. {
  148. switch (prop.Value.Type)
  149. {
  150. case JTokenType.Array:
  151. foreach (var item in ((RavenJArray)prop.Value))
  152. {
  153. var ravenJObject = item as RavenJObject;
  154. if (ravenJObject != null)
  155. TrySimplifyingJson(ravenJObject, jsonSerializer);
  156. }
  157. break;
  158. case JTokenType.Object:
  159. TrySimplifyingJson((RavenJObject)prop.Value, jsonSerializer);
  160. break;
  161. }
  162. }
  163. }
  164. private static Regex arrayEndRegex = new Regex(@"\[\], [\w\.-]+$",
  165. RegexOptions.Compiled
  166. );
  167. private static bool ShouldSimplifyJsonBasedOnType(string typeValue, JsonProperty jsonProperty)
  168. {
  169. if (jsonProperty != null && (jsonProperty.TypeNameHandling == TypeNameHandling.All || jsonProperty.TypeNameHandling == TypeNameHandling.Arrays))
  170. return false; // explicitly rejected what we are trying to do here
  171. if (typeValue == null)
  172. return false;
  173. if (typeValue.StartsWith("System.Collections.Generic.List`1[["))
  174. return true;
  175. if (typeValue.StartsWith("System.Collections.Generic.Dictionary`2[["))
  176. return true;
  177. if (arrayEndRegex.IsMatch(typeValue)) // array
  178. return true;
  179. return false;
  180. }
  181. }
  182. }