From 6ce972156dd187cb428593ec7a51574169b58ff6 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Wed, 24 Mar 2010 10:43:41 +0000 Subject: [PATCH] -New improved BSON writer -Performance improvement for reference loop checking --- .../Bson/BsonReaderTests.cs | 89 +++ .../Bson/BsonWriterTests.cs | 31 +- Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 13 +- Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs | 269 ++++++++ Src/Newtonsoft.Json/Bson/BsonReader.cs | 16 +- Src/Newtonsoft.Json/Bson/BsonWriter.cs | 593 +++++++++++------- .../Newtonsoft.Json.Compact.csproj | 1 + .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Silverlight.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + .../Serialization/DefaultReferenceResolver.cs | 7 + .../JsonSerializerInternalWriter.cs | 13 +- Src/Newtonsoft.Json/Utilities/MathUtils.cs | 11 + 13 files changed, 806 insertions(+), 240 deletions(-) create mode 100644 Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs diff --git a/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs b/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs index 8c6a0b39..a6489829 100644 --- a/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs +++ b/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs @@ -67,6 +67,95 @@ namespace Newtonsoft.Json.Tests.Bson Assert.IsFalse(reader.Read()); } + [Test] + public void WriteValues() + { + byte[] data = MiscellaneousUtils.HexToBytes("8C-00-00-00-12-30-00-FF-FF-FF-FF-FF-FF-FF-7F-12-31-00-FF-FF-FF-FF-FF-FF-FF-7F-10-32-00-FF-FF-FF-7F-10-33-00-FF-FF-FF-7F-10-34-00-FF-00-00-00-10-35-00-7F-00-00-00-02-36-00-02-00-00-00-61-00-01-37-00-00-00-00-00-00-00-F0-45-01-38-00-FF-FF-FF-FF-FF-FF-EF-7F-01-39-00-00-00-00-E0-FF-FF-EF-47-08-31-30-00-01-05-31-31-00-05-00-00-00-02-00-01-02-03-04-09-31-32-00-40-C5-E2-BA-E3-00-00-00-09-31-33-00-40-C5-E2-BA-E3-00-00-00-00"); + MemoryStream ms = new MemoryStream(data); + BsonReader reader = new BsonReader(ms); + reader.ReadRootValueAsArray = true; + reader.DateTimeKindHandling = DateTimeKind.Utc; + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(long.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(long.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(int.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(int.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(byte.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Integer, reader.TokenType); + Assert.AreEqual(sbyte.MaxValue, reader.Value); + Assert.AreEqual(typeof(long), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.String, reader.TokenType); + Assert.AreEqual("a", reader.Value); + Assert.AreEqual(typeof(string), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Float, reader.TokenType); + Assert.AreEqual(decimal.MaxValue, reader.Value); + Assert.AreEqual(typeof(double), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Float, reader.TokenType); + Assert.AreEqual(double.MaxValue, reader.Value); + Assert.AreEqual(typeof(double), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Float, reader.TokenType); + Assert.AreEqual(float.MaxValue, reader.Value); + Assert.AreEqual(typeof(double), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Boolean, reader.TokenType); + Assert.AreEqual(true, reader.Value); + Assert.AreEqual(typeof(bool), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Bytes, reader.TokenType); + Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4 }, reader.Value); + Assert.AreEqual(typeof(byte[]), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(new DateTime(2000, 12, 29, 12, 30, 0, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(new DateTime(2000, 12, 29, 12, 30, 0, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } + + [Test] public void ReadObjectBsonFromSite() { diff --git a/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs b/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs index 79307e2b..e99595ee 100644 --- a/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Bson/BsonWriterTests.cs @@ -48,11 +48,40 @@ namespace Newtonsoft.Json.Tests.Bson writer.WritePropertyName("Blah"); writer.WriteValue(1); writer.WriteEndObject(); - + string bson = MiscellaneousUtils.BytesToHex(ms.ToArray()); Assert.AreEqual("0F-00-00-00-10-42-6C-61-68-00-01-00-00-00-00", bson); } +#if !PocketPC && !NET20 + [Test] + public void WriteValues() + { + MemoryStream ms = new MemoryStream(); + BsonWriter writer = new BsonWriter(ms); + + writer.WriteStartArray(); + writer.WriteValue(long.MaxValue); + writer.WriteValue((ulong)long.MaxValue); + writer.WriteValue(int.MaxValue); + writer.WriteValue((uint)int.MaxValue); + writer.WriteValue(byte.MaxValue); + writer.WriteValue(sbyte.MaxValue); + writer.WriteValue('a'); + writer.WriteValue(decimal.MaxValue); + writer.WriteValue(double.MaxValue); + writer.WriteValue(float.MaxValue); + writer.WriteValue(true); + writer.WriteValue(new byte[] { 0, 1, 2, 3, 4 }); + writer.WriteValue(new DateTimeOffset(2000, 12, 29, 12, 30, 0, TimeSpan.Zero)); + writer.WriteValue(new DateTime(2000, 12, 29, 12, 30, 0, DateTimeKind.Utc)); + writer.WriteEnd(); + + string bson = MiscellaneousUtils.BytesToHex(ms.ToArray()); + Assert.AreEqual("8C-00-00-00-12-30-00-FF-FF-FF-FF-FF-FF-FF-7F-12-31-00-FF-FF-FF-FF-FF-FF-FF-7F-10-32-00-FF-FF-FF-7F-10-33-00-FF-FF-FF-7F-10-34-00-FF-00-00-00-10-35-00-7F-00-00-00-02-36-00-02-00-00-00-61-00-01-37-00-00-00-00-00-00-00-F0-45-01-38-00-FF-FF-FF-FF-FF-FF-EF-7F-01-39-00-00-00-00-E0-FF-FF-EF-47-08-31-30-00-01-05-31-31-00-05-00-00-00-02-00-01-02-03-04-09-31-32-00-40-C5-E2-BA-E3-00-00-00-09-31-33-00-40-C5-E2-BA-E3-00-00-00-00", bson); + } +#endif + [Test] public void WriteArrayBsonFromSite() { diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 6772418d..70e2e3d0 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -122,10 +122,13 @@ namespace Newtonsoft.Json.Tests private void SerializeSize(object value) { + // this is extremely slow with 5000 interations + int interations = 100; + byte[] jsonBytes = TimeOperation(() => { string json = null; - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < interations; i++) { json = JsonConvert.SerializeObject(value, Formatting.None); } @@ -136,7 +139,7 @@ namespace Newtonsoft.Json.Tests byte[] bsonBytes = TimeOperation(() => { MemoryStream ms = null; - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < interations; i++) { ms = new MemoryStream(); JsonSerializer serializer = new JsonSerializer(); @@ -152,7 +155,7 @@ namespace Newtonsoft.Json.Tests byte[] xmlBytes = TimeOperation(() => { MemoryStream ms = null; - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < interations; i++) { ms = new MemoryStream(); DataContractSerializer dataContractSerializer = new DataContractSerializer(value.GetType()); @@ -165,7 +168,7 @@ namespace Newtonsoft.Json.Tests byte[] wcfJsonBytes = TimeOperation(() => { MemoryStream ms = null; - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < interations; i++) { ms = new MemoryStream(); DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(value.GetType()); @@ -178,7 +181,7 @@ namespace Newtonsoft.Json.Tests byte[] binaryFormatterBytes = TimeOperation(() => { MemoryStream ms = null; - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < interations; i++) { ms = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); diff --git a/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs b/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs new file mode 100644 index 00000000..7ff69655 --- /dev/null +++ b/Src/Newtonsoft.Json/Bson/BsonBinaryWriter.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Bson +{ + internal class BsonBinaryWriter + { + private static readonly Encoding Encoding = Encoding.UTF8; + + private readonly BinaryWriter _writer; + + private byte[] _largeByteBuffer; + private int _maxChars; + + public BsonBinaryWriter(Stream stream) + { + _writer = new BinaryWriter(stream); + } + + public void Flush() + { + _writer.Flush(); + } + + public void WriteToken(BsonToken t) + { + CalculateSize(t); + WriteTokenInternal(t); + } + + private void WriteTokenInternal(BsonToken t) + { + switch (t.Type) + { + case BsonType.Object: + { + BsonObject value = (BsonObject) t; + _writer.Write(value.CalculatedSize); + foreach (BsonProperty property in value) + { + _writer.Write((sbyte)property.Value.Type); + WriteString((string)property.Name.Value, property.Name.ByteCount, null); + WriteTokenInternal(property.Value); + } + _writer.Write((byte)0); + } + break; + case BsonType.Array: + { + BsonArray value = (BsonArray) t; + _writer.Write(value.CalculatedSize); + int index = 0; + foreach (BsonToken c in value) + { + _writer.Write((sbyte)c.Type); + WriteString(index.ToString(CultureInfo.InvariantCulture), Encoding.GetByteCount(index.ToString(CultureInfo.InvariantCulture)), null); + WriteTokenInternal(c); + index++; + } + _writer.Write((byte)0); + } + break; + case BsonType.Integer: + { + BsonValue value = (BsonValue) t; + _writer.Write(Convert.ToInt32(value.Value, CultureInfo.InvariantCulture)); + } + break; + case BsonType.Long: + { + BsonValue value = (BsonValue)t; + _writer.Write(Convert.ToInt64(value.Value, CultureInfo.InvariantCulture)); + } + break; + case BsonType.Number: + { + BsonValue value = (BsonValue)t; + _writer.Write(Convert.ToDouble(value.Value, CultureInfo.InvariantCulture)); + } + break; + case BsonType.String: + { + BsonString value = (BsonString)t; + WriteString((string)value.Value, value.ByteCount, value.CalculatedSize - 4); + } + break; + case BsonType.Boolean: + { + BsonValue value = (BsonValue)t; + _writer.Write((bool)value.Value); + } + break; + case BsonType.Null: + case BsonType.Undefined: + break; + case BsonType.Date: + { + BsonValue value = (BsonValue)t; + + long ticks = 0; + + if (value.Value is DateTime) + { + DateTime dateTime = (DateTime) value.Value; + dateTime = dateTime.ToUniversalTime(); + ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTime); + } +#if !PocketPC && !NET20 + else + { + DateTimeOffset dateTimeOffset = (DateTimeOffset) value.Value; + ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTimeOffset.UtcDateTime, dateTimeOffset.Offset); + } +#endif + + _writer.Write(ticks); + } + break; + case BsonType.Binary: + { + BsonValue value = (BsonValue)t; + + byte[] data = (byte[])value.Value; + _writer.Write(data.Length); + _writer.Write((byte)BsonBinaryType.Data); + _writer.Write(data); + } + break; + case BsonType.Oid: + { + BsonValue value = (BsonValue)t; + + byte[] data = (byte[])value.Value; + _writer.Write(data); + } + break; + default: + throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); + } + } + + private void WriteString(string s, int byteCount, int? calculatedlengthPrefix) + { + if (calculatedlengthPrefix != null) + _writer.Write(calculatedlengthPrefix.Value); + + 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 int CalculateSize(int stringByteCount) + { + return stringByteCount + 1; + } + + private int CalculateSizeWithLength(int stringByteCount, bool includeSize) + { + int baseSize = (includeSize) + ? 5 // size bytes + terminator + : 1; // terminator + + return baseSize + stringByteCount; + } + + private int CalculateSize(BsonToken t) + { + switch (t.Type) + { + case BsonType.Object: + { + BsonObject value = (BsonObject) t; + + int bases = 4; + foreach (BsonProperty p in value) + { + int size = 1; + size += CalculateSize(p.Name); + size += CalculateSize(p.Value); + + bases += size; + } + bases += 1; + value.CalculatedSize = bases; + return bases; + } + case BsonType.Array: + { + BsonArray value = (BsonArray)t; + + int size = 4; + int index = 0; + foreach (BsonToken c in value) + { + size += 1; + size += CalculateSize(MathUtils.IntLength(index)); + size += CalculateSize(c); + index++; + } + size += 1; + value.CalculatedSize = size; + + return value.CalculatedSize; + } + case BsonType.Integer: + return 4; + case BsonType.Long: + return 8; + case BsonType.Number: + return 8; + case BsonType.String: + { + BsonString value = (BsonString)t; + string s = (string) value.Value; + value.ByteCount = Encoding.GetByteCount(s); + value.CalculatedSize = CalculateSizeWithLength(value.ByteCount, value.IncludeLength); + + return value.CalculatedSize; + } + case BsonType.Boolean: + return 1; + case BsonType.Null: + case BsonType.Undefined: + return 0; + case BsonType.Date: + return 8; + case BsonType.Binary: + { + BsonValue value = (BsonValue) t; + + byte[] data = (byte[])value.Value; + value.CalculatedSize = 4 + 1 + data.Length; + + return value.CalculatedSize; + } + case BsonType.Oid: + return 12; + default: + throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index f7858d06..710fe961 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -72,11 +72,11 @@ namespace Newtonsoft.Json.Bson private class ContainerContext { - public readonly JTokenType Type; + public readonly BsonType Type; public int Length; public int Position; - public ContainerContext(JTokenType type) + public ContainerContext(BsonType type) { Type = type; } @@ -210,7 +210,7 @@ namespace Newtonsoft.Json.Bson SetToken(JsonToken.StartObject); _bsonReaderState = BsonReaderState.CodeWScopeScopeObject; - ContainerContext newContext = new ContainerContext(JTokenType.Object); + ContainerContext newContext = new ContainerContext(BsonType.Object); PushContext(newContext); newContext.Length = ReadInt32(); @@ -289,7 +289,7 @@ namespace Newtonsoft.Json.Bson case State.Start: { JsonToken token = (!_readRootValueAsArray) ? JsonToken.StartObject : JsonToken.StartArray; - JTokenType type = (!_readRootValueAsArray) ? JTokenType.Object : JTokenType.Array; + BsonType type = (!_readRootValueAsArray) ? BsonType.Object : BsonType.Array; SetToken(token); ContainerContext newContext = new ContainerContext(type); @@ -316,7 +316,7 @@ namespace Newtonsoft.Json.Bson if (context.Position < lengthMinusEnd) { - if (context.Type == JTokenType.Array) + if (context.Type == BsonType.Array) { ReadElement(); ReadType(_currentElementType); @@ -337,7 +337,7 @@ namespace Newtonsoft.Json.Bson if (_currentContext != null) MovePosition(context.Length); - JsonToken endToken = (context.Type == JTokenType.Object) ? JsonToken.EndObject : JsonToken.EndArray; + JsonToken endToken = (context.Type == BsonType.Object) ? JsonToken.EndObject : JsonToken.EndArray; SetToken(endToken); return true; } @@ -396,7 +396,7 @@ namespace Newtonsoft.Json.Bson { SetToken(JsonToken.StartObject); - ContainerContext newContext = new ContainerContext(JTokenType.Object); + ContainerContext newContext = new ContainerContext(BsonType.Object); PushContext(newContext); newContext.Length = ReadInt32(); break; @@ -405,7 +405,7 @@ namespace Newtonsoft.Json.Bson { SetToken(JsonToken.StartArray); - ContainerContext newContext = new ContainerContext(JTokenType.Array); + ContainerContext newContext = new ContainerContext(BsonType.Array); PushContext(newContext); newContext.Length = ReadInt32(); break; diff --git a/Src/Newtonsoft.Json/Bson/BsonWriter.cs b/Src/Newtonsoft.Json/Bson/BsonWriter.cs index 7fa0808a..82940a69 100644 --- a/Src/Newtonsoft.Json/Bson/BsonWriter.cs +++ b/Src/Newtonsoft.Json/Bson/BsonWriter.cs @@ -24,6 +24,8 @@ #endregion using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Text; using Newtonsoft.Json.Utilities; @@ -32,17 +34,115 @@ using System.Globalization; namespace Newtonsoft.Json.Bson { + internal abstract class BsonToken + { + public abstract BsonType Type { get; } + public BsonToken Parent { get; set; } + public int CalculatedSize { get; set; } + } + + internal class BsonObject : BsonToken, IEnumerable + { + private readonly List _children = new List(); + + public void Add(string name, BsonToken token) + { + _children.Add(new BsonProperty { Name = new BsonString(name, false), Value = token }); + token.Parent = this; + } + + public override BsonType Type + { + get { return BsonType.Object; } + } + + public IEnumerator GetEnumerator() + { + return _children.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal class BsonArray : BsonToken, IEnumerable + { + private readonly List _children = new List(); + + public void Add(BsonToken token) + { + _children.Add(token); + token.Parent = this; + } + + public override BsonType Type + { + get { return BsonType.Array; } + } + + public IEnumerator GetEnumerator() + { + return _children.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal class BsonValue : BsonToken + { + private object _value; + private BsonType _type; + + public BsonValue(object value, BsonType type) + { + _value = value; + _type = type; + } + + public object Value + { + get { return _value; } + } + + public override BsonType Type + { + get { return _type; } + } + } + + internal class BsonString : BsonValue + { + public int ByteCount { get; set; } + public bool IncludeLength { get; set; } + + public BsonString(object value, bool includeLength) + : base(value, BsonType.String) + { + IncludeLength = includeLength; + } + } + + internal class BsonProperty + { + public BsonString Name { get; set; } + public BsonToken Value { get; set; } + } + /// /// Represents a writer that provides a fast, non-cached, forward-only way of generating Json data. /// - public class BsonWriter : JTokenWriter + public class BsonWriter : JsonWriter { - private static readonly Encoding Encoding = Encoding.UTF8; + private readonly BsonBinaryWriter _writer; - private readonly BinaryWriter _writer; - - private byte[] _largeByteBuffer; - private int _maxChars; + private BsonToken _root; + private BsonToken _parent; + private string _propertyName; /// /// Initializes a new instance of the class. @@ -51,7 +151,7 @@ namespace Newtonsoft.Json.Bson public BsonWriter(Stream stream) { ValidationUtils.ArgumentNotNull(stream, "stream"); - _writer = new BinaryWriter(stream); + _writer = new BsonBinaryWriter(stream); } /// @@ -62,218 +162,6 @@ namespace Newtonsoft.Json.Bson _writer.Flush(); } - private void WriteToken(JToken t) - { - switch (t.Type) - { - case JTokenType.Object: - { - int size = CalculateSize(t); - _writer.Write(size); - foreach (JProperty property in t) - { - _writer.Write((sbyte)GetTypeNumber(property.Value)); - WriteString(property.Name, false); - WriteToken(property.Value); - } - _writer.Write((byte)0); - } - break; - case JTokenType.Array: - { - int size = CalculateSize(t); - _writer.Write(size); - int index = 0; - foreach (JToken c in t) - { - _writer.Write((sbyte)GetTypeNumber(c)); - WriteString(index.ToString(CultureInfo.InvariantCulture), false); - WriteToken(c); - index++; - } - _writer.Write((byte)0); - } - break; - case JTokenType.Integer: - _writer.Write(Convert.ToInt32(((JValue)t).Value, CultureInfo.InvariantCulture)); - break; - case JTokenType.Float: - _writer.Write(Convert.ToDouble(((JValue)t).Value, CultureInfo.InvariantCulture)); - break; - case JTokenType.String: - WriteString((string)t, true); - break; - case JTokenType.Boolean: - _writer.Write((bool)t); - break; - case JTokenType.Null: - case JTokenType.Undefined: - break; - case JTokenType.Date: - DateTime dateTime = (DateTime)t; - dateTime = dateTime.ToUniversalTime(); - long ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTime); - _writer.Write(ticks); - break; - case JTokenType.Bytes: - byte[] data = (byte[])t; - _writer.Write(data.Length); - _writer.Write((byte)BsonBinaryType.Data); - _writer.Write(data); - break; - case JTokenType.Raw: - // hacky - JRaw r = (JRaw)t; - _writer.Write((byte[])r.Value); - break; - default: - throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); - } - } - - private void WriteString(string s, bool includeLength) - { - int byteCount = Encoding.GetByteCount(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) - { - switch (t.Type) - { - case JTokenType.Object: - return BsonType.Object; - case JTokenType.Array: - return BsonType.Array; - case JTokenType.Integer: - return BsonType.Integer; - case JTokenType.Float: - return BsonType.Number; - case JTokenType.String: - return BsonType.String; - case JTokenType.Boolean: - return BsonType.Boolean; - case JTokenType.Null: - case JTokenType.Undefined: - return BsonType.Null; - case JTokenType.Date: - return BsonType.Date; - case JTokenType.Bytes: - return BsonType.Binary; - case JTokenType.Raw: - // hacky - return BsonType.Oid; - default: - throw new ArgumentOutOfRangeException("t", "Unexpected token when resolving JSON type to BSON type: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); - } - } - - private int CalculateSize(string s) - { - 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 - - return baseSize + stringByteCount; - } - - private int CalculateSize(JToken t) - { - switch (t.Type) - { - case JTokenType.Object: - { - int bases = 4; - foreach (JProperty p in t) - { - bases += CalculateSize(p); - } - bases += 1; - return bases; - } - case JTokenType.Array: - { - int bases = 4; - int index = 0; - foreach (JToken c in t) - { - bases += 1; - bases += CalculateSize(index.ToString(CultureInfo.InvariantCulture)); - bases += CalculateSize(c); - index++; - } - bases += 1; - return bases; - } - case JTokenType.Property: - JProperty property = (JProperty)t; - int ss = 1; - ss += CalculateSize(property.Name); - ss += CalculateSize(property.Value); - return ss; - case JTokenType.Integer: - return 4; - case JTokenType.Float: - return 8; - case JTokenType.String: - string s = (string)t; - - return CalculateSizeWithLength(s, true); - case JTokenType.Boolean: - return 1; - case JTokenType.Null: - case JTokenType.Undefined: - return 0; - case JTokenType.Date: - return 8; - case JTokenType.Bytes: - byte[] data = (byte[])t; - return 4 + 1 + data.Length; - case JTokenType.Raw: - // hacky - return 12; - default: - throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); - } - } - /// /// Writes the end. /// @@ -281,10 +169,11 @@ namespace Newtonsoft.Json.Bson protected override void WriteEnd(JsonToken token) { base.WriteEnd(token); + RemoveParent(); if (Top == 0) { - WriteToken(Token); + _writer.WriteToken(_root); } } @@ -337,7 +226,269 @@ namespace Newtonsoft.Json.Bson // hack to update the writer state AutoComplete(JsonToken.Undefined); - AddValue(new JRaw(value), JsonToken.Raw); + AddValue(value, BsonType.Oid); } + + /// + /// Writes the beginning of a Json array. + /// + public override void WriteStartArray() + { + base.WriteStartArray(); + + AddParent(new BsonArray()); + } + + /// + /// Writes the beginning of a Json object. + /// + public override void WriteStartObject() + { + base.WriteStartObject(); + + AddParent(new BsonObject()); + } + + public override void WritePropertyName(string name) + { + base.WritePropertyName(name); + + _propertyName = name; + } + + private void AddParent(BsonToken container) + { + AddToken(container); + _parent = container; + } + + private void RemoveParent() + { + _parent = _parent.Parent; + } + + private void AddValue(object value, BsonType type) + { + AddToken(new BsonValue(value, type)); + } + + internal void AddToken(BsonToken token) + { + if (_parent != null) + { + if (_parent is BsonObject) + { + ((BsonObject)_parent).Add(_propertyName, token); + _propertyName = null; + } + else + { + ((BsonArray)_parent).Add(token); + } + } + else + { + _parent = token; + _root = token; + } + } + + #region WriteValue methods + /// + /// Writes a null value. + /// + public override void WriteNull() + { + base.WriteNull(); + AddValue(null, BsonType.Null); + } + + /// + /// Writes an undefined value. + /// + public override void WriteUndefined() + { + base.WriteUndefined(); + AddValue(null, BsonType.Undefined); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(string value) + { + base.WriteValue(value); + AddToken(new BsonString(value ?? string.Empty, true)); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(int value) + { + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(uint value) + { + if (value > int.MaxValue) + throw new JsonWriterException("Value is too large to fit in a signed 32 bit integer. BSON does not support unsigned values."); + + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(long value) + { + base.WriteValue(value); + AddValue(value, BsonType.Long); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(ulong value) + { + if (value > long.MaxValue) + throw new JsonWriterException("Value is too large to fit in a signed 64 bit integer. BSON does not support unsigned values."); + + base.WriteValue(value); + AddValue(value, BsonType.Long); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(float value) + { + base.WriteValue(value); + AddValue(value, BsonType.Number); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(double value) + { + base.WriteValue(value); + AddValue(value, BsonType.Number); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(bool value) + { + base.WriteValue(value); + AddValue(value, BsonType.Boolean); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(short value) + { + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(ushort value) + { + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(char value) + { + base.WriteValue(value); + AddToken(new BsonString(value.ToString(), true)); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(byte value) + { + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(sbyte value) + { + base.WriteValue(value); + AddValue(value, BsonType.Integer); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(decimal value) + { + base.WriteValue(value); + AddValue(value, BsonType.Number); + } + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(DateTime value) + { + base.WriteValue(value); + AddValue(value, BsonType.Date); + } + +#if !PocketPC && !NET20 + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(DateTimeOffset value) + { + base.WriteValue(value); + AddValue(value, BsonType.Date); + } +#endif + + /// + /// Writes a value. + /// + /// The value to write. + public override void WriteValue(byte[] value) + { + base.WriteValue(value); + AddValue(value, BsonType.Binary); + } + #endregion } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj index fa2ebc75..04581ede 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj @@ -58,6 +58,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index 204dadd4..0021f907 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -78,6 +78,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 2b11fe39..4745f601 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -55,6 +55,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 7b27b53a..79acf220 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -81,6 +81,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs index db8c47a9..cd226ff7 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using Newtonsoft.Json.Utilities; using System.Globalization; @@ -43,7 +44,13 @@ namespace Newtonsoft.Json.Serialization int IEqualityComparer.GetHashCode(object obj) { +#if !PocketPC + // put objects in a bucket based on their reference + return RuntimeHelpers.GetHashCode(obj); +#else + // put all objects in the same bucket so ReferenceEquals is called on all return -1; +#endif } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 94353a47..43bafd81 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -199,15 +199,18 @@ namespace Newtonsoft.Json.Serialization return; } - if (!CheckForCircularReference(memberValue, property.ReferenceLoopHandling)) + if (!CheckForCircularReference(memberValue, property.ReferenceLoopHandling, contract)) return; writer.WritePropertyName(propertyName); SerializeValue(writer, memberValue, memberConverter, contract); } - private bool CheckForCircularReference(object value, ReferenceLoopHandling? referenceLoopHandling) + private bool CheckForCircularReference(object value, ReferenceLoopHandling? referenceLoopHandling, JsonContract contract) { + if (value == null || contract is JsonPrimitiveContract) + return true; + if (SerializeStack.IndexOf(value) != -1) { switch (referenceLoopHandling.GetValueOrDefault(Serializer.ReferenceLoopHandling)) @@ -358,7 +361,7 @@ namespace Newtonsoft.Json.Serialization } else { - if (!CheckForCircularReference(value, null)) + if (!CheckForCircularReference(value, null, contract)) return; SerializeStack.Add(value); @@ -416,7 +419,7 @@ namespace Newtonsoft.Json.Serialization } else { - if (!CheckForCircularReference(value, null)) + if (!CheckForCircularReference(value, null, contract)) continue; SerializeValue(writer, value, null, valueContract); @@ -503,7 +506,7 @@ namespace Newtonsoft.Json.Serialization } else { - if (!CheckForCircularReference(value, null)) + if (!CheckForCircularReference(value, null, contract)) continue; writer.WritePropertyName(propertyName); diff --git a/Src/Newtonsoft.Json/Utilities/MathUtils.cs b/Src/Newtonsoft.Json/Utilities/MathUtils.cs index 07520830..db489eb9 100644 --- a/Src/Newtonsoft.Json/Utilities/MathUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MathUtils.cs @@ -31,6 +31,17 @@ namespace Newtonsoft.Json.Utilities { internal class MathUtils { + public static int IntLength(int i) + { + if (i < 0) + throw new ArgumentOutOfRangeException(); + + if (i == 0) + return 1; + + return (int)Math.Floor(Math.Log10(i)) + 1; + } + public static int HexToInt(char h) { if ((h >= '0') && (h <= '9'))