From 2d5eba5599f31c5edae1c1e70f22f746c89d1c0f Mon Sep 17 00:00:00 2001 From: Ilya Grebnov Date: Wed, 20 Jun 2018 15:22:33 -0700 Subject: [PATCH] Extracted JsonNameTable for JsonTextReader (#1703) --- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 32 ++++++++++++++++--- ...tyNameTable.cs => DefaultJsonNameTable.cs} | 27 +++++++++++++--- Src/Newtonsoft.Json/JsonNameTable.cs | 16 ++++++++++ Src/Newtonsoft.Json/JsonSerializer.cs | 9 +++--- Src/Newtonsoft.Json/JsonTextReader.Async.cs | 4 +-- Src/Newtonsoft.Json/JsonTextReader.cs | 10 ++++-- .../CamelCasePropertyNamesContractResolver.cs | 4 +-- .../Serialization/DefaultContractResolver.cs | 6 ++-- 8 files changed, 85 insertions(+), 23 deletions(-) rename Src/Newtonsoft.Json/{Utilities/PropertyNameTable.cs => DefaultJsonNameTable.cs} (82%) create mode 100644 Src/Newtonsoft.Json/JsonNameTable.cs diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index 8862c975..5d4cec78 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -174,11 +174,11 @@ namespace Newtonsoft.Json.Tests reader.Read(); JsonTextReader jsonTextReader = (JsonTextReader)reader; - Assert.IsNotNull(jsonTextReader.NameTable); + Assert.IsNotNull(jsonTextReader.PropertyNameTable); string s = serializer.Deserialize(reader); Assert.AreEqual("hi", s); - Assert.IsNotNull(jsonTextReader.NameTable); + Assert.IsNotNull(jsonTextReader.PropertyNameTable); NameTableTestClass o = new NameTableTestClass { @@ -200,16 +200,40 @@ namespace Newtonsoft.Json.Tests StringReader sr = new StringReader("{'property':'hi'}"); JsonTextReader jsonTextReader = new JsonTextReader(sr); - Assert.IsNull(jsonTextReader.NameTable); + Assert.IsNull(jsonTextReader.PropertyNameTable); JsonSerializer serializer = new JsonSerializer(); serializer.Converters.Add(new NameTableTestClassConverter()); NameTableTestClass o = serializer.Deserialize(jsonTextReader); - Assert.IsNull(jsonTextReader.NameTable); + Assert.IsNull(jsonTextReader.PropertyNameTable); Assert.AreEqual("hi", o.Value); } + public class CustonNameTable : JsonNameTable + { + public override string Get(char[] key, int start, int length) + { + return "_" + new string(key, start, length); + } + } + + [Test] + public void CustonNameTableTest() + { + StringReader sr = new StringReader("{'property':'hi'}"); + JsonTextReader jsonTextReader = new JsonTextReader(sr); + + Assert.IsNull(jsonTextReader.PropertyNameTable); + var nameTable = jsonTextReader.PropertyNameTable = new CustonNameTable(); + + JsonSerializer serializer = new JsonSerializer(); + Dictionary o = serializer.Deserialize>(jsonTextReader); + Assert.AreEqual("hi", o["_property"]); + + Assert.AreEqual(nameTable, jsonTextReader.PropertyNameTable); + } + [Test] public void DefaultSettings_Example() { diff --git a/Src/Newtonsoft.Json/Utilities/PropertyNameTable.cs b/Src/Newtonsoft.Json/DefaultJsonNameTable.cs similarity index 82% rename from Src/Newtonsoft.Json/Utilities/PropertyNameTable.cs rename to Src/Newtonsoft.Json/DefaultJsonNameTable.cs index 4f1d2fc7..66865cd2 100644 --- a/Src/Newtonsoft.Json/Utilities/PropertyNameTable.cs +++ b/Src/Newtonsoft.Json/DefaultJsonNameTable.cs @@ -25,9 +25,12 @@ using System; -namespace Newtonsoft.Json.Utilities +namespace Newtonsoft.Json { - internal class PropertyNameTable + /// + /// The default JSON name table implementation. + /// + public class DefaultJsonNameTable : JsonNameTable { // used to defeat hashtable DoS attack where someone passes in lots of strings that hash to the same hash code private static readonly int HashCodeRandomizer; @@ -36,17 +39,26 @@ namespace Newtonsoft.Json.Utilities private Entry[] _entries; private int _mask = 31; - static PropertyNameTable() + static DefaultJsonNameTable() { HashCodeRandomizer = Environment.TickCount; } - public PropertyNameTable() + /// + /// Initializes a new instance of the class. + /// + public DefaultJsonNameTable() { _entries = new Entry[_mask + 1]; } - public string Get(char[] key, int start, int length) + /// + /// Gets the string containing the same characters as the specified range of characters in the given array. + /// + /// The character array containing the name to find. + /// The zero-based index into the array specifying the first character of the name. + /// The number of characters in the name. + public override string Get(char[] key, int start, int length) { if (length == 0) { @@ -74,6 +86,11 @@ namespace Newtonsoft.Json.Utilities return null; } + /// + /// Adds the specified string into name table. + /// + /// The string to add. + /// This method is not thread-safe. public string Add(string key) { if (key == null) diff --git a/Src/Newtonsoft.Json/JsonNameTable.cs b/Src/Newtonsoft.Json/JsonNameTable.cs new file mode 100644 index 00000000..22888ae5 --- /dev/null +++ b/Src/Newtonsoft.Json/JsonNameTable.cs @@ -0,0 +1,16 @@ +namespace Newtonsoft.Json +{ + /// + /// Base class for a table of atomized string objects. + /// + public abstract class JsonNameTable + { + /// + /// Gets the string containing the same characters as the specified range of characters in the given array. + /// + /// The character array containing the name to find. + /// The zero-based index into the array specifying the first character of the name. + /// The number of characters in the name. + public abstract string Get(char[] key, int start, int length); + } +} diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index aad6fbcb..aac6cede 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -974,9 +974,9 @@ namespace Newtonsoft.Json if (reader is JsonTextReader textReader) { - if (_contractResolver is DefaultContractResolver resolver) + if (textReader.PropertyNameTable == null && _contractResolver is DefaultContractResolver resolver) { - textReader.NameTable = resolver.GetNameTable(); + textReader.PropertyNameTable = resolver.GetNameTable(); } } } @@ -1009,9 +1009,10 @@ namespace Newtonsoft.Json reader.DateFormatString = previousDateFormatString; } - if (reader is JsonTextReader textReader) + if (reader is JsonTextReader textReader && textReader.PropertyNameTable != null && + _contractResolver is DefaultContractResolver resolver && textReader.PropertyNameTable == resolver.GetNameTable()) { - textReader.NameTable = null; + textReader.PropertyNameTable = null; } } diff --git a/Src/Newtonsoft.Json/JsonTextReader.Async.cs b/Src/Newtonsoft.Json/JsonTextReader.Async.cs index 14f8902b..9a4638c4 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.Async.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.Async.cs @@ -979,9 +979,9 @@ namespace Newtonsoft.Json string propertyName; - if (NameTable != null) + if (PropertyNameTable != null) { - propertyName = NameTable.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length) + propertyName = PropertyNameTable.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length) // no match in name table ?? _stringReference.ToString(); } diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 78135b77..67512da2 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -74,7 +74,6 @@ namespace Newtonsoft.Json private StringBuffer _stringBuffer; private StringReference _stringReference; private IArrayPool _arrayPool; - internal PropertyNameTable NameTable; /// /// Initializes a new instance of the class with the specified . @@ -105,6 +104,11 @@ namespace Newtonsoft.Json internal int CharPos => _charPos; #endif + /// + /// Gets or sets the reader's property name table. + /// + public JsonNameTable PropertyNameTable { get; set; } + /// /// Gets or sets the reader's character buffer pool. /// @@ -1563,9 +1567,9 @@ namespace Newtonsoft.Json string propertyName; - if (NameTable != null) + if (PropertyNameTable != null) { - propertyName = NameTable.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); + propertyName = PropertyNameTable.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); // no match in name table if (propertyName == null) diff --git a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs index 786922d9..ec2c0c91 100644 --- a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs @@ -68,7 +68,7 @@ namespace Newtonsoft.Json.Serialization public class CamelCasePropertyNamesContractResolver : DefaultContractResolver { private static readonly object TypeContractCacheLock = new object(); - private static readonly PropertyNameTable NameTable = new PropertyNameTable(); + private static readonly DefaultJsonNameTable NameTable = new DefaultJsonNameTable(); private static Dictionary _contractCache; /// @@ -119,7 +119,7 @@ namespace Newtonsoft.Json.Serialization return contract; } - internal override PropertyNameTable GetNameTable() + internal override DefaultJsonNameTable GetNameTable() { return NameTable; } diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index ba0ef8b6..118df102 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -98,7 +98,7 @@ namespace Newtonsoft.Json.Serialization new RegexConverter() }; - private readonly PropertyNameTable _nameTable = new PropertyNameTable(); + private readonly DefaultJsonNameTable _nameTable = new DefaultJsonNameTable(); private readonly ThreadSafeStore _contractCache; @@ -1334,7 +1334,7 @@ namespace Newtonsoft.Json.Serialization throw new JsonSerializationException("Null collection of serializable members returned."); } - PropertyNameTable nameTable = GetNameTable(); + DefaultJsonNameTable nameTable = GetNameTable(); JsonPropertyCollection properties = new JsonPropertyCollection(type); @@ -1358,7 +1358,7 @@ namespace Newtonsoft.Json.Serialization return orderedProperties; } - internal virtual PropertyNameTable GetNameTable() + internal virtual DefaultJsonNameTable GetNameTable() { return _nameTable; }