From 4ee5fc35783dda7f238581a7918c9c23e8e4b723 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Sat, 15 Mar 2008 22:31:36 +0000 Subject: [PATCH] New features: Added Skip() method to JsonReader. Skips the current JSON token and any child tokens. JsonSerializer now has the ability to populate existing members on an object. This behaviour is controlled by the ObjectCreationHandling property. Changes: Improved behaviour of JsonReader.Depth property. Fixes: Floating point numbers now use ToString("r") to avoid an overflow error when serializing and deserializing a boundary value. JsonSerializer now correctly serializes and deserializes DBNull. JsonSerializer no longer errors when skipping a multi-token JSON structure. --- .../JsonSerializerTest.cs | 136 +++++- .../JsonTextReaderTest.cs | 57 +++ Src/Newtonsoft.Json/JavaScriptConvert.cs | 4 +- Src/Newtonsoft.Json/JsonReader.cs | 55 ++- Src/Newtonsoft.Json/JsonSerializer.cs | 253 +++++++---- Src/Newtonsoft.Json/JsonTextReader.cs | 2 +- Src/Newtonsoft.Json/JsonWriter.cs | 2 +- Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 2 + Src/Newtonsoft.Json/ObjectCreationHandling.cs | 39 ++ .../Utilities/CollectionUtils.cs | 97 ++++- .../Utilities/DictionaryWrapper.cs | 400 ++++++++++++++++++ .../Utilities/MiscellaneousUtils.cs | 4 - .../Utilities/ReflectionUtils.cs | 9 +- 13 files changed, 944 insertions(+), 116 deletions(-) create mode 100644 Src/Newtonsoft.Json/ObjectCreationHandling.cs create mode 100644 Src/Newtonsoft.Json/Utilities/DictionaryWrapper.cs diff --git a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs index 5680279b..6d03bb2b 100644 --- a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs @@ -118,6 +118,30 @@ namespace Newtonsoft.Json.Tests White } + public class ClassWithArray + { + private readonly IList bar; + private string foo; + + public ClassWithArray() + { + bar = new List() { int.MaxValue }; + } + + [JsonProperty("foo")] + public string Foo + { + get { return foo; } + set { foo = value; } + } + + [JsonProperty("bar")] + public IList Bar + { + get { return bar; } + } + } + public class JsonSerializerTest : TestFixtureBase { [Test] @@ -311,6 +335,9 @@ namespace Newtonsoft.Json.Tests { get { return _ignoredProperty; } } + + [JsonIgnore] + public Product IgnoredObject = new Product(); } [Test] @@ -320,7 +347,7 @@ namespace Newtonsoft.Json.Tests Assert.AreEqual(@"{""Field"":0,""Property"":21}", json); - JsonIgnoreAttributeTestClass c = JavaScriptConvert.DeserializeObject(@"{""Field"":99,""Property"":-1,""IgnoredField"":-1}"); + JsonIgnoreAttributeTestClass c = JavaScriptConvert.DeserializeObject(@"{""Field"":99,""Property"":-1,""IgnoredField"":-1,""IgnoredObject"":[1,2,3,4,5]}"); Assert.AreEqual(0, c.IgnoredField); Assert.AreEqual(99, c.Field); @@ -780,5 +807,112 @@ keyword such as type of business."" string json = JavaScriptConvert.SerializeObject(null); Assert.AreEqual("null", json); } + + [Test] + public void CanDeserializeIntArrayWhenNotFirstPropertyInJson() + { + string json = "{foo:'hello',bar:[1,2,3]}"; + ClassWithArray wibble = JavaScriptConvert.DeserializeObject(json); + Assert.AreEqual("hello", wibble.Foo); + + Assert.AreEqual(4, wibble.Bar.Count); + Assert.AreEqual(int.MaxValue, wibble.Bar[0]); + Assert.AreEqual(1, wibble.Bar[1]); + Assert.AreEqual(2, wibble.Bar[2]); + Assert.AreEqual(3, wibble.Bar[3]); + } + + [Test] + public void CanDeserializeIntArray_WhenArrayIsFirstPropertyInJson() + { + string json = "{bar:[1,2,3], foo:'hello'}"; + ClassWithArray wibble = JavaScriptConvert.DeserializeObject(json); + Assert.AreEqual("hello", wibble.Foo); + + Assert.AreEqual(4, wibble.Bar.Count); + Assert.AreEqual(int.MaxValue, wibble.Bar[0]); + Assert.AreEqual(1, wibble.Bar[1]); + Assert.AreEqual(2, wibble.Bar[2]); + Assert.AreEqual(3, wibble.Bar[3]); + } + + [Test] + public void ObjectCreationHandlingReplace() + { + string json = "{bar:[1,2,3], foo:'hello'}"; + + JsonSerializer s = new JsonSerializer(); + s.ObjectCreationHandling = ObjectCreationHandling.Replace; + + ClassWithArray wibble = (ClassWithArray)s.Deserialize(new StringReader(json), typeof(ClassWithArray)); + + Assert.AreEqual("hello", wibble.Foo); + + Assert.AreEqual(1, wibble.Bar.Count); + } + + [Test] + public void CanDeserializeSerializedJson() + { + ClassWithArray wibble = new ClassWithArray(); + wibble.Foo = "hello"; + wibble.Bar.Add(1); + wibble.Bar.Add(2); + wibble.Bar.Add(3); + string json = JavaScriptConvert.SerializeObject(wibble); + + ClassWithArray wibbleOut = JavaScriptConvert.DeserializeObject(json); + Assert.AreEqual("hello", wibbleOut.Foo); + + Assert.AreEqual(5, wibbleOut.Bar.Count); + Assert.AreEqual(int.MaxValue, wibbleOut.Bar[0]); + Assert.AreEqual(int.MaxValue, wibbleOut.Bar[1]); + Assert.AreEqual(1, wibbleOut.Bar[2]); + Assert.AreEqual(2, wibbleOut.Bar[3]); + Assert.AreEqual(3, wibbleOut.Bar[4]); + } + + public class ConverableMembers + { + public string String = "string"; + public int Int32 = int.MaxValue; + public uint UInt32 = uint.MaxValue; + public byte Byte = byte.MaxValue; + public sbyte SByte = sbyte.MaxValue; + public short Short = short.MaxValue; + public ushort UShort = ushort.MaxValue; + public long Long = long.MaxValue; + public ulong ULong = long.MaxValue; + public double Double = double.MaxValue; + public float Float = float.MaxValue; + public DBNull DBNull = DBNull.Value; + public bool Bool = true; + public char Char = '\0'; + } + + [Test] + public void SerializeConverableObjects() + { + string json = JavaScriptConvert.SerializeObject(new ConverableMembers()); + + Assert.AreEqual(@"{""String"":""string"",""Int32"":2147483647,""UInt32"":4294967295,""Byte"":255,""SByte"":127,""Short"":32767,""UShort"":65535,""Long"":9223372036854775807,""ULong"":9223372036854775807,""Double"":1.7976931348623157E+308,""Float"":3.40282347E+38,""DBNull"":null,""Bool"":true,""Char"":""\u0000""}", json); + + ConverableMembers c = JavaScriptConvert.DeserializeObject(json); + Assert.AreEqual("string", c.String); + Assert.AreEqual(double.MaxValue, c.Double); + Assert.AreEqual(DBNull.Value, c.DBNull); + } + + [Test] + public void SerializeStack() + { + Stack s = new Stack(); + s.Push(1); + s.Push(2); + s.Push(3); + + string json = JavaScriptConvert.SerializeObject(s); + Assert.AreEqual("[3,2,1]", json); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 05c9f302..0d5b9a60 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -120,6 +120,57 @@ namespace Newtonsoft.Json.Tests } } + [Test] + public void Depth() + { + string input = "{value:'Purple\\r \\n monkey\\'s:\\tdishwasher',array:[1,2]}"; + + StringReader sr = new StringReader(input); + + using (JsonReader jsonReader = new JsonTextReader(sr)) + { + Assert.AreEqual(0, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); + Assert.AreEqual(0, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.String); + Assert.AreEqual(jsonReader.Value, "Purple\r \n monkey's:\tdishwasher"); + Assert.AreEqual(jsonReader.QuoteChar, '\''); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.StartArray); + Assert.AreEqual(2, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); + Assert.AreEqual(3, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.Integer); + Assert.AreEqual(3, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.EndArray); + Assert.AreEqual(1, jsonReader.Depth); + + jsonReader.Read(); + Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); + Assert.AreEqual(0, jsonReader.Depth); + } + } + [Test] public void ReadingEscapedStrings() { @@ -129,19 +180,25 @@ namespace Newtonsoft.Json.Tests using (JsonReader jsonReader = new JsonTextReader(sr)) { + Assert.AreEqual(0, jsonReader.Depth); + jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.StartObject); + Assert.AreEqual(0, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.PropertyName); + Assert.AreEqual(1, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.String); Assert.AreEqual(jsonReader.Value, "Purple\r \n monkey's:\tdishwasher"); Assert.AreEqual(jsonReader.QuoteChar, '\''); + Assert.AreEqual(1, jsonReader.Depth); jsonReader.Read(); Assert.AreEqual(jsonReader.TokenType, JsonToken.EndObject); + Assert.AreEqual(0, jsonReader.Depth); } } diff --git a/Src/Newtonsoft.Json/JavaScriptConvert.cs b/Src/Newtonsoft.Json/JavaScriptConvert.cs index b9bb53f0..a40281fc 100644 --- a/Src/Newtonsoft.Json/JavaScriptConvert.cs +++ b/Src/Newtonsoft.Json/JavaScriptConvert.cs @@ -252,7 +252,7 @@ namespace Newtonsoft.Json /// A Json string representation of the . public static string ToString(float value) { - return value.ToString(null, CultureInfo.InvariantCulture); + return value.ToString("R", CultureInfo.InvariantCulture); } /// @@ -262,7 +262,7 @@ namespace Newtonsoft.Json /// A Json string representation of the . public static string ToString(double value) { - return value.ToString(null, CultureInfo.InvariantCulture); + return value.ToString("R", CultureInfo.InvariantCulture); } /// diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 7dea5e65..b50027dc 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -107,7 +107,14 @@ namespace Newtonsoft.Json public int Depth { - get { return _top - 1; } + get + { + int depth = _top - 1; + if (IsStartToken(TokenType)) + return depth - 1; + else + return depth; + } } /// @@ -148,6 +155,18 @@ namespace Newtonsoft.Json /// public abstract bool Read(); + public void Skip() + { + if (IsStartToken(TokenType)) + { + int depth = Depth; + + while (Read() && (depth < Depth)) + { + } + } + } + protected void SetToken(JsonToken newToken) { SetToken(newToken, null); @@ -185,6 +204,7 @@ namespace Newtonsoft.Json break; case JsonToken.PropertyName: _currentState = State.Property; + Push(JsonTokenType.Property); break; case JsonToken.Undefined: case JsonToken.Integer: @@ -197,6 +217,10 @@ namespace Newtonsoft.Json break; } + JsonTokenType current = Peek(); + if (current == JsonTokenType.Property && _currentState == State.PostValue) + Pop(); + if (value != null) { _value = value; @@ -216,7 +240,7 @@ namespace Newtonsoft.Json if (GetTypeForCloseToken(endToken) != currentObject) throw new JsonReaderException(string.Format("JsonToken {0} is not valid for closing JsonType {1}.", endToken, currentObject)); } - + protected void SetStateBasedOnCurrent() { JsonTokenType currentObject = Peek(); @@ -240,6 +264,33 @@ namespace Newtonsoft.Json } } + private bool IsStartToken(JsonToken token) + { + switch (token) + { + case JsonToken.StartObject: + case JsonToken.StartArray: + case JsonToken.StartConstructor: + case JsonToken.PropertyName: + return true; + case JsonToken.None: + case JsonToken.Comment: + case JsonToken.Integer: + case JsonToken.Float: + case JsonToken.String: + case JsonToken.Boolean: + case JsonToken.Null: + case JsonToken.Undefined: + case JsonToken.EndObject: + case JsonToken.EndArray: + case JsonToken.EndConstructor: + case JsonToken.Date: + return false; + default: + throw new ArgumentOutOfRangeException("token", token, "Unexpected JsonToken value."); + } + } + private JsonTokenType GetTypeForCloseToken(JsonToken token) { switch (token) diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 633d00c6..b529adeb 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -34,6 +34,7 @@ using Newtonsoft.Json.Utilities; using System.Globalization; using System.Linq; using Newtonsoft.Json.Linq; +using System.Collections.ObjectModel; namespace Newtonsoft.Json { @@ -45,6 +46,7 @@ namespace Newtonsoft.Json { private ReferenceLoopHandling _referenceLoopHandling; private MissingMemberHandling _missingMemberHandling; + private ObjectCreationHandling _objectCreationHandling; private NullValueHandling _nullValueHandling; private int _level; private JsonConverterCollection _converters; @@ -95,6 +97,18 @@ namespace Newtonsoft.Json } } + public ObjectCreationHandling ObjectCreationHandling + { + get { return _objectCreationHandling; } + set + { + if (value < ObjectCreationHandling.Auto || value > ObjectCreationHandling.Replace) + throw new ArgumentOutOfRangeException("value"); + + _objectCreationHandling = value; + } + } + public JsonConverterCollection Converters { get @@ -113,6 +127,8 @@ namespace Newtonsoft.Json { _referenceLoopHandling = ReferenceLoopHandling.Error; _missingMemberHandling = MissingMemberHandling.Error; + _nullValueHandling = NullValueHandling.Include; + _objectCreationHandling = ObjectCreationHandling.Auto; } #region Deserialize @@ -142,11 +158,16 @@ namespace Newtonsoft.Json return null; if (objectType != null) - return GetObject(reader, objectType); + return CreateObject(reader, objectType, null); else return CreateJToken(reader); } + public object Deserialize(StringReader reader, Type objectType) + { + return Deserialize(new JsonTextReader(reader), objectType); + } + private JToken CreateJToken(JsonReader reader) { JToken token; @@ -159,7 +180,7 @@ namespace Newtonsoft.Json return token; } - private object GetObject(JsonReader reader, Type objectType) + private object CreateObject(JsonReader reader, Type objectType, object existingValue) { _level++; @@ -177,10 +198,37 @@ namespace Newtonsoft.Json // populate a typed object or generic dictionary/array // depending upon whether an objectType was supplied case JsonToken.StartObject: - value = (objectType != null) ? PopulateObject(reader, objectType) : CreateJToken(reader); + if (objectType == null) + { + value = CreateJToken(reader); + } + else if (typeof(IDictionary).IsAssignableFrom(objectType) || ReflectionUtils.IsSubClass(objectType, typeof(IDictionary<,>))) + { + if (existingValue == null) + value = CreateAndPopulateDictionary(reader, objectType); + else + value = PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(existingValue), reader); + } + else + { + if (existingValue == null) + value = CreateAndPopulateObject(reader, objectType); + else + value = PopulateObject(existingValue, reader, objectType); + } break; case JsonToken.StartArray: - value = (objectType != null) ? PopulateList(reader, objectType) : CreateJToken(reader); + if (objectType != null) + { + if (existingValue == null) + value = CreateAndPopulateList(reader, objectType); + else + value = PopulateList(CollectionUtils.CreateListWrapper(existingValue), ReflectionUtils.GetListItemType(objectType), reader); + } + else + { + value = CreateJToken(reader); + } break; case JsonToken.Integer: case JsonToken.Float: @@ -197,7 +245,10 @@ namespace Newtonsoft.Json break; case JsonToken.Null: case JsonToken.Undefined: - value = null; + if (objectType == typeof(DBNull)) + value = DBNull.Value; + else + value = null; break; default: throw new JsonSerializationException("Unexpected token while deserializing object: " + reader.TokenType); @@ -209,25 +260,6 @@ namespace Newtonsoft.Json return value; } - private DateTime CreateDate(JsonReader reader) - { - // move to parameter - if (!reader.Read()) - throw new JsonSerializationException("Unexpected end while creating date."); - - //TODO: add support for Date constructor with multiple arguments - long ticks = Convert.ToInt64(reader.Value); - DateTime date = JavaScriptConvert.ConvertJavaScriptTicksToDateTime(ticks); - - // move to constructor end - if (!reader.Read()) - throw new JsonSerializationException("Unexpected end while creating date."); - if (reader.TokenType != JsonToken.EndConstructor) - throw new JsonSerializationException("Unexpected token while creating date: " + reader.TokenType); - - return date; - } - private object EnsureType(object value, Type targetType) { // do something about null value when the targetType is a valuetype? @@ -329,32 +361,33 @@ namespace Newtonsoft.Json MemberMapping memberMapping = memberMappings[memberName]; if (memberMapping.Ignored) + { + reader.Skip(); return; - - // ignore member if it is readonly - if (!memberMapping.Writable) - return; + } // get the member's underlying type memberType = ReflectionUtils.GetMemberUnderlyingType(memberMapping.Member); + object currentValue = ReflectionUtils.GetMemberValue(memberMapping.Member, target); - value = GetObject(reader, memberType); + bool useExistingValue = (currentValue != null + && (_objectCreationHandling == ObjectCreationHandling.Auto || _objectCreationHandling == ObjectCreationHandling.Reuse) + && (reader.TokenType == JsonToken.StartArray || reader.TokenType == JsonToken.StartObject) + && (!memberType.IsArray && !ReflectionUtils.IsSubClass(memberType, typeof(ReadOnlyCollection<>)))); + + if (!memberMapping.Writable && !useExistingValue) + { + reader.Skip(); + return; + } + + value = CreateObject(reader, memberType, (useExistingValue) ? currentValue : null); if (_nullValueHandling == NullValueHandling.Ignore && value == null) return; - ReflectionUtils.SetMemberValue(memberMapping.Member, target, value); - } - else if (typeof(IDictionary).IsAssignableFrom(targetType)) - { - // attempt to get the IDictionary's type - memberType = ReflectionUtils.GetDictionaryValueType(targetType); - value = GetObject(reader, memberType); - - Type keyType = ReflectionUtils.GetDictionaryKeyType(targetType); - object keyValue = EnsureType(memberName, keyType); - - ((IDictionary)target).Add(keyValue, value); + if (!useExistingValue) + ReflectionUtils.SetMemberValue(memberMapping.Member, target, value); } else { @@ -363,35 +396,67 @@ namespace Newtonsoft.Json } } - private object PopulateList(JsonReader reader, Type objectType) + private object CreateAndPopulateDictionary(JsonReader reader, Type objectType) { - Type elementType = ReflectionUtils.GetListItemType(objectType); + IWrappedDictionary dictionary = CollectionUtils.CreateDictionaryWrapper(Activator.CreateInstance(objectType)); + PopulateDictionary(dictionary, reader); - IList populatedList = CollectionUtils.CreateAndPopulateList(objectType, delegate(IList list) - { - while (reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.EndArray: - return; - case JsonToken.Comment: - break; - default: - object value = GetObject(reader, elementType); - - list.Add(value); - break; - } - } - - throw new JsonSerializationException("Unexpected end when deserializing array."); - }); - - return populatedList; + return dictionary.UnderlyingDictionary; } - private object PopulateObject(JsonReader reader, Type objectType) + private IDictionary PopulateDictionary(IWrappedDictionary dictionary, JsonReader reader) + { + Type dictionaryType = dictionary.UnderlyingDictionary.GetType(); + Type dictionaryKeyType = ReflectionUtils.GetDictionaryKeyType(dictionaryType); + Type dictionaryValueType = ReflectionUtils.GetDictionaryValueType(dictionaryType); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + object keyValue = EnsureType(reader.Value, dictionaryKeyType); + reader.Read(); + + dictionary.Add(keyValue, CreateObject(reader, dictionaryValueType, null)); + break; + case JsonToken.EndObject: + return dictionary; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } + + throw new JsonSerializationException("Unexpected end when deserializing object."); + } + + private object CreateAndPopulateList(JsonReader reader, Type objectType) + { + return CollectionUtils.CreateAndPopulateList(objectType, l => PopulateList(l, ReflectionUtils.GetListItemType(objectType), reader)); + } + + private IList PopulateList(IList list, Type listItemType, JsonReader reader) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.EndArray: + return list; + case JsonToken.Comment: + break; + default: + object value = CreateObject(reader, listItemType, null); + + list.Add(value); + break; + } + } + + throw new JsonSerializationException("Unexpected end when deserializing array."); + } + + private object CreateAndPopulateObject(JsonReader reader, Type objectType) { object newObject; @@ -399,23 +464,8 @@ namespace Newtonsoft.Json { newObject = Activator.CreateInstance(objectType); - while (reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - string memberName = reader.Value.ToString(); - - SetObjectMember(reader, newObject, objectType, memberName); - break; - case JsonToken.EndObject: - return newObject; - default: - throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); - } - } - - throw new JsonSerializationException("Unexpected end when deserializing object."); + PopulateObject(newObject, reader, objectType); + return newObject; } else { @@ -442,15 +492,9 @@ namespace Newtonsoft.Json if (!reader.Read()) throw new JsonSerializationException(string.Format("Unexpected end when setting {0}'s value.", memberName)); - if (matchingConstructorParameter != null) - { - constructorParameters[matchingConstructorParameter] = GetObject(reader, matchingConstructorParameter.ParameterType); - } + constructorParameters[matchingConstructorParameter] = CreateObject(reader, matchingConstructorParameter.ParameterType, null); - - - //SetObjectMember(reader, newObject, objectType, memberName); break; case JsonToken.EndObject: exit = true; @@ -465,6 +509,27 @@ namespace Newtonsoft.Json return newObject; } } + + private object PopulateObject(object newObject, JsonReader reader, Type objectType) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string memberName = reader.Value.ToString(); + + SetObjectMember(reader, newObject, objectType, memberName); + break; + case JsonToken.EndObject: + return newObject; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } + + throw new JsonSerializationException("Unexpected end when deserializing object."); + } #endregion #region Serialize @@ -560,6 +625,9 @@ namespace Newtonsoft.Json case TypeCode.Decimal: writer.WriteValue((decimal)convertible); break; + case TypeCode.DBNull: + writer.WriteNull(); + break; default: SerializeObject(writer, value); break; @@ -691,10 +759,7 @@ namespace Newtonsoft.Json private void SerializeCollection(JsonWriter writer, ICollection values) { - object[] collectionValues = new object[values.Count]; - values.CopyTo(collectionValues, 0); - - SerializeList(writer, collectionValues); + SerializeList(writer, values.Cast().ToList()); } private void SerializeList(JsonWriter writer, IList values) diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index d42afe9d..b9ece9e1 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -789,7 +789,7 @@ namespace Newtonsoft.Json /// /// Changes the to Closed. /// - public void Close() + public override void Close() { base.Close(); diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index 69e3f282..4651fea5 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -295,7 +295,7 @@ namespace Newtonsoft.Json ValidationUtils.ArgumentNotNull(reader, "reader"); int currentDepth = (reader.TokenType == JsonToken.None) ? -1 : reader.Depth; - bool continue1; + do { switch (reader.TokenType) diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 5ad7487f..0078628a 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -73,6 +73,7 @@ + @@ -109,6 +110,7 @@ + diff --git a/Src/Newtonsoft.Json/ObjectCreationHandling.cs b/Src/Newtonsoft.Json/ObjectCreationHandling.cs new file mode 100644 index 00000000..ec5048e2 --- /dev/null +++ b/Src/Newtonsoft.Json/ObjectCreationHandling.cs @@ -0,0 +1,39 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Newtonsoft.Json +{ + public enum ObjectCreationHandling + { + Auto = 0, + Reuse = 1, + Replace = 2 + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 8c1d4a3a..79f1845f 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -191,7 +191,7 @@ namespace Newtonsoft.Json.Utilities /// /// The list to add to. /// The collection of elements to add. - public static void AddRange(IList initial, IEnumerable collection) + public static void AddRange(this IList initial, IEnumerable collection) { if (initial == null) throw new ArgumentNullException("initial"); @@ -205,6 +205,14 @@ namespace Newtonsoft.Json.Utilities } } + public static void AddRange(this IList initial, IEnumerable collection) + { + ValidationUtils.ArgumentNotNull(initial, "initial"); + + ListWrapper wrapper = new ListWrapper(initial); + wrapper.AddRange(collection.Cast()); + } + public static List Distinct(List collection) { List distinctList = new List(); @@ -336,11 +344,11 @@ namespace Newtonsoft.Json.Utilities return tempList.ToArray(); } - public static object CreateGenericList(Type listType) + public static IList CreateGenericList(Type listType) { ValidationUtils.ArgumentNotNull(listType, "listType"); - return ReflectionUtils.CreateGeneric(typeof(List<>), listType); + return (IList)ReflectionUtils.CreateGeneric(typeof(List<>), listType); } public static bool IsListType(Type type) @@ -357,13 +365,70 @@ namespace Newtonsoft.Json.Utilities return false; } + public static IWrappedList CreateListWrapper(object list) + { + ValidationUtils.ArgumentNotNull(list, "list"); + + Type listDefinition; + if (ReflectionUtils.IsSubClass(list.GetType(), typeof(IList<>), out listDefinition)) + { + Type listItemType = ReflectionUtils.GetListItemType(listDefinition); + + // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor + Func, object> instanceCreator = (t, a) => + { + ConstructorInfo c = t.GetConstructor(new[] {listDefinition}); + return c.Invoke(new[] { list }); + }; + + return (IWrappedList)ReflectionUtils.CreateGeneric(typeof(ListWrapper<>), new[] { listItemType }, instanceCreator, list); + } + else if (list is IList) + { + return new ListWrapper((IList)list); + } + else + { + throw new Exception(string.Format("Can not create ListWrapper for type {0}.", list.GetType())); + } + } + + public static IWrappedDictionary CreateDictionaryWrapper(object dictionary) + { + ValidationUtils.ArgumentNotNull(dictionary, "dictionary"); + + Type dictionaryDefinition; + if (ReflectionUtils.IsSubClass(dictionary.GetType(), typeof(IDictionary<,>), out dictionaryDefinition)) + { + Type dictionaryKeyType = ReflectionUtils.GetDictionaryKeyType(dictionaryDefinition); + Type dictionaryValueType = ReflectionUtils.GetDictionaryValueType(dictionaryDefinition); + + // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor + Func, object> instanceCreator = (t, a) => + { + ConstructorInfo c = t.GetConstructor(new[] { dictionaryDefinition }); + return c.Invoke(new[] { dictionary }); + }; + + return (IWrappedDictionary)ReflectionUtils.CreateGeneric(typeof(DictionaryWrapper<,>), new[] { dictionaryKeyType, dictionaryValueType }, instanceCreator, dictionary); + } + else if (dictionary is IDictionary) + { + return new DictionaryWrapper((IDictionary)dictionary); + } + else + { + throw new Exception(string.Format("Can not create DictionaryWrapper for type {0}.", dictionary.GetType())); + } + } + public static IList CreateAndPopulateList(Type listType, Action populateList) { ValidationUtils.ArgumentNotNull(listType, "listType"); ValidationUtils.ArgumentNotNull(populateList, "populateList"); IList list; - Type readOnlyCollectionType; + Type collectionType; bool isReadOnlyOrFixedSize = false; if (listType.IsArray) @@ -373,9 +438,9 @@ namespace Newtonsoft.Json.Utilities list = new ArrayList(); isReadOnlyOrFixedSize = true; } - else if (ReflectionUtils.IsSubClass(listType, typeof(ReadOnlyCollection<>), out readOnlyCollectionType)) + else if (ReflectionUtils.IsSubClass(listType, typeof(ReadOnlyCollection<>), out collectionType)) { - Type readOnlyCollectionContentsType = readOnlyCollectionType.GetGenericArguments()[0]; + Type readOnlyCollectionContentsType = collectionType.GetGenericArguments()[0]; Type genericEnumerable = ReflectionUtils.MakeGenericType(typeof(IEnumerable<>), readOnlyCollectionContentsType); bool suitableConstructor = false; @@ -394,22 +459,34 @@ namespace Newtonsoft.Json.Utilities } if (!suitableConstructor) - throw new Exception(string.Format("Readonly type {0} does not have a public constructor that takes a type that implements {1}.", listType, genericEnumerable)); + throw new Exception(string.Format("Read-only type {0} does not have a public constructor that takes a type that implements {1}.", listType, genericEnumerable)); // can't add or modify a readonly list // use List and convert once populated list = (IList)CreateGenericList(readOnlyCollectionContentsType); isReadOnlyOrFixedSize = true; } - else if (typeof(IList).IsAssignableFrom(listType) && ReflectionUtils.IsInstantiatableType(listType)) + else if (typeof(IList).IsAssignableFrom(listType)) { - list = (IList)Activator.CreateInstance(listType); + if (ReflectionUtils.IsInstantiatableType(listType)) + list = (IList)Activator.CreateInstance(listType); + else if (listType == typeof(IList)) + list = new List(); + else + list = null; + } + else if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(IList<>)) + { + list = CollectionUtils.CreateGenericList(ReflectionUtils.GetListItemType(listType)); } else { - throw new Exception(string.Format("Cannot create and populate list type {0}.", listType)); + list = null; } + if (list == null) + throw new Exception(string.Format("Cannot create and populate list type {0}.", listType)); + populateList(list); // create readonly and fixed sized collections using the temporary list diff --git a/Src/Newtonsoft.Json/Utilities/DictionaryWrapper.cs b/Src/Newtonsoft.Json/Utilities/DictionaryWrapper.cs new file mode 100644 index 00000000..d2db5637 --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/DictionaryWrapper.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using System.Threading; + +namespace Newtonsoft.Json.Utilities +{ + public interface IWrappedDictionary : IDictionary + { + object UnderlyingDictionary { get; } + } + + public class DictionaryWrapper : IDictionary, IWrappedDictionary + { + private readonly IDictionary _dictionary; + private readonly IDictionary _genericDictionary; + private object _syncRoot; + + public DictionaryWrapper(IDictionary dictionary) + { + ValidationUtils.ArgumentNotNull(dictionary, "dictionary"); + + _dictionary = dictionary; + } + + public DictionaryWrapper(IDictionary dictionary) + { + ValidationUtils.ArgumentNotNull(dictionary, "dictionary"); + + _genericDictionary = dictionary; + } + + public void Add(TKey key, TValue value) + { + if (_genericDictionary != null) + _genericDictionary.Add(key, value); + else + _dictionary.Add(key, value); + } + + public bool ContainsKey(TKey key) + { + if (_genericDictionary != null) + return _genericDictionary.ContainsKey(key); + else + return _dictionary.Contains(key); + } + + public ICollection Keys + { + get + { + if (_genericDictionary != null) + return _genericDictionary.Keys; + else + return _dictionary.Keys.Cast().ToList(); + } + } + + public bool Remove(TKey key) + { + if (_genericDictionary != null) + { + return _genericDictionary.Remove(key); + } + else + { + if (_dictionary.Contains(key)) + { + _dictionary.Remove(key); + return true; + } + else + { + return false; + } + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (_genericDictionary != null) + { + return _genericDictionary.TryGetValue(key, out value); + } + else + { + if (!_dictionary.Contains(key)) + { + value = default(TValue); + return false; + } + else + { + value = (TValue)_dictionary[key]; + return true; + } + } + } + + public ICollection Values + { + get + { + if (_genericDictionary != null) + return _genericDictionary.Values; + else + return _dictionary.Values.Cast().ToList(); + } + } + + public TValue this[TKey key] + { + get + { + if (_genericDictionary != null) + return _genericDictionary[key]; + else + return (TValue)_dictionary[key]; + } + set + { + if (_genericDictionary != null) + _genericDictionary[key] = value; + else + _dictionary[key] = value; + } + } + + public void Add(KeyValuePair item) + { + if (_genericDictionary != null) + _genericDictionary.Add(item); + else + ((IList)_dictionary).Add(item); + } + + public void Clear() + { + if (_genericDictionary != null) + _genericDictionary.Clear(); + else + _dictionary.Clear(); + } + + public bool Contains(KeyValuePair item) + { + if (_genericDictionary != null) + return _genericDictionary.Contains(item); + else + return ((IList)_dictionary).Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (_genericDictionary != null) + { + _genericDictionary.CopyTo(array, arrayIndex); + } + else + { + foreach (DictionaryEntry item in _dictionary) + { + array[arrayIndex++] = new KeyValuePair((TKey)item.Key, (TValue)item.Value); + } + } + } + + public int Count + { + get + { + if (_genericDictionary != null) + return _genericDictionary.Count; + else + return _dictionary.Count; + } + } + + public bool IsReadOnly + { + get + { + if (_genericDictionary != null) + return _genericDictionary.IsReadOnly; + else + return _dictionary.IsReadOnly; + } + } + + public bool Remove(KeyValuePair item) + { + if (_genericDictionary != null) + { + return _genericDictionary.Remove(item); + } + else + { + if (_dictionary.Contains(item.Key)) + { + object value = _dictionary[item.Key]; + + if (object.Equals(value, item.Value)) + { + _dictionary.Remove(item.Key); + return true; + } + else + { + return false; + } + } + else + { + return true; + } + } + } + + public IEnumerator> GetEnumerator() + { + if (_genericDictionary != null) + return _genericDictionary.GetEnumerator(); + else + return _dictionary.Cast().Select(de => new KeyValuePair((TKey)de.Key, (TValue)de.Value)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void IDictionary.Add(object key, object value) + { + if (_genericDictionary != null) + _genericDictionary.Add((TKey)key, (TValue)value); + else + _dictionary.Add(key, value); + } + + bool IDictionary.Contains(object key) + { + if (_genericDictionary != null) + return _genericDictionary.ContainsKey((TKey)key); + else + return _dictionary.Contains(key); + } + + private struct DictionaryEnumerator : IDictionaryEnumerator + { + private IEnumerator _e; + + public DictionaryEnumerator(IEnumerator e) + { + ValidationUtils.ArgumentNotNull(e, "e"); + _e = e; + } + + public DictionaryEntry Entry + { + get { return (DictionaryEntry)Current; } + } + + public object Key + { + get { return Entry.Key; } + } + + public object Value + { + get { return Entry.Value; } + } + + public object Current + { + get { return _e.Current; } + } + + public bool MoveNext() + { + return _e.MoveNext(); + } + + public void Reset() + { + _e.Reset(); + } + } + + IDictionaryEnumerator IDictionary.GetEnumerator() + { + if (_genericDictionary != null) + return new DictionaryEnumerator(_genericDictionary.GetEnumerator()); + else + return _dictionary.GetEnumerator(); + } + + bool IDictionary.IsFixedSize + { + get + { + if (_genericDictionary != null) + return false; + else + return _dictionary.IsFixedSize; + } + } + + ICollection IDictionary.Keys + { + get + { + if (_genericDictionary != null) + return _genericDictionary.Keys.ToList(); + else + return _dictionary.Keys; + } + } + + public void Remove(object key) + { + if (_genericDictionary != null) + _genericDictionary.Remove((TKey)key); + else + _dictionary.Remove(key); + } + + ICollection IDictionary.Values + { + get + { + if (_genericDictionary != null) + return _genericDictionary.Values.ToList(); + else + return _dictionary.Values; + } + } + + object IDictionary.this[object key] + { + get + { + if (_genericDictionary != null) + return _genericDictionary[(TKey)key]; + else + return _dictionary[key]; + } + set + { + if (_genericDictionary != null) + _genericDictionary[(TKey)key] = (TValue)value; + else + _dictionary[key] = value; + } + } + + void ICollection.CopyTo(Array array, int index) + { + if (_genericDictionary != null) + _genericDictionary.CopyTo((KeyValuePair[])array, index); + else + _dictionary.CopyTo(array, index); + } + + bool ICollection.IsSynchronized + { + get + { + if (_genericDictionary != null) + return false; + else + return _dictionary.IsSynchronized; + } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + Interlocked.CompareExchange(ref _syncRoot, new object(), null); + + return _syncRoot; + } + } + + public object UnderlyingDictionary + { + get + { + if (_genericDictionary != null) + return _genericDictionary; + else + return _dictionary; + } + } + } +} diff --git a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs index f4fbdc37..5ead88ff 100644 --- a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs @@ -7,11 +7,7 @@ using System.Text; namespace Newtonsoft.Json.Utilities { - internal delegate void Action(); internal delegate T Creator(); - internal delegate T Func(A0 arg0); - internal delegate T Func(A0 arg0, A1 arg1); - internal delegate T Func(A0 arg0, A1 arg1, A2 arg2); internal static class MiscellaneousUtils { diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 79a21707..28b4d847 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -29,6 +29,7 @@ using System.Text; using System.Reflection; using System.Collections; using System.ComponentModel; +using System.Linq; namespace Newtonsoft.Json.Utilities { @@ -536,13 +537,19 @@ namespace Newtonsoft.Json.Utilities } public static object CreateGeneric(Type genericTypeDefinition, IList innerTypes, params object[] args) + { + return CreateGeneric(genericTypeDefinition, innerTypes, (t, a) => Activator.CreateInstance(t, a.ToArray()), args); + } + + public static object CreateGeneric(Type genericTypeDefinition, IList innerTypes, Func, object> instanceCreator, params object[] args) { ValidationUtils.ArgumentNotNull(genericTypeDefinition, "genericTypeDefinition"); ValidationUtils.ArgumentNotNullOrEmpty(innerTypes, "innerTypes"); + ValidationUtils.ArgumentNotNull(instanceCreator, "createInstance"); Type specificType = MakeGenericType(genericTypeDefinition, CollectionUtils.CreateArray(innerTypes)); - return Activator.CreateInstance(specificType, args); + return instanceCreator(specificType, args); } } } \ No newline at end of file