From 89ab4c0d1e067c24c01f6851d7109ff65d986188 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 6 May 2012 13:47:23 +1200 Subject: [PATCH] -Improved deserialization performance by only tracking property presence when needed --- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 2 +- .../Serialization/JsonObjectContract.cs | 30 ++++++ .../JsonSerializerInternalReader.cs | 99 ++++++++++--------- 3 files changed, 85 insertions(+), 46 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index a2c6dc0d..98ed8101 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -727,7 +727,7 @@ now brown cow?", '"', true); { StringBuilder sb = new StringBuilder(); - int depth = 700; + int depth = 900; for (int i = 0; i < depth; i++) { sb.Append("{'A':"); diff --git a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs index 86d98d65..2a424d35 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs @@ -72,6 +72,36 @@ namespace Newtonsoft.Json.Serialization /// The parametrized constructor. public ConstructorInfo ParametrizedConstructor { get; set; } + private bool? _hasRequiredOrDefaultValueProperties; + internal bool HasRequiredOrDefaultValueProperties + { + get + { + if (_hasRequiredOrDefaultValueProperties == null) + { + _hasRequiredOrDefaultValueProperties = false; + + if (ItemRequired.GetValueOrDefault(Required.Default) != Required.Default) + { + _hasRequiredOrDefaultValueProperties = true; + } + else + { + foreach (JsonProperty property in Properties) + { + if (property.Required != Required.Default || ((property.DefaultValueHandling & DefaultValueHandling.Populate) == DefaultValueHandling.Populate) && property.Writable) + { + _hasRequiredOrDefaultValueProperties = true; + break; + } + } + } + } + + return _hasRequiredOrDefaultValueProperties.Value; + } + } + /// /// Initializes a new instance of the class. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 3a9bbd6b..d4bf1d9d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -88,7 +88,7 @@ namespace Newtonsoft.Json.Serialization if (contract.ContractType == JsonContractType.Dictionary) PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(target), reader, (JsonDictionaryContract) contract, null, null, id); else if (contract.ContractType == JsonContractType.Object) - PopulateObject(target, reader, (JsonObjectContract) contract, null, null, id); + PopulateObject(target, reader, (JsonObjectContract) contract, null, id); else throw JsonSerializationException.Create(reader, "Cannot populate JSON object onto type '{0}'.".FormatWith(CultureInfo.InvariantCulture, objectType)); } @@ -317,7 +317,7 @@ namespace Newtonsoft.Json.Serialization else targetObject = CreateNewObject(reader, objectContract, member, containerMember, id); - return PopulateObject(targetObject, reader, objectContract, member, containerMember, id); + return PopulateObject(targetObject, reader, objectContract, member, id); case JsonContractType.Primitive: JsonPrimitiveContract primitiveContract = (JsonPrimitiveContract) contract; // if the content is inside $value then read past it @@ -1245,12 +1245,14 @@ To fix this error either change the environment to be fully trusted, change the return newObject; } - private object PopulateObject(object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, JsonProperty containerProperty, string id) + private object PopulateObject(object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, string id) { contract.InvokeOnDeserializing(newObject, Serializer.Context); - Dictionary propertiesPresence = - contract.Properties.ToDictionary(m => m, m => PropertyPresence.None); + // only need to keep a track of properies presence if they are required or a value should be defaulted if missing + Dictionary propertiesPresence = (contract.HasRequiredOrDefaultValueProperties || HasFlag(Serializer.DefaultValueHandling, DefaultValueHandling.Populate)) + ? contract.Properties.ToDictionary(m => m, m => PropertyPresence.None) + : null; if (id != null) Serializer.ReferenceResolver.AddReference(this, id, newObject); @@ -1303,45 +1305,7 @@ To fix this error either change the environment to be fully trusted, change the break; case JsonToken.EndObject: { - foreach (KeyValuePair propertyPresence in propertiesPresence) - { - JsonProperty property = propertyPresence.Key; - PropertyPresence presence = propertyPresence.Value; - - if (presence == PropertyPresence.None || presence == PropertyPresence.Null) - { - try - { - Required resolvedRequired = property._required ?? contract.ItemRequired ?? Required.Default; - - switch (presence) - { - case PropertyPresence.None: - if (resolvedRequired == Required.AllowNull || resolvedRequired == Required.Always) - throw JsonSerializationException.Create(reader, "Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); - - if (property.PropertyContract == null) - property.PropertyContract = GetContractSafe(property.PropertyType); - - if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Populate) - && property.Writable) - property.ValueProvider.SetValue(newObject, EnsureType(reader, property.DefaultValue, CultureInfo.InvariantCulture, property.PropertyContract, property.PropertyType)); - break; - case PropertyPresence.Null: - if (resolvedRequired == Required.Always) - throw JsonSerializationException.Create(reader, "Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); - break; - } - } - catch (Exception ex) - { - if (IsErrorHandled(newObject, contract, property.PropertyName, reader.Path, ex)) - HandleError(reader, initialDepth); - else - throw; - } - } - } + EndObject(newObject, reader, contract, initialDepth, propertiesPresence); contract.InvokeOnDeserialized(newObject, Serializer.Context); return newObject; @@ -1357,9 +1321,54 @@ To fix this error either change the environment to be fully trusted, change the throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object."); } + private void EndObject(object newObject, JsonReader reader, JsonObjectContract contract, int initialDepth, Dictionary propertiesPresence) + { + if (propertiesPresence != null) + { + foreach (KeyValuePair propertyPresence in propertiesPresence) + { + JsonProperty property = propertyPresence.Key; + PropertyPresence presence = propertyPresence.Value; + + if (presence == PropertyPresence.None || presence == PropertyPresence.Null) + { + try + { + Required resolvedRequired = property._required ?? contract.ItemRequired ?? Required.Default; + + switch (presence) + { + case PropertyPresence.None: + if (resolvedRequired == Required.AllowNull || resolvedRequired == Required.Always) + throw JsonSerializationException.Create(reader, "Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + + if (property.PropertyContract == null) + property.PropertyContract = GetContractSafe(property.PropertyType); + + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Populate) && property.Writable) + property.ValueProvider.SetValue(newObject, EnsureType(reader, property.DefaultValue, CultureInfo.InvariantCulture, property.PropertyContract, property.PropertyType)); + break; + case PropertyPresence.Null: + if (resolvedRequired == Required.Always) + throw JsonSerializationException.Create(reader, "Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + break; + } + } + catch (Exception ex) + { + if (IsErrorHandled(newObject, contract, property.PropertyName, reader.Path, ex)) + HandleError(reader, initialDepth); + else + throw; + } + } + } + } + } + private void SetPropertyPresence(JsonReader reader, JsonProperty property, Dictionary requiredProperties) { - if (property != null) + if (property != null && requiredProperties != null) { requiredProperties[property] = (reader.TokenType == JsonToken.Null || reader.TokenType == JsonToken.Undefined) ? PropertyPresence.Null