diff --git a/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs new file mode 100644 index 00000000..6346a85b --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Converters/DiscriminatedUnionConverterTests.cs @@ -0,0 +1,131 @@ +#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 + +#if !(NET35 || NET20 || NETFX_CORE) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Tests.TestObjects; +using NUnit.Framework; + +namespace Newtonsoft.Json.Tests.Converters +{ + [TestFixture] + public class DiscriminatedUnionConverterTests : TestFixtureBase + { + [Test] + public void SerializeBasicUnion() + { + string json = JsonConvert.SerializeObject(Currency.AUD); + + Assert.AreEqual(@"{""Case"":""AUD"",""Fields"":[]}", json); + } + + [Test] + public void SerializeUnionWithFields() + { + string json = JsonConvert.SerializeObject(Shape.NewRectangle(10.0, 5.0)); + + Assert.AreEqual(@"{""Case"":""Rectangle"",""Fields"":[10.0,5.0]}", json); + } + + [Test] + public void DeserializeBasicUnion() + { + Currency c = JsonConvert.DeserializeObject(@"{""Case"":""AUD"",""Fields"":[]}"); + Assert.AreEqual(Currency.AUD, c); + + c = JsonConvert.DeserializeObject(@"{""Fields"":[],""Case"":""EUR""}"); + Assert.AreEqual(Currency.EUR, c); + + c = JsonConvert.DeserializeObject(@"null"); + Assert.AreEqual(null, c); + } + + [Test] + public void DeserializeUnionWithFields() + { + Shape c = JsonConvert.DeserializeObject(@"{""Case"":""Rectangle"",""Fields"":[10.0,5.0]}"); + Assert.AreEqual(true, c.IsRectangle); + + Shape.Rectangle r = (Shape.Rectangle)c; + + Assert.AreEqual(5.0, r.length); + Assert.AreEqual(10.0, r.width); + } + + [Test] + public void DeserializeBasicUnion_NoMatch() + { + ExceptionAssert.Throws("No union type found with the name 'abcdefg'. Path 'Case', line 1, position 17.", + () => JsonConvert.DeserializeObject(@"{""Case"":""abcdefg"",""Fields"":[]}")); + } + + [Test] + public void DeserializeBasicUnion_MismatchedFieldCount() + { + ExceptionAssert.Throws("The number of field values does not match the number of properties definied by union 'AUD'. Path '', line 1, position 27.", + () => JsonConvert.DeserializeObject(@"{""Case"":""AUD"",""Fields"":[1]}")); + } + + [Test] + public void DeserializeBasicUnion_NoCaseName() + { + ExceptionAssert.Throws("No 'Case' property with union name found. Path '', line 1, position 14.", + () => JsonConvert.DeserializeObject(@"{""Fields"":[1]}")); + } + + [Test] + public void DeserializeBasicUnion_NoFields() + { + ExceptionAssert.Throws("No 'Fields' property with union fields found. Path '', line 1, position 14.", + () => JsonConvert.DeserializeObject(@"{""Case"":""AUD""}")); + } + + [Test] + public void DeserializeBasicUnion_UnexpectedEnd() + { + ExceptionAssert.Throws("Unexpected end when reading union. Path 'Case', line 1, position 8.", + () => JsonConvert.DeserializeObject(@"{""Case"":")); + } + + [Test] + public void DeserializeBasicUnion_FieldsObject() + { + ExceptionAssert.Throws("Union fields must been an array. Path 'Fields', line 1, position 24.", + () => JsonConvert.DeserializeObject(@"{""Case"":""AUD"",""Fields"":{}}")); + } + + [Test] + public void DeserializeBasicUnion_UnexpectedProperty() + { + ExceptionAssert.Throws("Unexpected property 'Case123' found when reading union. Path 'Case123', line 1, position 11.", + () => JsonConvert.DeserializeObject(@"{""Case123"":""AUD""}")); + } + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Converters/KeyValuePairConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/KeyValuePairConverterTests.cs index 337beef3..a5233a63 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/KeyValuePairConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/KeyValuePairConverterTests.cs @@ -34,7 +34,31 @@ namespace Newtonsoft.Json.Tests.Converters string json = JsonConvert.SerializeObject(values, Formatting.Indented); - Console.WriteLine(json); + Assert.AreEqual(@"[ + { + ""Key"": ""123"", + ""Value"": 123 + }, + { + ""Key"": ""456"", + ""Value"": 456 + } +]", json); + + IList> v2 = JsonConvert.DeserializeObject>>(json); + + Assert.AreEqual(2, v2.Count); + Assert.AreEqual("123", v2[0].Key); + Assert.AreEqual(123, v2[0].Value); + Assert.AreEqual("456", v2[1].Key); + Assert.AreEqual(456, v2[1].Value); + } + + [Test] + public void DeserializeUnexpectedEnd() + { + ExceptionAssert.Throws("Unexpected end when reading KeyValuePair. Path 'Key', line 1, position 14.", + () => JsonConvert.DeserializeObject>(@"{""Key"": ""123"",")); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 773e0b6a..e22624f8 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -68,6 +68,7 @@ + @@ -292,6 +293,7 @@ + Component @@ -383,6 +385,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 439da49a..113eaab3 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.4.17703")] +[assembly: AssemblyFileVersion("6.0.4.17705")] diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/Currency.cs b/Src/Newtonsoft.Json.Tests/TestObjects/Currency.cs new file mode 100644 index 00000000..fc856d2e --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestObjects/Currency.cs @@ -0,0 +1,173 @@ +#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 + +#if !(NET35 || NET20 || NETFX_CORE) +using Microsoft.FSharp.Core; +using System; +using System.Collections; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Newtonsoft.Json.Tests.TestObjects +{ + [Serializable, DebuggerDisplay("{__DebugDisplay(),nq}"), CompilationMapping(SourceConstructFlags.SumType)] + public class Currency + { + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly int _tag; + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_AUD = new Currency(1); + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_EUR = new Currency(4); + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_JPY = new Currency(5); + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_LocalCurrency = new Currency(0); + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_NZD = new Currency(2); + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Currency _unique_USD = new Currency(3); + + [CompilerGenerated, DebuggerNonUserCode] + internal Currency(int _tag) + { + this._tag = _tag; + } + + [CompilerGenerated, DebuggerNonUserCode] + internal object __DebugDisplay() + { + return ExtraTopLevelOperators.PrintFormatToString>(new PrintfFormat, Unit, string, string, string>("%+0.8A")).Invoke(this); + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency AUD + { + [CompilationMapping(SourceConstructFlags.UnionCase, 1)] + get { return _unique_AUD; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency EUR + { + [CompilationMapping(SourceConstructFlags.UnionCase, 4)] + get { return _unique_EUR; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsAUD + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 1); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsEUR + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 4); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsJPY + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 5); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsLocalCurrency + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 0); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsNZD + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 2); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsUSD + { + [CompilerGenerated, DebuggerNonUserCode] + get { return (this.Tag == 3); } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency JPY + { + [CompilationMapping(SourceConstructFlags.UnionCase, 5)] + get { return _unique_JPY; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency LocalCurrency + { + [CompilationMapping(SourceConstructFlags.UnionCase, 0)] + get { return _unique_LocalCurrency; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency NZD + { + [CompilationMapping(SourceConstructFlags.UnionCase, 2)] + get { return _unique_NZD; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public int Tag + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._tag; } + } + + [CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Currency USD + { + [CompilationMapping(SourceConstructFlags.UnionCase, 3)] + get { return _unique_USD; } + } + + public static class Tags + { + public const int AUD = 1; + public const int EUR = 4; + public const int JPY = 5; + public const int LocalCurrency = 0; + public const int NZD = 2; + public const int USD = 3; + } + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/Shape.cs b/Src/Newtonsoft.Json.Tests/TestObjects/Shape.cs new file mode 100644 index 00000000..2bc393cd --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestObjects/Shape.cs @@ -0,0 +1,227 @@ +#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 + +#if !(NET35 || NET20 || NETFX_CORE) +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.FSharp.Core; + +namespace Newtonsoft.Json.Tests.TestObjects +{ + [Serializable, DebuggerDisplay("{__DebugDisplay(),nq}"), CompilationMapping(SourceConstructFlags.SumType)] + public class Shape + { + // Fields + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly int _tag; + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal static readonly Shape _unique_Empty; + + static Shape() + { + _unique_Empty = new Shape(3); + } + + [CompilerGenerated] + internal Shape(int _tag) + { + this._tag = _tag; + } + + [CompilationMapping(SourceConstructFlags.UnionCase, 1)] + public static Shape NewCircle(double _radius) + { + return new Circle(_radius); + } + + [CompilationMapping(SourceConstructFlags.UnionCase, 2)] + public static Shape NewPrism(double _width, double item2, double _height) + { + return new Prism(_width, item2, _height); + } + + [CompilationMapping(SourceConstructFlags.UnionCase, 0)] + public static Shape NewRectangle(double _width, double _length) + { + return new Rectangle(_width, _length); + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public static Shape Empty + { + [CompilationMapping(SourceConstructFlags.UnionCase, 3)] + get { return _unique_Empty; } + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsCircle + { + [CompilerGenerated] + get { return (this.Tag == 1); } + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsEmpty + { + [CompilerGenerated] + get { return (this.Tag == 3); } + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsPrism + { + [CompilerGenerated] + get { return (this.Tag == 2); } + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public bool IsRectangle + { + [CompilerGenerated] + get { return (this.Tag == 0); } + } + + [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] + public int Tag + { + [CompilerGenerated] + get { return this._tag; } + } + + [Serializable, DebuggerDisplay("{__DebugDisplay(),nq}")] + public class Circle : Shape + { + // Fields + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double _radius; + + // Methods + [CompilerGenerated, DebuggerNonUserCode] + internal Circle(double _radius) : base(1) + { + this._radius = _radius; + } + + // Properties + [CompilationMapping(SourceConstructFlags.Field, 1, 0), CompilerGenerated, DebuggerNonUserCode] + public double radius + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._radius; } + } + } + + + [Serializable, DebuggerDisplay("{__DebugDisplay(),nq}")] + public class Prism : Shape + { + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double _height; + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double _width; + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double item2; + + [CompilerGenerated, DebuggerNonUserCode] + internal Prism(double _width, double item2, double _height) : base(2) + { + this._width = _width; + this.item2 = item2; + this._height = _height; + } + + [CompilationMapping(SourceConstructFlags.Field, 2, 2), CompilerGenerated, DebuggerNonUserCode] + public double height + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._height; } + } + + [CompilationMapping(SourceConstructFlags.Field, 2, 1), CompilerGenerated, DebuggerNonUserCode] + public double Item2 + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this.item2; } + } + + [CompilationMapping(SourceConstructFlags.Field, 2, 0), CompilerGenerated, DebuggerNonUserCode] + public double width + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._width; } + } + } + + [Serializable, DebuggerDisplay("{__DebugDisplay(),nq}")] + public class Rectangle : Shape + { + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double _length; + + [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated] + internal readonly double _width; + + [CompilerGenerated, DebuggerNonUserCode] + internal Rectangle(double _width, double _length) : base(0) + { + this._width = _width; + this._length = _length; + } + + [CompilationMapping(SourceConstructFlags.Field, 0, 1), CompilerGenerated, DebuggerNonUserCode] + public double length + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._length; } + } + + [CompilationMapping(SourceConstructFlags.Field, 0, 0), CompilerGenerated, DebuggerNonUserCode] + public double width + { + [CompilerGenerated, DebuggerNonUserCode] + get { return this._width; } + } + } + + public static class Tags + { + // Fields + public const int Circle = 1; + public const int Empty = 3; + public const int Prism = 2; + public const int Rectangle = 0; + } + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs index 91b304dd..3ab1469c 100644 --- a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs +++ b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs @@ -24,6 +24,7 @@ #endregion #if !(NET35 || NET20 || NETFX_CORE) +using Newtonsoft.Json.Linq; using System; using System.Collections; using System.Collections.Generic; @@ -86,44 +87,72 @@ namespace Newtonsoft.Json.Converters if (reader.TokenType == JsonToken.Null) return null; - IEnumerable cases = (IEnumerable)FSharpUtils.GetUnionCases(null, objectType, null); - - ReadAndAssertProperty(reader, CasePropertyName); - ReadAndAssert(reader); - - string caseName = reader.Value.ToString(); - object matchingCaseInfo = null; - foreach (object c in cases) + string caseName = null; + JArray fields = null; + + // start object + ReadAndAssert(reader); + + while (reader.TokenType == JsonToken.PropertyName) { - if ((string)FSharpUtils.GetUnionCaseInfoName(c) == caseName) + string propertyName = reader.Value.ToString(); + if (string.Equals(propertyName, CasePropertyName, StringComparison.OrdinalIgnoreCase)) { - matchingCaseInfo = c; - break; + ReadAndAssert(reader); + + IEnumerable cases = (IEnumerable)FSharpUtils.GetUnionCases(null, objectType, null); + + caseName = reader.Value.ToString(); + + foreach (object c in cases) + { + if ((string)FSharpUtils.GetUnionCaseInfoName(c) == caseName) + { + matchingCaseInfo = c; + break; + } + } + + if (matchingCaseInfo == null) + throw JsonSerializationException.Create(reader, "No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); } - } + else if (string.Equals(propertyName, FieldsPropertyName, StringComparison.OrdinalIgnoreCase)) + { + ReadAndAssert(reader); + if (reader.TokenType != JsonToken.StartArray) + throw JsonSerializationException.Create(reader, "Union fields must been an array."); - if (matchingCaseInfo == null) - throw new JsonSerializationException("No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); + fields = (JArray)JToken.ReadFrom(reader); + } + else + { + throw JsonSerializationException.Create(reader, "Unexpected property '{0}' found when reading union.".FormatWith(CultureInfo.InvariantCulture, propertyName)); + } - ReadAndAssertProperty(reader, FieldsPropertyName); - // start array - ReadAndAssert(reader); - // first value - ReadAndAssert(reader); - - PropertyInfo[] fieldProperties = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(matchingCaseInfo); - List fieldValues = new List(); - foreach (PropertyInfo field in fieldProperties) - { - fieldValues.Add(serializer.Deserialize(reader, field.PropertyType)); ReadAndAssert(reader); } - // end object - ReadAndAssert(reader); + if (matchingCaseInfo == null) + throw JsonSerializationException.Create(reader, "No '{0}' property with union name found.".FormatWith(CultureInfo.InvariantCulture, CasePropertyName)); + if (fields == null) + throw JsonSerializationException.Create(reader, "No '{0}' property with union fields found.".FormatWith(CultureInfo.InvariantCulture, FieldsPropertyName)); - return FSharpUtils.MakeUnion(null, matchingCaseInfo, fieldValues.ToArray(), null); + PropertyInfo[] fieldProperties = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(matchingCaseInfo); + + if (fieldProperties.Length != fields.Count) + throw JsonSerializationException.Create(reader, "The number of field values does not match the number of properties definied by union '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); + + object[] typedFieldValues = new object[fieldProperties.Length]; + for (int i = 0; i < fields.Count; i++) + { + JToken t = fields[i]; + PropertyInfo fieldProperty = fieldProperties[i]; + + typedFieldValues[i] = t.ToObject(fieldProperty.PropertyType); + } + + return FSharpUtils.MakeUnion(null, matchingCaseInfo, typedFieldValues, null); } /// @@ -166,18 +195,10 @@ namespace Newtonsoft.Json.Converters return (bool)FSharpUtils.IsUnion(null, objectType, null); } - private static void ReadAndAssertProperty(JsonReader reader, string propertyName) - { - ReadAndAssert(reader); - - if (reader.TokenType != JsonToken.PropertyName || !string.Equals(reader.Value.ToString(), propertyName, StringComparison.OrdinalIgnoreCase)) - throw new JsonSerializationException("Expected JSON property '{0}'.".FormatWith(CultureInfo.InvariantCulture, propertyName)); - } - private static void ReadAndAssert(JsonReader reader) { if (!reader.Read()) - throw new JsonSerializationException("Unexpected end."); + throw JsonSerializationException.Create(reader, "Unexpected end when reading union."); } } } diff --git a/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs b/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs index 4120d1d7..e5b219f0 100644 --- a/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs +++ b/Src/Newtonsoft.Json/Converters/KeyValuePairConverter.cs @@ -99,19 +99,19 @@ namespace Newtonsoft.Json.Converters object key = null; object value = null; - reader.Read(); + ReadAndAssert(reader); while (reader.TokenType == JsonToken.PropertyName) { string propertyName = reader.Value.ToString(); if (string.Equals(propertyName, KeyName, StringComparison.OrdinalIgnoreCase)) { - reader.Read(); + ReadAndAssert(reader); key = serializer.Deserialize(reader, reflectionObject.GetType(KeyName)); } else if (string.Equals(propertyName, ValueName, StringComparison.OrdinalIgnoreCase)) { - reader.Read(); + ReadAndAssert(reader); value = serializer.Deserialize(reader, reflectionObject.GetType(ValueName)); } else @@ -119,7 +119,7 @@ namespace Newtonsoft.Json.Converters reader.Skip(); } - reader.Read(); + ReadAndAssert(reader); } return reflectionObject.Creator(key, value); @@ -143,5 +143,11 @@ namespace Newtonsoft.Json.Converters return false; } + + private static void ReadAndAssert(JsonReader reader) + { + if (!reader.Read()) + throw JsonSerializationException.Create(reader, "Unexpected end when reading KeyValuePair."); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 1c7dca5f..c78f33c5 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.4.17703")] +[assembly: AssemblyFileVersion("6.0.4.17705")] [assembly: CLSCompliant(true)]