diff --git a/Doc/JsonSchema.aml b/Doc/JsonSchema.aml index feb06ea5..cf04b466 100644 --- a/Doc/JsonSchema.aml +++ b/Doc/JsonSchema.aml @@ -13,6 +13,22 @@ _blank + + + + The JSON Schema standard isn't final. + + Json.NET implements + + JSON Schema Draft 3 + http://tools.ietf.org/search/draft-zyp-json-schema-03/ + _blank + . + + There will be breaking changes to some parts of Json.NET's JSON Schema API when it is updated to implement newer drafts of JSON Schema. + + +
diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 4d70475c..818b4407 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -119,6 +119,28 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath () => { o.SelectToken("[1]", true); }); } + [Test] + public void EvaluateWildcardIndexOnObjectWithError() + { + JObject o = new JObject( + new JProperty("Blah", 1)); + + ExceptionAssert.Throws( + @"Index * not valid on JObject.", + () => { o.SelectToken("[*]", true); }); + } + + [Test] + public void EvaluateSliceOnObjectWithError() + { + JObject o = new JObject( + new JProperty("Blah", 1)); + + ExceptionAssert.Throws( + @"Array slice is not valid on JObject.", + () => { o.SelectToken("[:]", true); }); + } + [Test] public void EvaluatePropertyOnArray() { @@ -128,6 +150,16 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.IsNull(t); } + [Test] + public void EvaluateMultipleResultsError() + { + JArray a = new JArray(1, 2, 3, 4, 5); + + ExceptionAssert.Throws( + @"Path returned multiple tokens.", + () => { a.SelectToken("[0, 1]"); }); + } + [Test] public void EvaluatePropertyOnArrayWithError() { @@ -138,6 +170,16 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath () => { a.SelectToken("BlahBlah", true); }); } + [Test] + public void EvaluateNoResultsWithMultipleArrayIndexes() + { + JArray a = new JArray(1, 2, 3, 4, 5); + + ExceptionAssert.Throws( + @"Index 9 outside the bounds of JArray.", + () => { a.SelectToken("[9,10]", true); }); + } + [Test] public void EvaluateConstructorOutOfBoundsIndxerWithError() { @@ -148,6 +190,14 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath () => { c.SelectToken("[1]", true); }); } + [Test] + public void EvaluateConstructorOutOfBoundsIndxer() + { + JConstructor c = new JConstructor("Blah"); + + Assert.IsNull(c.SelectToken("[1]")); + } + [Test] public void EvaluateMissingPropertyWithError() { @@ -159,6 +209,51 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath () => { o.SelectToken("Missing", true); }); } + [Test] + public void EvaluateMissingPropertyIndexWithError() + { + JObject o = new JObject( + new JProperty("Blah", 1)); + + ExceptionAssert.Throws( + "Property 'Missing' does not exist on JObject.", + () => { o.SelectToken("['Missing','Missing2']", true); }); + } + + [Test] + public void EvaluateMultiPropertyIndexOnArrayWithError() + { + JArray a = new JArray(1, 2, 3, 4, 5); + + ExceptionAssert.Throws( + "Properties 'Missing', 'Missing2' not valid on JArray.", + () => { a.SelectToken("['Missing','Missing2']", true); }); + } + + [Test] + public void EvaluateArraySliceWithError() + { + JArray a = new JArray(1, 2, 3, 4, 5); + + ExceptionAssert.Throws( + "Array slice of 99 to * returned no results.", + () => { a.SelectToken("[99:]", true); }); + + ExceptionAssert.Throws( + "Array slice of 1 to -19 returned no results.", + () => { a.SelectToken("[1:-19]", true); }); + + ExceptionAssert.Throws( + "Array slice of * to -19 returned no results.", + () => { a.SelectToken("[:-19]", true); }); + + a = new JArray(); + + ExceptionAssert.Throws( + "Array slice of * to * returned no results.", + () => { a.SelectToken("[:]", true); }); + } + [Test] public void EvaluateOutOfBoundsIndxer() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs index 6571c920..fb04e301 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/QueryExpressionTests.cs @@ -131,5 +131,39 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.IsFalse(compositeExpression.IsMatch(o3)); } + + [Test] + public void BooleanExpressionTest() + { + BooleanQueryExpression e1 = new BooleanQueryExpression + { + Operator = QueryOperator.LessThan, + Value = new JValue(3), + Path = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e1.IsMatch(new JArray(1, 2, 3, 4, 5))); + Assert.IsTrue(e1.IsMatch(new JArray(2, 3, 4, 5))); + Assert.IsFalse(e1.IsMatch(new JArray(3, 4, 5))); + Assert.IsFalse(e1.IsMatch(new JArray(4, 5))); + + BooleanQueryExpression e2 = new BooleanQueryExpression + { + Operator = QueryOperator.LessThanOrEquals, + Value = new JValue(3), + Path = new List + { + new ArrayIndexFilter() + } + }; + + Assert.IsTrue(e2.IsMatch(new JArray(1, 2, 3, 4, 5))); + Assert.IsTrue(e2.IsMatch(new JArray(2, 3, 4, 5))); + Assert.IsTrue(e2.IsMatch(new JArray(3, 4, 5))); + Assert.IsFalse(e2.IsMatch(new JArray(4, 5))); + } } } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs index 3b810f2c..c5a894c9 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs @@ -1653,6 +1653,35 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual("string!", ((MyChild)p2.Child).MyProperty); } #endif + + [Test] + public void ListOfStackWithFullAssemblyName() + { + var input = new List>(); + + input.Add(new Stack(new List { "One", "Two", "Three" })); + input.Add(new Stack(new List { "Four", "Five", "Six" })); + input.Add(new Stack(new List { "Seven", "Eight", "Nine" })); + + string serialized = JsonConvert.SerializeObject(input, + Newtonsoft.Json.Formatting.Indented, + new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Full } // TypeNameHandling.Auto will work + ); + + Console.WriteLine(serialized); + + var output = JsonConvert.DeserializeObject>>(serialized, + new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } + ); + + foreach (var stack in output) + { + foreach (var value in stack) + { + Console.WriteLine(value); + } + } + } } #if !NETFX_CORE diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 372ab34b..1abc5590 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -819,14 +819,16 @@ namespace Newtonsoft.Json { ValidationUtils.ArgumentNotNull(value, "value"); - StringReader sr = new StringReader(value); JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings); // by default DeserializeObject should check for additional content if (!jsonSerializer.IsCheckAdditionalContentSet()) jsonSerializer.CheckAdditionalContent = true; - return jsonSerializer.Deserialize(new JsonTextReader(sr), type); + using (var reader = new JsonTextReader(new StringReader(value))) + { + return jsonSerializer.Deserialize(reader, type); + } } #if !(NET20 || NET35 || PORTABLE40) @@ -916,10 +918,9 @@ namespace Newtonsoft.Json /// public static void PopulateObject(string value, object target, JsonSerializerSettings settings) { - StringReader sr = new StringReader(value); JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings); - using (JsonReader jsonReader = new JsonTextReader(sr)) + using (JsonReader jsonReader = new JsonTextReader(new StringReader(value))) { jsonSerializer.Populate(jsonReader, target); diff --git a/Src/Newtonsoft.Json/Linq/JArray.cs b/Src/Newtonsoft.Json/Linq/JArray.cs index fb0fbe3d..adebfd00 100644 --- a/Src/Newtonsoft.Json/Linq/JArray.cs +++ b/Src/Newtonsoft.Json/Linq/JArray.cs @@ -143,14 +143,15 @@ namespace Newtonsoft.Json.Linq /// public new static JArray Parse(string json) { - JsonReader reader = new JsonTextReader(new StringReader(json)); + using (JsonReader reader = new JsonTextReader(new StringReader(json))) + { + JArray a = Load(reader); - JArray a = Load(reader); + if (reader.Read() && reader.TokenType != JsonToken.Comment) + throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - if (reader.Read() && reader.TokenType != JsonToken.Comment) - throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - - return a; + return a; + } } /// diff --git a/Src/Newtonsoft.Json/Linq/JObject.cs b/Src/Newtonsoft.Json/Linq/JObject.cs index 379954c3..9198237f 100644 --- a/Src/Newtonsoft.Json/Linq/JObject.cs +++ b/Src/Newtonsoft.Json/Linq/JObject.cs @@ -326,14 +326,15 @@ namespace Newtonsoft.Json.Linq /// public new static JObject Parse(string json) { - JsonReader reader = new JsonTextReader(new StringReader(json)); + using (JsonReader reader = new JsonTextReader(new StringReader(json))) + { + JObject o = Load(reader); - JObject o = Load(reader); + if (reader.Read() && reader.TokenType != JsonToken.Comment) + throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - if (reader.Read() && reader.TokenType != JsonToken.Comment) - throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - - return o; + return o; + } } /// diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 5b75ba0b..4f58be3e 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -1853,14 +1853,15 @@ namespace Newtonsoft.Json.Linq /// A populated from the string that contains JSON. public static JToken Parse(string json) { - JsonReader reader = new JsonTextReader(new StringReader(json)); + using (JsonReader reader = new JsonTextReader(new StringReader(json))) + { + JToken t = Load(reader); - JToken t = Load(reader); + if (reader.Read() && reader.TokenType != JsonToken.Comment) + throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - if (reader.Read() && reader.TokenType != JsonToken.Comment) - throw JsonReaderException.Create(reader, "Additional text found in JSON string after parsing content."); - - return t; + return t; + } } /// @@ -1907,48 +1908,61 @@ namespace Newtonsoft.Json.Linq } /// - /// Selects the token that matches the object path. + /// Selects a using a JPath expression. Selects the token that matches the object path. /// /// - /// The object path from the current to the - /// to be returned. This must be a string of property names or array indexes separated - /// by periods, such as Tables[0].DefaultView[0].Price in C# or - /// Tables(0).DefaultView(0).Price in Visual Basic. + /// A that contains a JPath expression. /// - /// The that matches the object path or a null reference if no matching token is found. + /// A , or null. public JToken SelectToken(string path) { return SelectToken(path, false); } /// - /// Selects the token that matches the object path. + /// Selects a using a JPath expression. Selects the token that matches the object path. /// /// - /// The object path from the current to the - /// to be returned. This must be a string of property names or array indexes separated - /// by periods, such as Tables[0].DefaultView[0].Price in C# or - /// Tables(0).DefaultView(0).Price in Visual Basic. + /// A that contains a JPath expression. /// - /// A flag to indicate whether an error should be thrown if no token is found. - /// The that matches the object path. + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// A . public JToken SelectToken(string path, bool errorWhenNoMatch) { JPath p = new JPath(path); - // todo change this to a foreach - List result = p.Evaluate(this, errorWhenNoMatch).ToList(); - if (result.Count > 1) - throw new Exception("TODO"); + JToken token = null; + foreach (JToken t in p.Evaluate(this, errorWhenNoMatch)) + { + if (token != null) + throw new JsonException("Path returned multiple tokens."); - return result.SingleOrDefault(); + token = t; + } + + return token; } + /// + /// Selects a collection of elements using a JPath expression. + /// + /// + /// A that contains a JPath expression. + /// + /// An that contains the selected elements. public IEnumerable SelectTokens(string path) { return SelectTokens(path, false); } + /// + /// Selects a collection of elements using a JPath expression. + /// + /// + /// A that contains a JPath expression. + /// + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// An that contains the selected elements. public IEnumerable SelectTokens(string path, bool errorWhenNoMatch) { JPath p = new JPath(path); diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs index 262cbe3d..ee229dec 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Linq.JsonPath { @@ -14,33 +16,59 @@ namespace Newtonsoft.Json.Linq.JsonPath if (Step == 0) throw new JsonException("Step cannot be zero."); - // todo error when no match foreach (JToken t in current) { - IList sourceCollection = t as IList ?? new List(t); - - // set defaults for null arguments - int stepCount = Step ?? 1; - int startIndex = Start ?? ((stepCount > 0) ? 0 : sourceCollection.Count - 1); - int stopIndex = End ?? ((stepCount > 0) ? sourceCollection.Count : -1); - - // start from the end of the list if start is negitive - if (Start < 0) startIndex = sourceCollection.Count + startIndex; - - // end from the start of the list if stop is negitive - if (End < 0) stopIndex = sourceCollection.Count + stopIndex; - - // ensure indexes keep within collection bounds - startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue); - startIndex = Math.Min(startIndex, (stepCount > 0) ? sourceCollection.Count : sourceCollection.Count - 1); - stopIndex = Math.Max(stopIndex, -1); - stopIndex = Math.Min(stopIndex, sourceCollection.Count); - - for (int i = startIndex; (stepCount > 0) ? i < stopIndex : i > stopIndex; i += stepCount) + JArray a = t as JArray; + if (a != null) { - yield return sourceCollection[i]; + // set defaults for null arguments + int stepCount = Step ?? 1; + int startIndex = Start ?? ((stepCount > 0) ? 0 : a.Count - 1); + int stopIndex = End ?? ((stepCount > 0) ? a.Count : -1); + + // start from the end of the list if start is negitive + if (Start < 0) startIndex = a.Count + startIndex; + + // end from the start of the list if stop is negitive + if (End < 0) stopIndex = a.Count + stopIndex; + + // ensure indexes keep within collection bounds + startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue); + startIndex = Math.Min(startIndex, (stepCount > 0) ? a.Count : a.Count - 1); + stopIndex = Math.Max(stopIndex, -1); + stopIndex = Math.Min(stopIndex, a.Count); + + bool positiveStep = (stepCount > 0); + + if (IsValid(startIndex, stopIndex, positiveStep)) + { + for (int i = startIndex; IsValid(i, stopIndex, positiveStep); i += stepCount) + { + yield return a[i]; + } + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Array slice of {0} to {1} returned no results.".FormatWith(CultureInfo.InvariantCulture, + Start != null ? Start.Value.ToString(CultureInfo.InvariantCulture) : "*", + End != null ? End.Value.ToString(CultureInfo.InvariantCulture) : "*")); + } + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Array slice is not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name)); } } } + + private bool IsValid(int index, int stopIndex, bool positiveStep) + { + if (positiveStep) + return (index < stopIndex); + + return (index > stopIndex); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs index d4ae7dcf..2b9bfaa0 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs @@ -27,8 +27,6 @@ namespace Newtonsoft.Json.Linq.JsonPath } else { - // todo - error when no results - foreach (KeyValuePair p in o) { yield return p.Value; diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs index 1fbabcbe..7480a514 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldMultipleFilter.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Linq.JsonPath @@ -21,13 +22,16 @@ namespace Newtonsoft.Json.Linq.JsonPath if (v != null) yield return v; + + if (errorWhenNoMatch) + throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, name)); } } - //else - //{ - // if (errorWhenNoMatch) - // throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, Name ?? "*", t.GetType().Name)); - //} + else + { + if (errorWhenNoMatch) + throw new JsonException("Properties {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, string.Join(", ", Names.Select(n => "'" + n + "'")), t.GetType().Name)); + } } } } diff --git a/Src/Newtonsoft.Json/Schema/JsonSchema.cs b/Src/Newtonsoft.Json/Schema/JsonSchema.cs index 0084a6f1..25ad8c6d 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchema.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchema.cs @@ -301,9 +301,10 @@ namespace Newtonsoft.Json.Schema { ValidationUtils.ArgumentNotNull(json, "json"); - JsonReader reader = new JsonTextReader(new StringReader(json)); - - return Read(reader, resolver); + using (JsonReader reader = new JsonTextReader(new StringReader(json))) + { + return Read(reader, resolver); + } } ///