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