diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index 6a6bbf6f..84ac9c5f 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj @@ -77,22 +77,30 @@ + + + + + + + + @@ -105,7 +113,9 @@ + + @@ -189,6 +199,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 5c1cd929..91f5aa9d 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Diagnostics; - -using Newtonsoft.Json; using System.IO; using System.Web.Script.Serialization; @@ -16,6 +14,13 @@ using Newtonsoft.Json.Bson; namespace Newtonsoft.Json.Tests { + public class Image + { + public string FileName { get; set; } + public string Title { get; set; } + public byte[] Data { get; set; } + } + public class PerformanceTests : TestFixtureBase { private const int Iterations = 100; @@ -70,6 +75,50 @@ namespace Newtonsoft.Json.Tests [Test] public void Serialize() + { + TestClass test = CreateSerializationObject(); + + BenchmarkSerializeMethod(SerializeMethod.JsonNet, test); + BenchmarkSerializeMethod(SerializeMethod.JsonNetBinary, test); + BenchmarkSerializeMethod(SerializeMethod.JavaScriptSerializer, test); + BenchmarkSerializeMethod(SerializeMethod.DataContractJsonSerializer, test); + } + + [Test] + public void Deserialize() + { + BenchmarkDeserializeMethod(SerializeMethod.JsonNet, JsonText); + BenchmarkDeserializeMethod(SerializeMethod.JsonNetBinary, MiscellaneousUtils.HexToBytes(BsonHex)); + BenchmarkDeserializeMethod(SerializeMethod.JavaScriptSerializer, JsonText); + BenchmarkDeserializeMethod(SerializeMethod.DataContractJsonSerializer, JsonText); + } + + [Test] + public void SerializeDataSize() + { + Image image = new Image(); + image.Data = System.IO.File.ReadAllBytes(@"..\..\bunny_pancake.jpg"); + image.FileName = "bunny_pancake.jpg"; + image.Title = "I have no idea what you are talking about so here's a bunny with a pancake on its head"; + + string json = JsonConvert.SerializeObject(image, Formatting.None); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + + MemoryStream ms = new MemoryStream(); + JsonSerializer serializer = new JsonSerializer(); + BsonWriter writer = new BsonWriter(ms); + + serializer.Serialize(writer, image); + writer.Flush(); + + byte[] bsonBytes = ms.ToArray(); + + Console.WriteLine("Image size: {0} bytes", image.Data.Length); + Console.WriteLine("JSON size: {0} bytes", jsonBytes.Length); + Console.WriteLine("BSON size: {0} bytes", bsonBytes.Length); + } + + private TestClass CreateSerializationObject() { TestClass test = new TestClass(); @@ -90,20 +139,7 @@ namespace Newtonsoft.Json.Tests address.Entered = DateTime.Now.AddDays(-2); address.Address = "array 2 address"; test.Addresses.Add(address); - - BenchmarkSerializeMethod(SerializeMethod.JsonNet, test); - BenchmarkSerializeMethod(SerializeMethod.JsonNetBinary, test); - BenchmarkSerializeMethod(SerializeMethod.JavaScriptSerializer, test); - BenchmarkSerializeMethod(SerializeMethod.DataContractJsonSerializer, test); - } - - [Test] - public void Deserialize() - { - BenchmarkDeserializeMethod(SerializeMethod.JsonNet, JsonText); - BenchmarkDeserializeMethod(SerializeMethod.JsonNetBinary, MiscellaneousUtils.HexToBytes(BsonHex)); - BenchmarkDeserializeMethod(SerializeMethod.JavaScriptSerializer, JsonText); - BenchmarkDeserializeMethod(SerializeMethod.DataContractJsonSerializer, JsonText); + return test; } public string SerializeJsonNet(object value) @@ -203,33 +239,49 @@ namespace Newtonsoft.Json.Tests return dataContractSerializer.ReadObject(ms); } + private static readonly byte[] Buffer = new byte[4096]; + + private string Serialize(SerializeMethod method, object value) + { + string json; + + switch (method) + { + case SerializeMethod.JsonNet: + json = JsonConvert.SerializeObject(value); + break; + case SerializeMethod.JsonNetBinary: + MemoryStream ms = new MemoryStream(Buffer); + JsonSerializer serializer = new JsonSerializer(); + BsonWriter writer = new BsonWriter(ms); + serializer.Serialize(writer, value); + + json = "Bytes = " + ms.Length; + break; + case SerializeMethod.JavaScriptSerializer: + json = SerializeWebExtensions(value); + break; + case SerializeMethod.DataContractJsonSerializer: + json = SerializeDataContract(value); + break; + default: + throw new ArgumentOutOfRangeException("method"); + } + + return json; + } + public void BenchmarkSerializeMethod(SerializeMethod method, object value) { + Serialize(method, value); + Stopwatch timed = new Stopwatch(); timed.Start(); string json = null; for (int x = 0; x < Iterations; x++) { - switch (method) - { - case SerializeMethod.JsonNet: - json = JsonConvert.SerializeObject(value); - break; - case SerializeMethod.JsonNetBinary: - JsonSerializer serializer = new JsonSerializer(); - MemoryStream ms = new MemoryStream(); - BsonWriter writer = new BsonWriter(ms); - serializer.Serialize(writer, value); - json = "{Bytes " + ms.Length + "}"; - break; - case SerializeMethod.JavaScriptSerializer: - json = SerializeWebExtensions(value); - break; - case SerializeMethod.DataContractJsonSerializer: - json = SerializeDataContract(value); - break; - } + json = Serialize(method, value); } timed.Stop(); @@ -248,21 +300,7 @@ namespace Newtonsoft.Json.Tests object value = null; for (int x = 0; x < Iterations; x++) { - switch (method) - { - case SerializeMethod.JsonNet: - value = DeserializeJsonNet((string)json); - break; - case SerializeMethod.JsonNetBinary: - value = DeserializeJsonNetBinary((byte[])json); - break; - case SerializeMethod.JavaScriptSerializer: - value = DeserializeWebExtensions((string)json); - break; - case SerializeMethod.DataContractJsonSerializer: - value = DeserializeDataContract((string)json); - break; - } + value = Deserialize(method, json); } timed.Stop(); @@ -272,6 +310,30 @@ namespace Newtonsoft.Json.Tests Console.WriteLine(value); Console.WriteLine(); } + + private object Deserialize(SerializeMethod method, object json) + { + object value; + + switch (method) + { + case SerializeMethod.JsonNet: + value = DeserializeJsonNet((string) json); + break; + case SerializeMethod.JsonNetBinary: + value = DeserializeJsonNetBinary((byte[]) json); + break; + case SerializeMethod.JavaScriptSerializer: + value = DeserializeWebExtensions((string) json); + break; + case SerializeMethod.DataContractJsonSerializer: + value = DeserializeDataContract((string) json); + break; + default: + throw new ArgumentOutOfRangeException("method"); + } + return value; + } } diff --git a/Src/Newtonsoft.Json.Tests/bunny_pancake.jpg b/Src/Newtonsoft.Json.Tests/bunny_pancake.jpg new file mode 100644 index 00000000..59e882ca Binary files /dev/null and b/Src/Newtonsoft.Json.Tests/bunny_pancake.jpg differ diff --git a/Src/Newtonsoft.Json/Bson/BsonWriter.cs b/Src/Newtonsoft.Json/Bson/BsonWriter.cs index 575c5c16..1d7bc0c7 100644 --- a/Src/Newtonsoft.Json/Bson/BsonWriter.cs +++ b/Src/Newtonsoft.Json/Bson/BsonWriter.cs @@ -37,8 +37,13 @@ namespace Newtonsoft.Json.Bson /// public class BsonWriter : JTokenWriter { + private static readonly Encoding Encoding = Encoding.UTF8; + private readonly BinaryWriter _writer; + private byte[] _largeByteBuffer; + private int _maxChars; + /// /// Initializes a new instance of the class. /// @@ -68,7 +73,7 @@ namespace Newtonsoft.Json.Bson foreach (JProperty property in t) { _writer.Write((sbyte)GetTypeNumber(property.Value)); - WriteString(property.Name); + WriteString(property.Name, false); WriteToken(property.Value); } _writer.Write((byte)0); @@ -82,7 +87,7 @@ namespace Newtonsoft.Json.Bson foreach (JToken c in t) { _writer.Write((sbyte)GetTypeNumber(c)); - WriteString(index.ToString(CultureInfo.InvariantCulture)); + WriteString(index.ToString(CultureInfo.InvariantCulture), false); WriteToken(c); index++; } @@ -96,7 +101,7 @@ namespace Newtonsoft.Json.Bson _writer.Write(Convert.ToDouble(((JValue)t).Value, CultureInfo.InvariantCulture)); break; case JTokenType.String: - WriteStringWithLength((string)t); + WriteString((string)t, true); break; case JTokenType.Boolean: _writer.Write((bool)t); @@ -121,17 +126,37 @@ namespace Newtonsoft.Json.Bson } } - private void WriteString(string s) + private void WriteString(string s, bool includeLength) { - byte[] bytes = Encoding.UTF8.GetBytes(s); - _writer.Write(bytes); - _writer.Write((byte)0); - } + int byteCount = Encoding.GetByteCount(s); - private void WriteStringWithLength(string s) - { - _writer.Write(CalculateSizeWithLength(s, false)); - WriteString(s); + if (includeLength) + _writer.Write(CalculateSizeWithLength(byteCount, false)); + + if (_largeByteBuffer == null) + { + _largeByteBuffer = new byte[256]; + _maxChars = 256 / Encoding.GetMaxByteCount(1); + } + if (byteCount <= 256) + { + Encoding.GetBytes(s, 0, s.Length, _largeByteBuffer, 0); + _writer.Write(_largeByteBuffer, 0, byteCount); + } + else + { + int charCount; + int totalCharsWritten = 0; + for (int i = s.Length; i > 0; i -= charCount) + { + charCount = (i > _maxChars) ? _maxChars : i; + int count = Encoding.GetBytes(s, totalCharsWritten, charCount, _largeByteBuffer, 0); + _writer.Write(_largeByteBuffer, 0, count); + totalCharsWritten += charCount; + } + } + + _writer.Write((byte)0); } private BsonType GetTypeNumber(JToken t) @@ -164,18 +189,21 @@ namespace Newtonsoft.Json.Bson private int CalculateSize(string s) { - return Encoding.UTF8.GetByteCount(s) + 1; + return Encoding.GetByteCount(s) + 1; } private int CalculateSizeWithLength(string s, bool includeSize) + { + return CalculateSizeWithLength(Encoding.GetByteCount(s), includeSize); + } + + private int CalculateSizeWithLength(int stringByteCount, bool includeSize) { int baseSize = (includeSize) ? 5 // size bytes + terminator : 1; // terminator - int byteCount = Encoding.UTF8.GetByteCount(s); - - return baseSize + byteCount; + return baseSize + stringByteCount; } private int CalculateSize(JToken t) diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index 09d2ec5e..2a7ddc6f 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -601,9 +601,11 @@ namespace Newtonsoft.Json WriteIndentSpace(); } + WriteState writeState = WriteState; + // don't indent a property when it is the first token to be written (i.e. at the start) - if ((tokenBeingWritten == JsonToken.PropertyName && WriteState != WriteState.Start) || - WriteState == WriteState.Array || WriteState == WriteState.Constructor) + if ((tokenBeingWritten == JsonToken.PropertyName && writeState != WriteState.Start) || + writeState == WriteState.Array || writeState == WriteState.Constructor) { WriteIndent(); } diff --git a/Src/Newtonsoft.Json/Linq/JContainer.cs b/Src/Newtonsoft.Json/Linq/JContainer.cs index df4d9b57..ecd21917 100644 --- a/Src/Newtonsoft.Json/Linq/JContainer.cs +++ b/Src/Newtonsoft.Json/Linq/JContainer.cs @@ -219,8 +219,7 @@ namespace Newtonsoft.Json.Linq { yield return current; } - while (current != null - && ((current = current.Next) != null)); + while ((current = current.Next) != null); } /// diff --git a/Src/Newtonsoft.Json/Linq/JObject.cs b/Src/Newtonsoft.Json/Linq/JObject.cs index 5fb4260f..0087f473 100644 --- a/Src/Newtonsoft.Json/Linq/JObject.cs +++ b/Src/Newtonsoft.Json/Linq/JObject.cs @@ -105,10 +105,14 @@ namespace Newtonsoft.Json.Linq if (o.Type != JTokenType.Property) throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType())); + // looping over all properties every time isn't good + // need to think about performance here JProperty property = (JProperty)o; - bool matchingProperty = Properties().Any(p => string.Equals(p.Name, property.Name, StringComparison.Ordinal) && p != existing); - if (matchingProperty) - throw new ArgumentException("Can not add property {0} to {1}. Property with the same name already exists on object.".FormatWith(CultureInfo.InvariantCulture, property.Name, GetType())); + foreach (JProperty childProperty in Children()) + { + if (childProperty != existing && string.Equals(childProperty.Name, property.Name, StringComparison.Ordinal)) + throw new ArgumentException("Can not add property {0} to {1}. Property with the same name already exists on object.".FormatWith(CultureInfo.InvariantCulture, property.Name, GetType())); + } } internal void InternalPropertyChanged(JProperty childProperty) diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index 8bfccdaf..1bb723aa 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -35,71 +35,68 @@ namespace Newtonsoft.Json.Utilities { internal static class JavaScriptUtils { - public static void WriteEscapedJavaScriptChar(TextWriter writer, char c, char delimiter) - { - switch (c) - { - case '\t': - writer.Write(@"\t"); - break; - case '\n': - writer.Write(@"\n"); - break; - case '\r': - writer.Write(@"\r"); - break; - case '\f': - writer.Write(@"\f"); - break; - case '\b': - writer.Write(@"\b"); - break; - case '\\': - writer.Write(@"\\"); - break; - case '\u0085': // Next Line - writer.Write(@"\u0085"); - break; - case '\u2028': // Line Separator - writer.Write(@"\u2028"); - break; - case '\u2029': // Paragraph Separator - writer.Write(@"\u2029"); - break; - case '\'': - // only escape if this charater is being used as the delimiter - writer.Write((delimiter == '\'') ? @"\'" : @"'"); - break; - case '"': - // only escape if this charater is being used as the delimiter - writer.Write((delimiter == '"') ? "\\\"" : @""""); - break; - default: - if (c > '\u001f') - writer.Write(c); - else - StringUtils.WriteCharAsUnicode(writer, c); - break; - } - } - - public static void WriteEscapedJavaScriptString(TextWriter writer, string value, char delimiter, bool appendDelimiters) + public static void WriteEscapedJavaScriptString(StringBuilder writer, string value, char delimiter, bool appendDelimiters) { // leading delimiter if (appendDelimiters) - writer.Write(delimiter); + writer.Append(delimiter); if (value != null) { for (int i = 0; i < value.Length; i++) { - WriteEscapedJavaScriptChar(writer, value[i], delimiter); + char c = value[i]; + + switch (c) + { + case '\t': + writer.Append(@"\t"); + break; + case '\n': + writer.Append(@"\n"); + break; + case '\r': + writer.Append(@"\r"); + break; + case '\f': + writer.Append(@"\f"); + break; + case '\b': + writer.Append(@"\b"); + break; + case '\\': + writer.Append(@"\\"); + break; + case '\u0085': // Next Line + writer.Append(@"\u0085"); + break; + case '\u2028': // Line Separator + writer.Append(@"\u2028"); + break; + case '\u2029': // Paragraph Separator + writer.Append(@"\u2029"); + break; + case '\'': + // only escape if this charater is being used as the delimiter + writer.Append((delimiter == '\'') ? @"\'" : @"'"); + break; + case '"': + // only escape if this charater is being used as the delimiter + writer.Append((delimiter == '"') ? "\\\"" : @""""); + break; + default: + if (c > '\u001f') + writer.Append(c); + else + StringUtils.WriteCharAsUnicode(writer, c); + break; + } } } // trailing delimiter if (appendDelimiters) - writer.Write(delimiter); + writer.Append(delimiter); } public static string ToEscapedJavaScriptString(string value) @@ -109,11 +106,14 @@ namespace Newtonsoft.Json.Utilities public static string ToEscapedJavaScriptString(string value, char delimiter, bool appendDelimiters) { - using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(value) ?? 16)) - { - WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters); - return w.ToString(); - } + int length = value.Length; + if (appendDelimiters) + length += 2; + + StringBuilder builder = new StringBuilder(length); + + WriteEscapedJavaScriptString(builder, value, delimiter, appendDelimiters); + return builder.ToString(); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/StringUtils.cs b/Src/Newtonsoft.Json/Utilities/StringUtils.cs index 35d3cf18..79b19a89 100644 --- a/Src/Newtonsoft.Json/Utilities/StringUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/StringUtils.cs @@ -325,14 +325,13 @@ namespace Newtonsoft.Json.Utilities public static string ToCharAsUnicode(char c) { - using (StringWriter w = new StringWriter(CultureInfo.InvariantCulture)) - { - WriteCharAsUnicode(w, c); - return w.ToString(); - } + StringBuilder builder = new StringBuilder(); + WriteCharAsUnicode(builder, c); + + return builder.ToString(); } - public static void WriteCharAsUnicode(TextWriter writer, char c) + public static void WriteCharAsUnicode(StringBuilder writer, char c) { ValidationUtils.ArgumentNotNull(writer, "writer"); @@ -341,12 +340,12 @@ namespace Newtonsoft.Json.Utilities char h3 = MathUtils.IntToHex((c >> 4) & '\x000f'); char h4 = MathUtils.IntToHex(c & '\x000f'); - writer.Write('\\'); - writer.Write('u'); - writer.Write(h1); - writer.Write(h2); - writer.Write(h3); - writer.Write(h4); + writer.Append('\\'); + writer.Append('u'); + writer.Append(h1); + writer.Append(h2); + writer.Append(h3); + writer.Append(h4); } public static TSource ForgivingCaseSensitiveFind(this IEnumerable source, Func valueSelector, string testValue)