From 8e727a27b28d790e0986530a2962cda510353813 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 3 Aug 2014 00:08:31 +1200 Subject: [PATCH] -JProperty performance -Improved property name escape skipping --- .../Linq/JPropertyTests.cs | 58 +++++++++++ Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 19 ++++ .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/ContractResolverTests.cs | 98 ++++++++++++++++++- Src/Newtonsoft.Json/Linq/JProperty.cs | 93 +++++++++++++++++- .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonProperty.cs | 24 +---- .../Utilities/JavaScriptUtils.cs | 14 +++ 8 files changed, 281 insertions(+), 29 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Linq/JPropertyTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JPropertyTests.cs index 4d295e79..22d40d05 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JPropertyTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JPropertyTests.cs @@ -27,6 +27,11 @@ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; +#endif using Newtonsoft.Json.Linq; #if !NETFX_CORE using NUnit.Framework; @@ -121,6 +126,59 @@ namespace Newtonsoft.Json.Tests.Linq () => { l.Remove(p.Value); }); } + [Test] + public void IListRemoveAt() + { + JProperty p = new JProperty("TestProperty", null); + IList l = p; + + ExceptionAssert.Throws( + "Cannot add or remove items from Newtonsoft.Json.Linq.JProperty.", + () => { l.RemoveAt(0); }); + } + + [Test] + public void JPropertyLinq() + { + JProperty p = new JProperty("TestProperty", null); + IList l = p; + + List result = l.Cast().ToList(); + Assert.AreEqual(1, result.Count); + } + + [Test] + public void JPropertyDeepEquals() + { + JProperty p1 = new JProperty("TestProperty", null); + JProperty p2 = new JProperty("TestProperty", null); + + Assert.AreEqual(true, JToken.DeepEquals(p1, p2)); + } + + [Test] + public void JPropertyIndexOf() + { + JValue v = new JValue(1); + JProperty p1 = new JProperty("TestProperty", v); + + IList l1 = p1; + Assert.AreEqual(0, l1.IndexOf(v)); + + IList l2 = p1; + Assert.AreEqual(0, l2.IndexOf(v)); + } + + [Test] + public void JPropertyContains() + { + JValue v = new JValue(1); + JProperty p = new JProperty("TestProperty", v); + + Assert.AreEqual(true, p.Contains(v)); + Assert.AreEqual(false, p.Contains(new JValue(1))); + } + [Test] public void Load() { diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 1656d8af..446ccab3 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -1039,6 +1039,25 @@ If attributes are not mentioned, default values are used in each case. }, "JObject.ToString"); } + [Test] + public void JObjectCreationAndPropertyAccess() + { + TimeOperation(() => + { + for (int i = 0; i < Iterations * 100; i++) + { + JObject test = new JObject( + new JProperty("one", 1), + new JProperty("two", 2)); + + test["i"] = i; + int j = (int)test["i"]; + test["j"] = j; + } + return null; + }, "JObjectCreationAndPropertyAccess"); + } + [Test] public void NestedJToken() { diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 8101b3c0..6e067e48 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.17601")] +[assembly: AssemblyFileVersion("6.0.4.17602")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs index 8b882f0c..c4b32372 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs @@ -71,9 +71,12 @@ namespace Newtonsoft.Json.Tests.Serialization public class EscapedPropertiesContractResolver : DefaultContractResolver { + public string PropertyPrefix { get; set; } + public string PropertySuffix { get; set; } + protected internal override string ResolvePropertyName(string propertyName) { - return base.ResolvePropertyName(propertyName + @"-'-""-"); + return base.ResolvePropertyName(PropertyPrefix + propertyName + PropertySuffix); } } @@ -296,7 +299,10 @@ namespace Newtonsoft.Json.Tests.Serialization }, new JsonSerializerSettings { - ContractResolver = new EscapedPropertiesContractResolver() + ContractResolver = new EscapedPropertiesContractResolver + { + PropertySuffix = @"-'-""-" + } }); Assert.AreEqual(@"{""AddressLine1-'-\""-"":""value!""}", json); @@ -308,6 +314,94 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"AddressLine1-'-""-", reader.Value); } + [Test] + public void SerializeWithHtmlEscapedPropertyName() + { + string json = JsonConvert.SerializeObject( + new AddressWithDataMember + { + AddressLine1 = "value!" + }, + new JsonSerializerSettings + { + ContractResolver = new EscapedPropertiesContractResolver + { + PropertyPrefix = "", + PropertySuffix = "" + }, + StringEscapeHandling = StringEscapeHandling.EscapeHtml + }); + + Assert.AreEqual(@"{""\u003cb\u003eAddressLine1\u003c/b\u003e"":""value!""}", json); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.Read(); + reader.Read(); + + Assert.AreEqual(@"AddressLine1", reader.Value); + } + + [Test] + public void CalculatingPropertyNameEscapedSkipping() + { + JsonProperty p = new JsonProperty { PropertyName = "abc" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "123" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "._-" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "!@#" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "$%^" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "?*(" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = ")_+" }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "=:," }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = null }; + Assert.IsTrue(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "&" }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "<" }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = ">" }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = "'" }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = @"""" }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = Environment.NewLine }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = '\0'.ToString(CultureInfo.InvariantCulture) }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = '\n'.ToString(CultureInfo.InvariantCulture) }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = '\v'.ToString(CultureInfo.InvariantCulture) }; + Assert.IsFalse(p._skipPropertyNameEscape); + + p = new JsonProperty { PropertyName = '\u00B9'.ToString(CultureInfo.InvariantCulture) }; + Assert.IsFalse(p._skipPropertyNameEscape); + } + #if !NET20 [Test] public void DeserializeDataMemberClassWithNoDataContract() diff --git a/Src/Newtonsoft.Json/Linq/JProperty.cs b/Src/Newtonsoft.Json/Linq/JProperty.cs index 5051eb8c..ab277605 100644 --- a/Src/Newtonsoft.Json/Linq/JProperty.cs +++ b/Src/Newtonsoft.Json/Linq/JProperty.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using Newtonsoft.Json.Utilities; using System.Diagnostics; @@ -36,7 +37,93 @@ namespace Newtonsoft.Json.Linq /// public class JProperty : JContainer { - private readonly List _content = new List(); + #region JPropertyList + private class JPropertyList : IList + { + internal JToken _token; + + public IEnumerator GetEnumerator() + { + if (_token != null) + yield return _token; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(JToken item) + { + _token = item; + } + + public void Clear() + { + _token = null; + } + + public bool Contains(JToken item) + { + return (_token == item); + } + + public void CopyTo(JToken[] array, int arrayIndex) + { + if (_token != null) + array[arrayIndex] = _token; + } + + public bool Remove(JToken item) + { + if (_token == item) + { + _token = null; + return true; + } + return false; + } + + public int Count + { + get { return (_token != null) ? 1 : 0; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public int IndexOf(JToken item) + { + return (_token == item) ? 0 : -1; + } + + public void Insert(int index, JToken item) + { + if (index == 0) + _token = item; + } + + public void RemoveAt(int index) + { + if (index == 0) + _token = null; + } + + public JToken this[int index] + { + get { return (index == 0) ? _token : null; } + set + { + if (index == 0) + _token = value; + } + } + } + #endregion + + private readonly JPropertyList _content = new JPropertyList(); private readonly string _name; /// @@ -65,14 +152,14 @@ namespace Newtonsoft.Json.Linq public JToken Value { [DebuggerStepThrough] - get { return (_content.Count > 0) ? _content[0] : null; } + get { return _content._token; } set { CheckReentrancy(); JToken newValue = value ?? JValue.CreateNull(); - if (_content.Count == 0) + if (_content._token == null) { InsertItem(0, newValue, false); } diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index d0b29931..eca9a3a0 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.17601")] +[assembly: AssemblyFileVersion("6.0.4.17602")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs index a275557c..604dc347 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs @@ -43,7 +43,7 @@ namespace Newtonsoft.Json.Serialization private object _defaultValue; private bool _hasGeneratedDefaultValue; private string _propertyName; - private bool _skipPropertyNameEscape; + internal bool _skipPropertyNameEscape; private Type _propertyType; // use to cache contract during deserialization @@ -59,27 +59,7 @@ namespace Newtonsoft.Json.Serialization set { _propertyName = value; - CalculateSkipPropertyNameEscape(); - } - } - - private void CalculateSkipPropertyNameEscape() - { - if (_propertyName == null) - { - _skipPropertyNameEscape = false; - } - else - { - _skipPropertyNameEscape = true; - foreach (char c in _propertyName) - { - if (!char.IsLetterOrDigit(c) && c != '_' && c != '@') - { - _skipPropertyNameEscape = false; - break; - } - } + _skipPropertyNameEscape = !JavaScriptUtils.ShouldEscapeJavaScriptString(_propertyName, JavaScriptUtils.HtmlCharEscapeFlags); } } diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index 0ec0887f..ed618139 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -72,6 +72,20 @@ namespace Newtonsoft.Json.Utilities private const string EscapedUnicodeText = "!"; + public static bool ShouldEscapeJavaScriptString(string s, bool[] charEscapeFlags) + { + if (s == null) + return false; + + foreach (char c in s) + { + if (c >= charEscapeFlags.Length || charEscapeFlags[c]) + return true; + } + + return false; + } + public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters, bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling, ref char[] writeBuffer) {