From c032b292bb20a80fc5fcbf7acbed98511b547107 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 20 Apr 2013 15:45:34 +1200 Subject: [PATCH] -Improved memory usage --- Src/Newtonsoft.Json/JsonConvert.cs | 49 +++++++- Src/Newtonsoft.Json/JsonReader.cs | 98 ++++++++++------ Src/Newtonsoft.Json/JsonTextReader.cs | 10 +- .../Serialization/JsonContract.cs | 106 ++++++++++++++---- .../Serialization/JsonPropertyCollection.cs | 1 - .../JsonSerializerInternalReader.cs | 16 ++- .../JsonSerializerInternalWriter.cs | 9 +- 7 files changed, 219 insertions(+), 70 deletions(-) diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 64a471b6..a64572bc 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -217,7 +217,7 @@ namespace Newtonsoft.Json } else { - writer.Write(value.ToString(@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture)); + WriteIsoDate(writer, value); switch (kind) { @@ -232,6 +232,53 @@ namespace Newtonsoft.Json } } + internal static void WriteIsoDate(TextWriter writer, DateTime dt) + { + char[] chars = new char[27]; + int length = 19; + + IntToCharArray(chars, 0, dt.Year, 4); + chars[4] = '-'; + IntToCharArray(chars, 5, dt.Month, 2); + chars[7] = '-'; + IntToCharArray(chars, 8, dt.Day, 2); + chars[10] = 'T'; + IntToCharArray(chars, 11, dt.Hour, 2); + chars[13] = ':'; + IntToCharArray(chars, 14, dt.Minute, 2); + chars[16] = ':'; + IntToCharArray(chars, 17, dt.Second, 2); + + DateTime time = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); + int fraction = (int)(dt.Ticks - time.Ticks); + + if (fraction != 0) + { + int digits = 7; + while ((fraction % 10) == 0) + { + digits--; + fraction /= 10; + } + + chars[19] = '.'; + IntToCharArray(chars, 20, fraction, digits); + + length += digits + 1; + } + + writer.Write(chars, 0, length); + } + + private static void IntToCharArray(char[] text, int start, int value, int digits) + { + while (digits-- != 0) + { + text[start + digits] = (char)((value % 10) + 48); + value /= 10; + } + } + internal static void WriteDateTimeOffset(TextWriter writer, TimeSpan offset, DateFormatHandling format) { writer.Write((offset.Ticks >= 0L) ? "+" : "-"); diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index faa66061..2960b736 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -103,7 +103,7 @@ namespace Newtonsoft.Json // current Token data private JsonToken _tokenType; private object _value; - private char _quoteChar; + internal char _quoteChar; internal State _currentState; internal ReadType _readType; private JsonPosition _currentPosition; @@ -380,6 +380,8 @@ namespace Newtonsoft.Json { _readType = ReadType.ReadAsDateTimeOffset; + JsonToken t; + do { if (!ReadInternal()) @@ -387,9 +389,13 @@ namespace Newtonsoft.Json SetToken(JsonToken.None); return null; } - } while (TokenType == JsonToken.Comment); + else + { + t = TokenType; + } + } while (t == JsonToken.Comment); - if (TokenType == JsonToken.Date) + if (t == JsonToken.Date) { if (Value is DateTime) SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value)); @@ -397,11 +403,11 @@ namespace Newtonsoft.Json return (DateTimeOffset)Value; } - if (TokenType == JsonToken.Null) + if (t == JsonToken.Null) return null; DateTimeOffset dt; - if (TokenType == JsonToken.String) + if (t == JsonToken.String) { string s = (string)Value; if (string.IsNullOrEmpty(s)) @@ -421,10 +427,10 @@ namespace Newtonsoft.Json } } - if (TokenType == JsonToken.EndArray) + if (t == JsonToken.EndArray) return null; - throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + throw JsonReaderException.Create(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } #endif @@ -432,6 +438,8 @@ namespace Newtonsoft.Json { _readType = ReadType.ReadAsBytes; + JsonToken t; + do { if (!ReadInternal()) @@ -439,7 +447,11 @@ namespace Newtonsoft.Json SetToken(JsonToken.None); return null; } - } while (TokenType == JsonToken.Comment); + else + { + t = TokenType; + } + } while (t == JsonToken.Comment); if (IsWrappedInTypeObject()) { @@ -450,26 +462,28 @@ namespace Newtonsoft.Json } // attempt to convert possible base 64 string to bytes - if (TokenType == JsonToken.String) + if (t == JsonToken.String) { string s = (string)Value; byte[] data = (s.Length == 0) ? new byte[0] : Convert.FromBase64String(s); SetToken(JsonToken.Bytes, data); + return data; } - if (TokenType == JsonToken.Null) + if (t == JsonToken.Null) return null; - if (TokenType == JsonToken.Bytes) + if (t == JsonToken.Bytes) return (byte[])Value; - if (TokenType == JsonToken.StartArray) + if (t == JsonToken.StartArray) { List data = new List(); while (ReadInternal()) { - switch (TokenType) + t = TokenType; + switch (t) { case JsonToken.Integer: data.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); @@ -482,23 +496,25 @@ namespace Newtonsoft.Json // skip break; default: - throw JsonReaderException.Create(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + throw JsonReaderException.Create(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } } throw JsonReaderException.Create(this, "Unexpected end when reading bytes."); } - if (TokenType == JsonToken.EndArray) + if (t == JsonToken.EndArray) return null; - throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + throw JsonReaderException.Create(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal decimal? ReadAsDecimalInternal() { _readType = ReadType.ReadAsDecimal; + JsonToken t; + do { if (!ReadInternal()) @@ -506,9 +522,13 @@ namespace Newtonsoft.Json SetToken(JsonToken.None); return null; } - } while (TokenType == JsonToken.Comment); + else + { + t = TokenType; + } + } while (t == JsonToken.Comment); - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) + if (t == JsonToken.Integer || t == JsonToken.Float) { if (!(Value is decimal)) SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture)); @@ -516,11 +536,11 @@ namespace Newtonsoft.Json return (decimal)Value; } - if (TokenType == JsonToken.Null) + if (t == JsonToken.Null) return null; decimal d; - if (TokenType == JsonToken.String) + if (t == JsonToken.String) { string s = (string)Value; if (string.IsNullOrEmpty(s)) @@ -540,16 +560,18 @@ namespace Newtonsoft.Json } } - if (TokenType == JsonToken.EndArray) + if (t == JsonToken.EndArray) return null; - throw JsonReaderException.Create(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + throw JsonReaderException.Create(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal int? ReadAsInt32Internal() { _readType = ReadType.ReadAsInt32; + JsonToken t; + do { if (!ReadInternal()) @@ -557,9 +579,13 @@ namespace Newtonsoft.Json SetToken(JsonToken.None); return null; } - } while (TokenType == JsonToken.Comment); + else + { + t = TokenType; + } + } while (t == JsonToken.Comment); - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) + if (t == JsonToken.Integer || t == JsonToken.Float) { if (!(Value is int)) SetToken(JsonToken.Integer, Convert.ToInt32(Value, CultureInfo.InvariantCulture)); @@ -567,11 +593,11 @@ namespace Newtonsoft.Json return (int)Value; } - if (TokenType == JsonToken.Null) + if (t == JsonToken.Null) return null; int i; - if (TokenType == JsonToken.String) + if (t == JsonToken.String) { string s = (string)Value; if (string.IsNullOrEmpty(s)) @@ -591,7 +617,7 @@ namespace Newtonsoft.Json } } - if (TokenType == JsonToken.EndArray) + if (t == JsonToken.EndArray) return null; throw JsonReaderException.Create(this, "Error reading integer. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); @@ -601,6 +627,8 @@ namespace Newtonsoft.Json { _readType = ReadType.ReadAsString; + JsonToken t; + do { if (!ReadInternal()) @@ -608,15 +636,19 @@ namespace Newtonsoft.Json SetToken(JsonToken.None); return null; } - } while (TokenType == JsonToken.Comment); + else + { + t = TokenType; + } + } while (t == JsonToken.Comment); - if (TokenType == JsonToken.String) + if (t == JsonToken.String) return (string)Value; - if (TokenType == JsonToken.Null) + if (t == JsonToken.Null) return null; - if (IsPrimitiveToken(TokenType)) + if (IsPrimitiveToken(t)) { if (Value != null) { @@ -631,10 +663,10 @@ namespace Newtonsoft.Json } } - if (TokenType == JsonToken.EndArray) + if (t == JsonToken.EndArray) return null; - throw JsonReaderException.Create(this, "Error reading string. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + throw JsonReaderException.Create(this, "Error reading string. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, t)); } internal DateTime? ReadAsDateTimeInternal() diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index db4fb92d..a744dd1f 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -78,7 +78,7 @@ namespace Newtonsoft.Json _reader = reader; _lineNumber = 1; - _chars = new char[4097]; + _chars = new char[2049]; } #if DEBUG @@ -92,7 +92,7 @@ namespace Newtonsoft.Json { if (_buffer == null) { - _buffer = new StringBuffer(4096); + _buffer = new StringBuffer(2048); } else { @@ -134,7 +134,7 @@ namespace Newtonsoft.Json string text = _stringReference.ToString(); SetToken(JsonToken.String, text); - QuoteChar = quote; + _quoteChar = quote; } else { @@ -161,7 +161,7 @@ namespace Newtonsoft.Json } SetToken(JsonToken.String, text); - QuoteChar = quote; + _quoteChar = quote; } } @@ -846,7 +846,7 @@ namespace Newtonsoft.Json _charPos++; SetToken(JsonToken.PropertyName, propertyName); - QuoteChar = quoteChar; + _quoteChar = quoteChar; ClearRecentString(); return true; diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index a9bf1963..01472342 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -70,11 +70,18 @@ namespace Newtonsoft.Json.Serialization { internal bool IsNullable; internal bool IsConvertable; + internal bool IsSealed; internal Type NonNullableUnderlyingType; internal ReadType InternalReadType; internal JsonContractType ContractType; internal bool IsReadOnlyOrFixedSize; + private List _onDeserializedCallbacks; + private IList _onDeserializingCallbacks; + private IList _onSerializedCallbacks; + private IList _onSerializingCallbacks; + private IList _onErrorCallbacks; + /// /// Gets the underlying type for the contract. /// @@ -107,31 +114,76 @@ namespace Newtonsoft.Json.Serialization /// Gets or sets all methods called immediately after deserialization of the object. /// /// The methods called immediately after deserialization of the object. - public IList OnDeserializedCallbacks { get; private set; } + public IList OnDeserializedCallbacks + { + get + { + if (_onDeserializedCallbacks == null) + _onDeserializedCallbacks = new List(); + + return _onDeserializedCallbacks; + } + } /// /// Gets or sets all methods called during deserialization of the object. /// /// The methods called during deserialization of the object. - public IList OnDeserializingCallbacks { get; private set; } + public IList OnDeserializingCallbacks + { + get + { + if (_onDeserializingCallbacks == null) + _onDeserializingCallbacks = new List(); + + return _onDeserializingCallbacks; + } + } /// /// Gets or sets all methods called after serialization of the object graph. /// /// The methods called after serialization of the object graph. - public IList OnSerializedCallbacks { get; private set; } + public IList OnSerializedCallbacks + { + get + { + if (_onSerializedCallbacks == null) + _onSerializedCallbacks = new List(); + + return _onSerializedCallbacks; + } + } /// /// Gets or sets all methods called before serialization of the object. /// /// The methods called before serialization of the object. - public IList OnSerializingCallbacks { get; private set; } + public IList OnSerializingCallbacks + { + get + { + if (_onSerializingCallbacks == null) + _onSerializingCallbacks = new List(); + + return _onSerializingCallbacks; + } + } /// /// Gets or sets all method called when an error is thrown during the serialization of the object. /// /// The methods called when an error is thrown during the serialization of the object. - public IList OnErrorCallbacks { get; private set; } + public IList OnErrorCallbacks + { + get + { + if (_onErrorCallbacks == null) + _onErrorCallbacks = new List(); + + return _onErrorCallbacks; + } + } /// /// Gets or sets the method called immediately after deserialization of the object. @@ -226,6 +278,8 @@ namespace Newtonsoft.Json.Serialization UnderlyingType = underlyingType; + IsSealed = underlyingType.IsSealed(); + IsNullable = ReflectionUtils.IsNullable(underlyingType); NonNullableUnderlyingType = (IsNullable && ReflectionUtils.IsNullableType(underlyingType)) ? Nullable.GetUnderlyingType(underlyingType) : underlyingType; @@ -233,6 +287,7 @@ namespace Newtonsoft.Json.Serialization IsConvertable = ConvertUtils.IsConvertible(NonNullableUnderlyingType); + if (NonNullableUnderlyingType == typeof(byte[])) { InternalReadType = ReadType.ReadAsBytes; @@ -263,51 +318,60 @@ namespace Newtonsoft.Json.Serialization { InternalReadType = ReadType.Read; } - - OnErrorCallbacks = new List(); - OnSerializedCallbacks = new List(); - OnSerializingCallbacks = new List(); - OnDeserializedCallbacks = new List(); - OnDeserializingCallbacks = new List(); } internal void InvokeOnSerializing(object o, StreamingContext context) { - foreach (SerializationCallback callback in OnSerializingCallbacks) + if (_onSerializingCallbacks != null) { - callback(o, context); + foreach (SerializationCallback callback in _onSerializingCallbacks) + { + callback(o, context); + } } } internal void InvokeOnSerialized(object o, StreamingContext context) { - foreach (SerializationCallback callback in OnSerializedCallbacks) + if (_onSerializedCallbacks != null) { - callback(o, context); + foreach (SerializationCallback callback in _onSerializedCallbacks) + { + callback(o, context); + } } } internal void InvokeOnDeserializing(object o, StreamingContext context) { - foreach (SerializationCallback callback in OnDeserializingCallbacks) + if (_onDeserializingCallbacks != null) { - callback(o, context); + foreach (SerializationCallback callback in _onDeserializingCallbacks) + { + callback(o, context); + } } } internal void InvokeOnDeserialized(object o, StreamingContext context) { - foreach (SerializationCallback callback in OnDeserializedCallbacks) + if (_onDeserializedCallbacks != null) { - callback(o, context); + foreach (SerializationCallback callback in _onDeserializedCallbacks) + { + callback(o, context); + } } } internal void InvokeOnError(object o, StreamingContext context, ErrorContext errorContext) { - foreach (SerializationErrorCallback callback in OnErrorCallbacks) + if (_onErrorCallbacks != null) { - callback(o, context, errorContext); + foreach (SerializationErrorCallback callback in _onErrorCallbacks) + { + callback(o, context, errorContext); + } } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs b/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs index af4b4601..ed0129ee 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs @@ -133,7 +133,6 @@ namespace Newtonsoft.Json.Serialization return Dictionary.TryGetValue(key, out item); } - /// /// Gets a property by property name. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 802fa4c3..63d00185 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -261,15 +261,17 @@ namespace Newtonsoft.Json.Serialization case JsonToken.Bytes: return EnsureType(reader, reader.Value, CultureInfo.InvariantCulture, contract, objectType); case JsonToken.String: + string s = (string)reader.Value; + // convert empty string to null automatically for nullable types - if (string.IsNullOrEmpty((string)reader.Value) && objectType != typeof(string) && objectType != typeof(object) && contract != null && contract.IsNullable) + if (string.IsNullOrEmpty(s) && objectType != typeof(string) && objectType != typeof(object) && contract != null && contract.IsNullable) return null; // string that needs to be returned as a byte array should be base 64 decoded if (objectType == typeof (byte[])) - return Convert.FromBase64String((string) reader.Value); + return Convert.FromBase64String(s); - return EnsureType(reader, reader.Value, CultureInfo.InvariantCulture, contract, objectType); + return EnsureType(reader, s, CultureInfo.InvariantCulture, contract, objectType); case JsonToken.StartConstructor: string constructorName = reader.Value.ToString(); @@ -778,6 +780,8 @@ To fix this error either change the JSON to a {1} or change the deserialized typ return true; } + JsonToken tokenType = reader.TokenType; + if (property.PropertyContract == null) property.PropertyContract = GetContractSafe(property.PropertyType); @@ -785,7 +789,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ property.ObjectCreationHandling.GetValueOrDefault(Serializer.ObjectCreationHandling); if ((objectCreationHandling != ObjectCreationHandling.Replace) - && (reader.TokenType == JsonToken.StartArray || reader.TokenType == JsonToken.StartObject) + && (tokenType == JsonToken.StartArray || tokenType == JsonToken.StartObject) && property.Readable) { currentValue = property.ValueProvider.GetValue(target); @@ -806,7 +810,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ } // test tokentype here because null might not be convertable to some types, e.g. ignoring null when applied to DateTime - if (property.NullValueHandling.GetValueOrDefault(Serializer.NullValueHandling) == NullValueHandling.Ignore && reader.TokenType == JsonToken.Null) + if (property.NullValueHandling.GetValueOrDefault(Serializer.NullValueHandling) == NullValueHandling.Ignore && tokenType == JsonToken.Null) { reader.Skip(); return true; @@ -814,7 +818,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ // test tokentype here because default value might not be convertable to actual type, e.g. default of "" for DateTime if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Ignore) - && JsonReader.IsPrimitiveToken(reader.TokenType) + && JsonReader.IsPrimitiveToken(tokenType) && MiscellaneousUtils.ValueEquals(reader.Value, property.GetResolvedDefaultValue())) { reader.Skip(); diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 034c0c79..d6f32840 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -370,8 +370,9 @@ namespace Newtonsoft.Json.Serialization int initialDepth = writer.Top; - foreach (JsonProperty property in contract.Properties) + for (int index = 0; index < contract.Properties.Count; index++) { + JsonProperty property = contract.Properties[index]; try { object memberValue; @@ -407,7 +408,7 @@ namespace Newtonsoft.Json.Serialization property.PropertyContract = Serializer.ContractResolver.ResolveContract(property.PropertyType); memberValue = property.ValueProvider.GetValue(value); - memberContract = (property.PropertyContract.UnderlyingType.IsSealed()) ? property.PropertyContract : GetContractSafe(memberValue); + memberContract = (property.PropertyContract.IsSealed) ? property.PropertyContract : GetContractSafe(memberValue); if (ShouldWriteProperty(memberValue, property)) { @@ -719,8 +720,10 @@ To fix this error either change the environment to be fully trusted, change the int initialDepth = writer.Top; - foreach (JsonProperty property in contract.Properties) + for (int index = 0; index < contract.Properties.Count; index++) { + JsonProperty property = contract.Properties[index]; + // only write non-dynamic properties that have an explicit attribute if (property.HasMemberAttribute) {