JSON DOM refactor (dotnet/corefx#41041)
authorKatarzyna Bułat <t-kabul@microsoft.com>
Thu, 14 Nov 2019 00:14:02 +0000 (16:14 -0800)
committerAhson Khan <ahson_ahmedk@yahoo.com>
Thu, 14 Nov 2019 00:14:02 +0000 (16:14 -0800)
* work on review comments, documentation changes included

* work on including review comments

* work on including review comments

* StringComparison methods improvements

* work on adressing review comments

* helping comment added

* StringComparison tests improved to check all enum values

* setting CurrentCulture test added

* review comments included

* build fixes

* CurrentCulture test removed

* Address nits and react to recent API changes.

Commit migrated from https://github.com/dotnet/corefx/commit/a0f71fa14ecd17c6aee146b8ef76aaec0dd389ed

20 files changed:
src/libraries/System.Text.Json/docs/writable_json_dom_spec.md
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.RecursionStackFrame.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.Traversal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.TraversalHelpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNull.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
src/libraries/System.Text.Json/tests/JsonArrayTests.cs
src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs
src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs
src/libraries/System.Text.Json/tests/JsonNode.TraversalTests.cs
src/libraries/System.Text.Json/tests/JsonNullTests.cs
src/libraries/System.Text.Json/tests/JsonObjectTests.cs
src/libraries/System.Text.Json/tests/JsonStringTests.cs

index c1705ba..35f8a83 100644 (file)
@@ -194,20 +194,20 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement());
 * `JsonNull` class instead of `null` reference to node.
 * No additional overloads of Add methods for primary types (bool, string, int, double,  long...) for `JsonObject` and `JsonArray`. Instead - implicit cast operators in JsonNode.
 * `Sort` not implemented for `JsonArray`, beacuse there is no right way to compare `JsonObjects`. If a user wants to sort a `JsonArray` of `JsonNumbers`, `JsonBooleans` or `JsonStrings` they now needs to do the following: convert the `JsonArray` to a regular array (by iterating through all elements), call sort (and convert back to `JsonArray` if needed).
-* Property names duplicates handling method possible to chose during parsing to `JsonNode`. When creating `JsonObject` Add method throws an exception for duplicates and indexer replaces old property value with new one. 
+* Property names duplicates handling method possible to choose during parsing to `JsonNode`. When creating `JsonObject` Add method throws an exception for duplicates and indexer replaces old property value with new one. 
 * No support for escaped characters when creating `JsonNumber` from string.
 * `JsonValueKind` property that a caller can inspect and cast to the right concrete type
 * Transformation API:
     * `DeepCopy` method allowing to change JsonElement into JsonNode recursively transforming all of the elements.
     * `AsJsonElement`method allowing to change JsonNode into JsonElement with IsImmutable property set to false.
     * `IsImmutable` property informing if JsonElement is keeping JsonDocument or JsonNode underneath.
-    * `Parse(string)` method to be able to parse a JSON string right into JsonNode if the user knows they wants mutable version. It allows chosing duplicates handling method.
+    * `Parse(string)` method to be able to parse a JSON string right into JsonNode if the user knows they want a mutable version. It allows choosing duplicates handling method.
     * `Clone` method to make a copy of the whole JsonNode tree.
-    * `GetNode` and TryGetNode methods allowing to retrieve it from JsonElement.
+    * `GetNode` and `TryGetNode` methods allowing to retrieve it from JsonElement.
     * Internal `WriteTo(Utf8JsonWriter)` method for writing a JsonNode to a Utf8JsonWriter without having to go through JsonElement.
     * `ToJsonString` method transforming JsonNode to string representation using WriteTo.
 * No recursive equals for `JsonArray` and `JsonObject`.
-* `JsonNode` derived types does not implement `IComparable`.
+* `JsonNode` derived types do not implement `IComparable`.
 * `JsonObject` does not implement `IDictionary`, but `JsonArray` implements `IList`. 
 * We support order preservation when adding/removing values in `JsonArray`/`JsonObject`.
 * We do not support creating `JsonNumber` from `BigInterger` without changing it to string.
@@ -234,6 +234,13 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement());
     - Internal struct field which has all the supported numeric types
     - Unsigned long field accompanying string to store types that are <= 8 bytes long
 
+* Should we add overloads for all nullable types as well? For example: 
+    ```csharp 
+    public static implicit operator System.Text.Json.JsonNode (bool? value) { throw null; }
+    ```
+
+* Do we want to have implicit cast operators on `JsonNull`, `JsonBoolean`, `JsonString` and `JsonNumber` while we already have them in `JsonNode`? It would be consistent, but implicit cast from e.g. float.Infinity to `JsonNumber` would throw an exception, because we would not be able return `JsonString` in this case anymore. 
+
 ## Useful links
 
 ### JSON
index 6d36478..59b88fa 100644 (file)
     <value>The JSON array was modified during iteration.</value>
   </data>
   <data name="NotNodeJsonElementParent" xml:space="preserve">
-    <value>This JsonElement instance was not built from JsonNode</value>
+    <value>This JsonElement instance was not built from a JsonNode and is immutable.</value>
   </data>
 </root>
\ No newline at end of file
index 72f632b..47e245c 100644 (file)
     <Compile Include="System\Text\Json\Node\JsonArray.cs" />
     <Compile Include="System\Text\Json\Node\JsonArrayEnumerator.cs" />
     <Compile Include="System\Text\Json\Node\JsonBoolean.cs" />
-    <Compile Include="System\Text\Json\Node\JsonNode.cs" /> 
+    <Compile Include="System\Text\Json\Node\JsonNode.cs" />
+    <Compile Include="System\Text\Json\Node\JsonNode.RecursionStackFrame.cs" />
     <Compile Include="System\Text\Json\Node\JsonNode.Traversal.cs" /> 
     <Compile Include="System\Text\Json\Node\JsonNode.TraversalHelpers.cs" /> 
     <Compile Include="System\Text\Json\Node\JsonNodeOptions.cs" />
index cfc7494..0ea9d06 100644 (file)
@@ -1602,11 +1602,12 @@ namespace System.Text.Json
             if (_parent is JsonDocument document)
             {
                 document.WriteElementTo(_idx, writer);
-                return;
             }
-
-            var jsonNode = (JsonNode)_parent;
-            jsonNode.WriteTo(writer);
+            else
+            {
+                var jsonNode = (JsonNode)_parent;
+                jsonNode.WriteTo(writer);
+            }
         }
 
         /// <summary>
@@ -1685,7 +1686,10 @@ namespace System.Text.Json
         ///   Gets a string representation for the current value appropriate to the value type.
         /// </summary>
         /// <remarks>
-        ///   For JsonElement built from <see cref="JsonDocument"/>:
+        ///   <para>
+        ///     For JsonElement built from <see cref="JsonDocument"/>:
+        ///   </para>
+        ///
         ///   <para>
         ///     For <see cref="JsonValueKind.Null"/>, <see cref="string.Empty"/> is returned.
         ///   </para>
@@ -1705,9 +1709,10 @@ namespace System.Text.Json
         ///   <para>
         ///     For other types, the value of <see cref="GetRawText"/>() is returned.
         ///   </para>
-        /// </remarks>
-        /// <remarks>
-        ///   For JsonElement built from <see cref="JsonNode"/>, the value of <see cref="JsonNode.ToJsonString"/> is returned.
+        ///
+        ///   <para>
+        ///     For JsonElement built from <see cref="JsonNode"/>, the value of <see cref="JsonNode.ToJsonString"/> is returned.
+        ///   </para>
         /// </remarks>
         /// <returns>
         ///   A string representation for the current value appropriate to the value type.
@@ -1759,12 +1764,14 @@ namespace System.Text.Json
         ///   original <see cref="JsonDocument"/>.
         /// </returns>
         /// <remarks>
-        ///   If this JsonElement is itself the output of a previous call to Clone, or
-        ///   a value contained within another JsonElement which was the output of a previous
-        ///   call to Clone, this method results in no additional memory allocation.
-        /// </remarks>
-        /// <remarks>
-        ///   For <see cref="JsonElement"/> built from <see cref="JsonNode"/> performs <see cref="JsonNode.Clone"/>.
+        ///   <para>
+        ///     If this JsonElement is itself the output of a previous call to Clone, or
+        ///     a value contained within another JsonElement which was the output of a previous
+        ///     call to Clone, this method results in no additional memory allocation.
+        ///   </para>
+        ///   <para>
+        ///     For <see cref="JsonElement"/> built from <see cref="JsonNode"/>, performs <see cref="JsonNode.Clone"/>.
+        ///   </para>
         /// </remarks>
         public JsonElement Clone()
         {
index 163383d..4c9f126 100644 (file)
@@ -95,5 +95,23 @@ namespace System.Text.Json
             return dictionary.TryAdd(key, value);
 #endif
         }
+
+        internal static bool IsFinite(double value)
+        {
+#if BUILDING_INBOX_LIBRARY
+            return double.IsFinite(value);
+#else
+            return !(double.IsNaN(value) || double.IsInfinity(value));
+#endif
+        }
+
+        internal static bool IsFinite(float value)
+        {
+#if BUILDING_INBOX_LIBRARY
+            return float.IsFinite(value);
+#else
+            return !(float.IsNaN(value) || float.IsInfinity(value));
+#endif
+        }
     }
 }
index f7e47eb..17b8ed1 100644 (file)
@@ -213,7 +213,7 @@ namespace System.Text.Json
             get => _list[idx];
             set
             {
-                _list[idx] = value ?? new JsonNull();
+                _list[idx] = value ?? JsonNull.Instance;
                 _version++;
             }
         }
@@ -225,7 +225,7 @@ namespace System.Text.Json
         /// <remarks>Null value is allowed and will be converted to the <see cref="JsonNull"/> instance.</remarks>
         public void Add(JsonNode value)
         {
-            _list.Add(value ?? new JsonNull());
+            _list.Add(value ?? JsonNull.Instance);
             _version++;
         }
 
@@ -237,7 +237,7 @@ namespace System.Text.Json
         /// <remarks>Null value is allowed and will be converted to the <see cref="JsonNull"/> instance.</remarks>
         public void Insert(int index, JsonNode item)
         {
-            _list.Insert(index, item ?? new JsonNull());
+            _list.Insert(index, item ?? JsonNull.Instance);
             _version++;
         }
 
@@ -250,7 +250,7 @@ namespace System.Text.Json
         ///   <see langword="false"/> otherwise.
         /// </returns>
         /// <remarks>Null value is allowed and will be converted to the <see cref="JsonNull"/> instance.</remarks>
-        public bool Contains(JsonNode value) => _list.Contains(value ?? new JsonNull());
+        public bool Contains(JsonNode value) => _list.Contains(value ?? JsonNull.Instance);
 
         /// <summary>
         ///   Gets the number of elements contained in the collection.
@@ -268,7 +268,7 @@ namespace System.Text.Json
         /// <param name="item">Item to find.</param>
         /// <returns>The zero-based starting index of the search. 0 (zero) is valid in an empty collection.</returns>
         /// <remarks>Null value is allowed and will be converted to the <see cref="JsonNull"/> instance.</remarks>
-        public int IndexOf(JsonNode item) => _list.IndexOf(item ?? new JsonNull());
+        public int IndexOf(JsonNode item) => _list.IndexOf(item ?? JsonNull.Instance);
 
         /// <summary>
         ///   Returns the zero-based index of the last occurrence of a specified item in the collection.
@@ -276,7 +276,7 @@ namespace System.Text.Json
         /// <param name="item">Item to find.</param>
         /// <returns>The zero-based starting index of the search. 0 (zero) is valid in an empty collection.</returns>
         /// <remarks>Null value is allowed and will be converted to the <see cref="JsonNull"/> instance.</remarks>
-        public int LastIndexOf(JsonNode item) => _list.LastIndexOf(item ?? new JsonNull());
+        public int LastIndexOf(JsonNode item) => _list.LastIndexOf(item ?? JsonNull.Instance);
 
         /// <summary>
         ///   Removes all elements from the JSON array.
@@ -301,7 +301,7 @@ namespace System.Text.Json
         public bool Remove(JsonNode item)
         {
             _version++;
-            return _list.Remove(item ?? new JsonNull());
+            return _list.Remove(item ?? JsonNull.Instance);
         }
 
         /// <summary>
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.RecursionStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.RecursionStackFrame.cs
new file mode 100644 (file)
index 0000000..ba0eaba
--- /dev/null
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Text.Json
+{
+    /// <summary>
+    ///   The base class that represents a single node within a mutable JSON document.
+    /// </summary>
+    public abstract partial class JsonNode
+    {
+        private readonly struct RecursionStackFrame
+        {
+            public string PropertyName { get; }
+            public JsonNode PropertyValue { get; }
+            public JsonValueKind ValueKind { get; } // to retrieve ValueKind when PropertyValue is null
+
+            public RecursionStackFrame(string propertyName, JsonNode propertyValue, JsonValueKind valueKind)
+            {
+                PropertyName = propertyName;
+                PropertyValue = propertyValue;
+                ValueKind = valueKind;
+            }
+
+            public RecursionStackFrame(string propertyName, JsonNode propertyValue) : this(propertyName, propertyValue, propertyValue.ValueKind)
+            {
+            }
+        }
+    }
+}
index c606fa0..5bbfc79 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Buffers;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
@@ -101,7 +102,7 @@ namespace System.Text.Json
                         AddToParent(new KeyValuePair<string, JsonNode>(currentPair.Key, jsonBooleanFalse), ref currentNodes, ref toReturn);
                         break;
                     case JsonValueKind.Null:
-                        var jsonNull = new JsonNull();
+                        var jsonNull = JsonNull.Instance;
                         AddToParent(new KeyValuePair<string, JsonNode>(currentPair.Key, jsonNull), ref currentNodes, ref toReturn);
                         break;
                     default:
@@ -209,7 +210,7 @@ namespace System.Text.Json
                         AddNewPair(new JsonBoolean(false));
                         break;
                     case JsonTokenType.Null:
-                        AddNewPair(new JsonNull());
+                        AddNewPair(JsonNull.Instance);
                         break;
                 }
             }
@@ -219,9 +220,9 @@ namespace System.Text.Json
         }
 
         /// <summary>
-        ///   Writes this instance to provided writer.
+        ///   Writes this instance to the provided writer.
         /// </summary>
-        /// <param name="writer">Writer to wrtire this instance to.</param>
+        /// <param name="writer">Writer to write this instance to.</param>
         public void WriteTo(Utf8JsonWriter writer)
         {
             var recursionStack = new Stack<RecursionStackFrame>();
@@ -240,7 +241,7 @@ namespace System.Text.Json
                     {
                         writer.WriteEndObject();
                     }
-                    if (currentFrame.ValueKind == JsonValueKind.Array)
+                    else if (currentFrame.ValueKind == JsonValueKind.Array)
                     {
                         writer.WriteEndArray();
                     }
@@ -292,8 +293,6 @@ namespace System.Text.Json
                         writer.WriteNullValue();
                         break;
                 }
-
-                writer.Flush();
             }
 
             writer.Flush();
@@ -305,12 +304,12 @@ namespace System.Text.Json
         /// <returns>JSON representation of current instance.</returns>
         public string ToJsonString()
         {
-            var stream = new MemoryStream();
-            using (var writer = new Utf8JsonWriter(stream))
+            var output = new ArrayBufferWriter<byte>();
+            using (var writer = new Utf8JsonWriter(output))
             {
                 WriteTo(writer);
-                return JsonHelpers.Utf8GetString(stream.ToArray());
             }
+            return JsonHelpers.Utf8GetString(output.WrittenSpan);
         }
     }
 }
index c29e8dd..90f8f55 100644 (file)
@@ -53,25 +53,9 @@ namespace System.Text.Json
             }
             else
             {
-                toReturn = nodePair.Value;
-            }
-        }
-
-        private struct RecursionStackFrame
-        {
-            public string PropertyName { get; set; }
-            public JsonNode PropertyValue { get; set; }
-            public JsonValueKind ValueKind { get; set; } // to retrieve ValueKind when PropertyValue is null
+                // We are at the top level, so adding node to parent means setting it as returned one
 
-            public RecursionStackFrame(string propertyName, JsonNode propertyValue, JsonValueKind valueKind)
-            {
-                PropertyName = propertyName;
-                PropertyValue = propertyValue;
-                ValueKind = valueKind;
-            }
-
-            public RecursionStackFrame(string propertyName, JsonNode propertyValue) : this(propertyName, propertyValue, propertyValue.ValueKind)
-            {
+                toReturn = nodePair.Value;
             }
         }
     }
index b37205a..3d2a50d 100644 (file)
@@ -26,11 +26,12 @@ namespace System.Text.Json
         /// <summary>
         ///   Gets the <see cref="JsonNode"/> represented by <paramref name="jsonElement"/>.
         ///   Operations performed on the returned <see cref="JsonNode"/> will modify the <paramref name="jsonElement"/>.
+        ///   See also: <seealso cref="JsonElement.IsImmutable"/>.
         /// </summary>
         /// <param name="jsonElement"><see cref="JsonElement"/> to get the <see cref="JsonNode"/> from.</param>
         /// <returns><see cref="JsonNode"/> represented by <paramref name="jsonElement"/>.</returns>
         /// <exception cref="ArgumentException">
-        ///   Provided <see cref="JsonElement"/> was not build from <see cref="JsonNode"/>.
+        ///   Provided <see cref="JsonElement"/> was not built from <see cref="JsonNode"/>.
         /// </exception>
         public static JsonNode GetNode(JsonElement jsonElement) => !jsonElement.IsImmutable ? (JsonNode)jsonElement._parent : throw new ArgumentException(SR.NotNodeJsonElementParent);
 
@@ -67,11 +68,14 @@ namespace System.Text.Json
         ///   Converts a <see cref="string"/> to a <see cref="JsonString"/>.
         /// </summary>
         /// <param name="value">The value to convert.</param>
+        /// <remarks>
+        ///   Null value is accepted and will be interpreted as <see cref="JsonNull"/>.
+        /// </remarks>
         public static implicit operator JsonNode(string value)
         {
             if (value == null)
             {
-                return new JsonNull();
+                return JsonNull.Instance;
             }
 
             return new JsonString(value);
@@ -131,7 +135,7 @@ namespace System.Text.Json
         /// <param name="value">The value to convert.</param>
         public static implicit operator JsonNode(float value)
         {
-            if (float.IsInfinity(value) || float.IsNaN(value))
+            if (!JsonHelpers.IsFinite(value))
             {
                 return new JsonString(value.ToString());
             }
@@ -145,7 +149,7 @@ namespace System.Text.Json
         /// <param name="value">The value to convert.</param>
         public static implicit operator JsonNode(double value)
         {
-            if (double.IsInfinity(value) || double.IsNaN(value))
+            if (!JsonHelpers.IsFinite(value))
             {
                 return new JsonString(value.ToString());
             }
index 82b8e73..472051a 100644 (file)
@@ -14,6 +14,8 @@ namespace System.Text.Json
         /// </summary>
         public JsonNull() { }
 
+        internal static JsonNull Instance { get; } = new JsonNull();
+
         /// <summary>
         ///   Converts the null value to the string in JSON format.
         /// </summary>
index 5ead7c9..fbc9531 100644 (file)
@@ -52,7 +52,7 @@ namespace System.Text.Json
 
                 if (_dictionary.ContainsKey(propertyName))
                 {
-                    _dictionary[propertyName].Value = value ?? new JsonNull();
+                    _dictionary[propertyName].Value = value ?? JsonNull.Instance;
                 }
                 else
                 {
@@ -96,15 +96,17 @@ namespace System.Text.Json
                 throw new ArgumentException(SR.Format(SR.JsonObjectDuplicateKey, propertyName));
             }
 
+            JsonNode valueOrJsonNull = propertyValue ?? JsonNull.Instance;
+
             // Add property to linked list:
             if (_last == null)
             {
-                _last = new JsonObjectProperty(propertyName, propertyValue ?? new JsonNull(), null, null);
+                _last = new JsonObjectProperty(propertyName, valueOrJsonNull, null, null);
                 _first = _last;
             }
             else
             {
-                var newJsonObjectProperty = new JsonObjectProperty(propertyName, propertyValue ?? new JsonNull(), _last, null);
+                var newJsonObjectProperty = new JsonObjectProperty(propertyName, valueOrJsonNull, _last, null);
                 _last.Next = newJsonObjectProperty;
                 _last = newJsonObjectProperty;
             }
@@ -123,7 +125,7 @@ namespace System.Text.Json
         ///   Provided collection contains duplicates.
         /// </exception>
         /// <exception cref="ArgumentNullException">
-        ///   Some of property names are null.
+        ///   Some of the property names are null.
         /// </exception>
         public void AddRange(IEnumerable<KeyValuePair<string, JsonNode>> jsonProperties)
         {
@@ -136,7 +138,7 @@ namespace System.Text.Json
         /// <summary>
         ///   Removes the property with the specified name.
         /// </summary>
-        /// <param name="propertyName">>Name of a property to remove.</param>
+        /// <param name="propertyName">Name of the property to remove.</param>
         /// <returns>
         ///   <see langword="true"/> if the property is successfully found in a JSON object and removed,
         ///   <see langword="false"/> otherwise.
@@ -176,8 +178,8 @@ namespace System.Text.Json
         /// <summary>
         ///   Removes the property with the specified name.
         /// </summary>
-        /// <param name="propertyName">>Name of a property to remove.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
+        /// <param name="propertyName">Name of the property to remove.</param>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
         /// <returns>
         ///   <see langword="true"/> if the property is successfully found in a JSON object and removed,
         ///   <see langword="false"/> otherwise.
@@ -185,8 +187,16 @@ namespace System.Text.Json
         /// <exception cref="ArgumentNullException">
         ///   Provided property name is null.
         /// </exception>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="Remove(string)"/>.
+        /// </remarks>
         public bool Remove(string propertyName, StringComparison stringComparison)
         {
+            if (stringComparison == StringComparison.Ordinal)
+            {
+                return Remove(propertyName);
+            }
+
             if (propertyName == null)
             {
                 throw new ArgumentNullException(nameof(propertyName));
@@ -246,19 +256,27 @@ namespace System.Text.Json
         public bool ContainsProperty(string propertyName) => propertyName != null ? _dictionary.ContainsKey(propertyName) : throw new ArgumentNullException(nameof(propertyName));
 
         /// <summary>
-        ///   Determines whether a property is in a JSON object.
+        ///   Determines whether a property is in this JSON object.
         /// </summary>
         /// <param name="propertyName">Name of the property to check.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
         /// <returns>
-        ///   <see langword="true"/> if the property is successfully found in a JSON object,
+        ///   <see langword="true"/> if the property is successfully found in the JSON object,
         ///   <see langword="false"/> otherwise.
         /// </returns>
         /// <exception cref="ArgumentNullException">
         ///   Provided property name is null.
         /// </exception>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="ContainsProperty(string)"/>.
+        /// </remarks>
         public bool ContainsProperty(string propertyName, StringComparison stringComparison)
         {
+            if (stringComparison == StringComparison.Ordinal)
+            {
+                return ContainsProperty(propertyName);
+            }
+
             foreach (KeyValuePair<string, JsonNode> property in this)
             {
                 if (string.Equals(property.Key, propertyName, stringComparison))
@@ -274,9 +292,9 @@ namespace System.Text.Json
         ///   Returns the value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <returns>Value of the property with the specified name.</returns>
+        /// <returns>The JSON value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON object.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
         public JsonNode GetPropertyValue(string propertyName)
         {
@@ -292,11 +310,14 @@ namespace System.Text.Json
         ///   Returns the value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <returns>Value of the property with the specified name.</returns>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <returns>The JSON value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON object.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="GetPropertyValue(string)"/>.
+        /// </remarks>
         public JsonNode GetPropertyValue(string propertyName, StringComparison stringComparison)
         {
             if (!TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode))
@@ -311,13 +332,13 @@ namespace System.Text.Json
         ///   Returns the value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="jsonNode">Value of the property with specified name.</param>
+        /// <param name="jsonNode">The JSON value of the property with the specified name.</param>
         /// <returns>
-        ///  <see langword="true"/> if property with specified name was found;
+        ///  <see langword="true"/> if a property with the specified name was found;
         ///  otherwise, <see langword="false"/>
         /// </returns>
         /// <remarks>
-        ///   When returns <see langword="false"/>, the value of <paramref name="jsonNode"/> is meaningless.
+        ///   When this method returns <see langword="false"/>, the value of <paramref name="jsonNode"/> is meaningless.
         /// </remarks>
         public bool TryGetPropertyValue(string propertyName, out JsonNode jsonNode)
         {
@@ -335,17 +356,25 @@ namespace System.Text.Json
         ///   Returns the value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <param name="jsonNode">Value of the property with specified name.</param>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <param name="jsonNode">The JSON value of the property with the specified name.</param>
         /// <returns>
         ///  <see langword="true"/> if property with specified name was found;
         ///  otherwise, <see langword="false"/>
         /// </returns>
         /// <remarks>
-        ///   When returns <see langword="false"/>, the value of <paramref name="jsonNode"/> is meaningless.
+        ///   When this method returns <see langword="false"/>, the value of <paramref name="jsonNode"/> is meaningless.
+        /// </remarks>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="TryGetPropertyValue(string, out JsonNode)"/>.
         /// </remarks>
         public bool TryGetPropertyValue(string propertyName, StringComparison stringComparison, out JsonNode jsonNode)
         {
+            if (stringComparison == StringComparison.Ordinal)
+            {
+                return TryGetPropertyValue(propertyName, out jsonNode);
+            }
+
             foreach (KeyValuePair<string, JsonNode> property in this)
             {
                 if (string.Equals(property.Key, propertyName, stringComparison))
@@ -363,12 +392,12 @@ namespace System.Text.Json
         ///   Returns the JSON object value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <returns>JSON objectvalue of a property with the specified name.</returns>
+        /// <returns>The JSON object value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON object.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
         /// <exception cref="ArgumentException">
-        ///   Property with specified name is not a JSON object.
+        ///   The property with the specified name is not a JSON object.
         /// </exception>
         public JsonObject GetJsonObjectPropertyValue(string propertyName)
         {
@@ -384,14 +413,17 @@ namespace System.Text.Json
         ///   Returns the JSON object value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <returns>JSON objectvalue of a property with the specified name.</returns>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <returns>The JSON object value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON object.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
         /// <exception cref="ArgumentException">
-        ///   Property with specified name is not a JSON object.
+        ///   The property with the specified name is not a JSON object.
         /// </exception>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="GetJsonObjectPropertyValue(string)"/>.
+        /// </remarks>
         public JsonObject GetJsonObjectPropertyValue(string propertyName, StringComparison stringComparison)
         {
             if (GetPropertyValue(propertyName, stringComparison) is JsonObject jsonObject)
@@ -406,7 +438,7 @@ namespace System.Text.Json
         ///   Returns the JSON object value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="jsonObject">JSON object value of the property with specified name.</param>
+        /// <param name="jsonObject">The JSON object value of the property with the specified name.</param>
         /// <returns>
         ///  <see langword="true"/> if JSON object property with specified name was found;
         ///  otherwise, <see langword="false"/>
@@ -427,12 +459,15 @@ namespace System.Text.Json
         ///   Returns the JSON object value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <param name="jsonObject">JSON object value of the property with specified name.</param>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <param name="jsonObject">The JSON object value of the property with the specified name.</param>
         /// <returns>
         ///  <see langword="true"/> if JSON object property with specified name was found;
         ///  otherwise, <see langword="false"/>
         /// </returns>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="TryGetJsonObjectPropertyValue(string, out JsonObject)"/>.
+        /// </remarks>
         public bool TryGetJsonObjectPropertyValue(string propertyName, StringComparison stringComparison, out JsonObject jsonObject)
         {
             if (TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode))
@@ -449,12 +484,12 @@ namespace System.Text.Json
         ///   Returns the JSON array value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <returns>JSON objectvalue of a property with the specified name.</returns>
+        /// <returns>The JSON array value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON array.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
         /// <exception cref="ArgumentException">
-        ///   Property with specified name is not a JSON array.
+        ///   The property with the specified name is not a JSON array.
         /// </exception>
         public JsonArray GetJsonArrayPropertyValue(string propertyName)
         {
@@ -470,14 +505,17 @@ namespace System.Text.Json
         ///   Returns the JSON array value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <returns>JSON objectvalue of a property with the specified name.</returns>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <returns>The JSON array value of the property with the specified name.</returns>
         /// <exception cref="KeyNotFoundException">
-        ///   Property with specified name is not found in JSON array.
+        ///   A property with the specified name is not found in this JSON object.
         /// </exception>
         /// <exception cref="ArgumentException">
-        ///   Property with specified name is not a JSON array.
+        ///   The property with the specified name is not a JSON array.
         /// </exception>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="GetJsonArrayPropertyValue(string)"/>.
+        /// </remarks>
         public JsonArray GetJsonArrayPropertyValue(string propertyName, StringComparison stringComparison)
         {
             if (GetPropertyValue(propertyName, stringComparison) is JsonArray jsonArray)
@@ -492,7 +530,7 @@ namespace System.Text.Json
         ///   Returns the JSON array value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="jsonArray">JSON array value of the property with specified name.</param>
+        /// <param name="jsonArray">The JSON array value of the property with the specified name.</param>
         /// <returns>
         ///  <see langword="true"/> if JSON array property with specified name was found;
         ///  otherwise, <see langword="false"/>
@@ -513,12 +551,15 @@ namespace System.Text.Json
         ///   Returns the JSON array value of a property with the specified name.
         /// </summary>
         /// <param name="propertyName">Name of the property to return.</param>
-        /// <param name="stringComparison">The culture and case to be used when comparing string value.</param>
-        /// <param name="jsonArray">JSON array value of the property with specified name.</param>
+        /// <param name="stringComparison">The culture, case, and sort rules to be used when comparing string value.</param>
+        /// <param name="jsonArray">The JSON array value of the property with the specified name.</param>
         /// <returns>
         ///  <see langword="true"/> if JSON array property with specified name was found;
         ///  otherwise, <see langword="false"/>
         /// </returns>
+        /// <remarks>
+        ///   If <paramref name="stringComparison"/> is set to <see cref="StringComparison.Ordinal"/>, calling this method is equivalent to calling <see cref="TryGetJsonArrayPropertyValue(string, out JsonArray)"/>.
+        /// </remarks>
         public bool TryGetJsonArrayPropertyValue(string propertyName, StringComparison stringComparison, out JsonArray jsonArray)
         {
             if (TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode))
index d3be981..996f7a9 100644 (file)
@@ -55,11 +55,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static void ValidateDouble(double value)
         {
-#if BUILDING_INBOX_LIBRARY
-            if (!double.IsFinite(value))
-#else
-            if (double.IsNaN(value) || double.IsInfinity(value))
-#endif
+            if (!JsonHelpers.IsFinite(value))
             {
                 ThrowHelper.ThrowArgumentException_ValueNotSupported();
             }
@@ -68,11 +64,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static void ValidateSingle(float value)
         {
-#if BUILDING_INBOX_LIBRARY
-            if (!float.IsFinite(value))
-#else
-            if (float.IsNaN(value) || float.IsInfinity(value))
-#endif
+            if (!JsonHelpers.IsFinite(value))
             {
                 ThrowHelper.ThrowArgumentException_ValueNotSupported();
             }
index d9be2d4..2a398a7 100644 (file)
@@ -430,20 +430,22 @@ namespace System.Text.Json.Tests
         [Fact]
         public static void TestHeterogeneousArray()
         {
-            var mixedTypesArray = new JsonArray { 1, "value", true };
+            var mixedTypesArray = new JsonArray { 1, "value", true, null, 2.3, new JsonObject() };
 
             Assert.Equal(1, mixedTypesArray[0]);
             Assert.Equal("value", mixedTypesArray[1]);
             Assert.Equal(true, mixedTypesArray[2]);
+            Assert.IsType<JsonNull>(mixedTypesArray[3]);
+            Assert.Equal(2.3, mixedTypesArray[4]);
+            Assert.IsType<JsonObject>(mixedTypesArray[5]);
 
-            mixedTypesArray.Add(17);
+            mixedTypesArray.Add(false);
             mixedTypesArray.Insert(4, "another");
             mixedTypesArray.Add(new JsonNull());
 
-            Assert.Equal(17, mixedTypesArray[3]);
+            Assert.Equal(false, mixedTypesArray[7]);
             Assert.Equal("another", mixedTypesArray[4]);
-            Assert.IsType<JsonNull>(mixedTypesArray[5]);
-
+            Assert.IsType<JsonNull>(mixedTypesArray[8]);
         }
 
         [Fact]
@@ -520,16 +522,17 @@ namespace System.Text.Json.Tests
 
             Assert.Null(jsonArrayEnumerator.Current);
 
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal(1, jsonArrayEnumerator.Current);
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal("value", jsonArrayEnumerator.Current);
+            Assert.False(jsonArrayEnumerator.MoveNext());
 
             jsonArrayEnumerator.Reset();
 
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal(1, jsonArrayEnumerator.Current);
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal("value", jsonArrayEnumerator.Current);
 
             // Test non-generic IEnumerator:
@@ -539,15 +542,17 @@ namespace System.Text.Json.Tests
 
             jsonArrayEnumerator2.MoveNext();
             Assert.Equal((JsonNumber)1, jsonArrayEnumerator2.Current);
-            jsonArrayEnumerator2.MoveNext();
+            Assert.True(jsonArrayEnumerator2.MoveNext());
             Assert.Equal((JsonString)"value", jsonArrayEnumerator2.Current);
+            Assert.False(jsonArrayEnumerator2.MoveNext());
 
             jsonArrayEnumerator2.Reset();
 
-            jsonArrayEnumerator2.MoveNext();
+            Assert.True(jsonArrayEnumerator2.MoveNext());
             Assert.Equal((JsonNumber)1, jsonArrayEnumerator2.Current);
-            jsonArrayEnumerator2.MoveNext();
+            Assert.True(jsonArrayEnumerator2.MoveNext());
             Assert.Equal((JsonString)"value", jsonArrayEnumerator2.Current);
+            Assert.False(jsonArrayEnumerator2.MoveNext());
         }
 
         [Fact]
@@ -558,9 +563,9 @@ namespace System.Text.Json.Tests
             
             Assert.Null(jsonArrayEnumerator.Current);
 
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal((JsonNumber)1, jsonArrayEnumerator.Current);
-            jsonArrayEnumerator.MoveNext();
+            Assert.True(jsonArrayEnumerator.MoveNext());
             Assert.Equal((JsonString)"value", jsonArrayEnumerator.Current);
         }
 
index aeca65a..80171a6 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Buffers;
 using System.Globalization;
 using System.IO;
 using Xunit;
@@ -100,7 +101,6 @@ namespace System.Text.Json.Tests
             jsonObject["2"] = 4;
             Assert.Throws<InvalidOperationException>(() => objectEnumerator.MoveNext());
 
-
             JsonElement notObject = new JsonArray().AsJsonElement();
             Assert.Throws<InvalidOperationException>(() => notObject.EnumerateObject());
         }
@@ -258,13 +258,13 @@ namespace System.Text.Json.Tests
                 ["array"] = new JsonArray() { 1, 2 }
             };
 
-            var stream = new MemoryStream();
-            using (var writer = new Utf8JsonWriter(stream))
+            var output = new ArrayBufferWriter<byte>();
+            using (var writer = new Utf8JsonWriter(output))
             {
                 jsonObject.AsJsonElement().WriteTo(writer);
-                string result = Encoding.UTF8.GetString(stream.ToArray());
-                Assert.Equal(jsonObject.ToJsonString(), result);
             }
+
+            JsonTestHelper.AssertContents(jsonObject.ToJsonString(), output);
         }
 
         [Fact]
@@ -272,7 +272,10 @@ namespace System.Text.Json.Tests
         {
             var jsonObject = new JsonObject()
             {
-                ["text"] = "value",
+                ["text"] = "za\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 ja\u017A\u0144",
+                ["text2"] = ">><++>>>\">>\\>>&>>>\u6f22\u5B57>>>",
+                ["text3"] = "..\t\r\n...\"quote\"",
+                ["<<.\t\r\n.\"quote\".\u017C\u00F3>>"] = "",
                 ["boolean"] = true,
                 ["null"] = null,
                 ["array"] = new JsonArray() { 1, 2 }
index 4f654e9..087ba9f 100644 (file)
@@ -54,7 +54,7 @@ namespace System.Text.Json.Tests
                 {
                     jsonNode.WriteTo(writer);
                     stream.Seek(0, SeekOrigin.Begin);
-                    var jsonDocument = JsonDocument.Parse(stream, s_options);
+                    JsonDocument jsonDocument = JsonDocument.Parse(stream, s_options);
                     return jsonDocument;
                 }
             }
index cb04733..e909124 100644 (file)
@@ -90,7 +90,7 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
-        public static void TestParseDoesNotOverflow()
+        public static void TestParseDoesNotStackOverflow()
         {
             var builder = new StringBuilder();
             for (int i = 0; i < 2_000; i++)
@@ -105,10 +105,29 @@ namespace System.Text.Json.Tests
 
             var options = new JsonNodeOptions { MaxDepth = 5_000 };
             JsonNode jsonNode = JsonNode.Parse(builder.ToString(), options);
+
+            Assert.Equal(1, ((JsonArray)jsonNode).Count);
         }
 
         [Fact]
-        public static void TestDeepCopyDoesNotOverflow()
+        public static void TestParseFailsWhenExceedsMaxDepth()
+        {
+            var builder = new StringBuilder();
+            for (int i = 0; i < 100; i++)
+            {
+                builder.Append("[");
+            }
+
+            for (int i = 0; i < 100; i++)
+            {
+                builder.Append("]");
+            }
+
+            Assert.ThrowsAny<JsonException>(() => JsonNode.Parse(builder.ToString()));
+        }
+
+        [Fact]
+        public static void TestDeepCopyDoesNotStackOverflow()
         {
             var builder = new StringBuilder();
             for (int i = 0; i < 2_000; i++)
@@ -124,7 +143,8 @@ namespace System.Text.Json.Tests
             var options = new JsonDocumentOptions { MaxDepth = 5_000 };
             using (JsonDocument dom = JsonDocument.Parse(builder.ToString(), options))
             {
-                JsonNode node = JsonNode.DeepCopy(dom.RootElement);
+                JsonNode jsonNode = JsonNode.DeepCopy(dom.RootElement);
+                Assert.Equal(1, ((JsonArray)jsonNode).Count);
             }
         }
 
@@ -198,5 +218,66 @@ namespace System.Text.Json.Tests
 
             Assert.Throws<ArgumentException>(() => JsonNode.Parse(stringWithDuplicates, new JsonNodeOptions() { DuplicatePropertyNameHandling = DuplicatePropertyNameHandlingStrategy.Error }));
         }
+
+        [Theory]
+        [InlineData(DuplicatePropertyNameHandlingStrategy.Replace)]
+        [InlineData(DuplicatePropertyNameHandlingStrategy.Ignore)]
+        [InlineData(DuplicatePropertyNameHandlingStrategy.Error)]
+        public static void TestParseWithNestedDuplicates(DuplicatePropertyNameHandlingStrategy duplicatePropertyNameHandling)
+        {
+            var options = new JsonNodeOptions
+            {
+                DuplicatePropertyNameHandling = duplicatePropertyNameHandling
+            };
+
+            var stringWithDuplicates = @"
+            {
+                ""property"": ""first value"",
+                ""nested object"": 
+                {
+                    ""property"": ""duplicate value"",
+                    ""more nested object"": 
+                    {
+                        ""property"": ""last duplicate value""
+                    }
+                },
+                ""array"" : [ ""property"" ]
+            }";
+
+            var jsonObject = (JsonObject)JsonNode.Parse(stringWithDuplicates, options);
+            Assert.Equal(3, jsonObject.GetPropertyNames().Count);
+            Assert.Equal(3, jsonObject.GetPropertyValues().Count);
+            Assert.Equal("first value", jsonObject["property"]);
+            CheckNestedValues(jsonObject);
+
+            jsonObject.Remove("property");
+
+            Assert.Equal(2, jsonObject.GetPropertyNames().Count);
+            Assert.Equal(2, jsonObject.GetPropertyValues().Count);
+            CheckNestedValues(jsonObject);
+
+            void CheckNestedValues(JsonObject jsonObject)
+            {
+                var nestedObject = (JsonObject)jsonObject["nested object"];
+                Assert.Equal(2, nestedObject.GetPropertyNames().Count);
+                Assert.Equal(2, nestedObject.GetPropertyValues().Count);
+                Assert.Equal("duplicate value", nestedObject["property"]);
+
+                var moreNestedObject = (JsonObject)nestedObject["more nested object"];
+                Assert.Equal(1, moreNestedObject.GetPropertyNames().Count);
+                Assert.Equal(1, moreNestedObject.GetPropertyValues().Count);
+                Assert.Equal("last duplicate value", moreNestedObject["property"]);
+
+                var array = (JsonArray)jsonObject["array"];
+                Assert.Equal(1, array.Count);
+                Assert.True(array.Contains("property"));
+            }
+
+            var nestedObject = (JsonObject)jsonObject["nested object"];
+            nestedObject.Add("array", new JsonNumber());
+
+            Assert.IsType<JsonArray>(jsonObject["array"]);
+            Assert.IsType<JsonNumber>(nestedObject["array"]);
+        }
     }
 }
index 0e9ca76..bc0891e 100644 (file)
@@ -82,7 +82,7 @@ namespace System.Text.Json.Tests
             IEquatable<JsonNull> jsonNullIEquatable = new JsonNull();
             Assert.Equal(jsonNull.GetHashCode(), jsonNullIEquatable.GetHashCode());
 
-            object jsonNullCopy= jsonNull;
+            object jsonNullCopy = jsonNull;
             object jsonNullObject = new JsonNull();
             Assert.Equal(jsonNull.GetHashCode(), jsonNullCopy.GetHashCode());
             Assert.Equal(jsonNull.GetHashCode(), jsonNullObject.GetHashCode());
index 813c875..eb56984 100644 (file)
@@ -344,6 +344,19 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
+        public static void TestGetAndTryGetNullProperty()
+        {
+            var jsonObject = new JsonObject
+            {
+                { "null", null }
+            };
+
+            Assert.IsType<JsonNull>(jsonObject.GetPropertyValue("null"));
+            Assert.True(jsonObject.TryGetPropertyValue("null", out JsonNode node));
+            Assert.IsType<JsonNull>(node);
+        }
+
+        [Fact]
         public static void TestContains()
         {
             var person = new JsonObject
@@ -568,6 +581,23 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
+        public static void TestGetJsonObjectPropertyValueOnDifferentLevelFails()
+        {
+            var jsonObject = new JsonObject()
+            {
+                {
+                    "inner object", new JsonObject()
+                    {
+                        {  "object", new JsonObject() }
+                    }
+                }
+            };
+
+            Assert.Equal(1, jsonObject.GetJsonObjectPropertyValue("inner object").GetPropertyNames().Count);
+            Assert.Throws<KeyNotFoundException>(() => jsonObject.GetJsonObjectPropertyValue("object"));
+        }
+
+        [Fact]
         public static void TestTryGetObjectPropertyFails()
         {
             var jsonObject = new JsonObject()
@@ -597,6 +627,22 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
+        public static void TestGetJsonArrayPropertyValueOnDifferentLevelFails()
+        {
+            var jsonObject = new JsonObject()
+            {
+                {
+                    "inner object", new JsonObject()
+                    {
+                        {  "array", new JsonArray() { 1, 2 } }
+                    }
+                }
+            };
+
+            Assert.Throws<KeyNotFoundException>(() => jsonObject.GetJsonArrayPropertyValue("array"));
+        }
+
+        [Fact]
         public static void TestTryGetArrayPropertyFails()
         {
             var jsonObject = new JsonObject()
@@ -652,7 +698,7 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
-        public static void TestStringComparisonEnum()
+        public static void TestStringComparisonEnumGetPropertyRemoveContains()
         {
             var jsonObject = new JsonObject()
             {
@@ -668,30 +714,56 @@ namespace System.Text.Json.Tests
             Assert.False(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", out JsonNode jsonNode));
             Assert.Null(jsonNode);
             Assert.Throws<KeyNotFoundException>(() => jsonObject.GetPropertyValue("ENCYCLOPAEDIA"));
-            jsonObject.Remove("ENCYCLOPAEDIA");
+            Assert.False(jsonObject.Remove("ENCYCLOPAEDIA"));
             Assert.Equal(4, jsonObject.Count());
 
-            Assert.False(jsonObject.ContainsProperty("ENCYCLOPAEDIA", StringComparison.CurrentCulture));
-            Assert.False(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", StringComparison.CurrentCulture, out jsonNode));
-            Assert.Null(jsonNode);
-            Assert.Throws<KeyNotFoundException>(() => jsonObject.GetPropertyValue("ENCYCLOPAEDIA", StringComparison.CurrentCulture));
-            jsonObject.Remove("ENCYCLOPAEDIA", StringComparison.CurrentCulture);
-            Assert.Equal(4, jsonObject.Count());
+            Check(jsonObject, StringComparison.CurrentCulture);
+            Check(jsonObject, StringComparison.InvariantCulture);
+            Check(jsonObject, StringComparison.Ordinal);
+
+            CheckIgnoreCase(StringComparison.CurrentCultureIgnoreCase);
+            CheckIgnoreCase(StringComparison.InvariantCultureIgnoreCase);
+            CheckIgnoreCase(StringComparison.OrdinalIgnoreCase);
+
+            void Check(JsonObject jsonObject, StringComparison stringComparison)
+            {
+                Assert.False(jsonObject.ContainsProperty("ENCYCLOPAEDIA", stringComparison));
+                Assert.False(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", stringComparison, out JsonNode jsonNode));
+                Assert.Null(jsonNode);
+                Assert.Throws<KeyNotFoundException>(() => jsonObject.GetPropertyValue("ENCYCLOPAEDIA", stringComparison));
+                Assert.False(jsonObject.Remove("ENCYCLOPAEDIA", stringComparison));
+                Assert.Equal(4, jsonObject.Count());
+            }
 
-            Assert.True(jsonObject.ContainsProperty("ENCYCLOPAEDIA", StringComparison.InvariantCultureIgnoreCase));
-            Assert.True(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", StringComparison.InvariantCultureIgnoreCase, out jsonNode));
-            Assert.Equal("value2", jsonNode);
-            Assert.Equal("value2", jsonObject.GetPropertyValue("ENCYCLOPAEDIA", StringComparison.InvariantCultureIgnoreCase));
-            jsonObject.Remove("ENCYCLOPAEDIA", StringComparison.InvariantCultureIgnoreCase);
-            Assert.Equal(3, jsonObject.Count());
+            void CheckIgnoreCase(StringComparison stringComparison)
+            {
+                var jsonObject = new JsonObject()
+                {
+                    { "not encyclopaedia", "value1" },
+                    { "Encyclopaedia", "value2" },
+                    { "NOT encyclopaedia", "value3" },
+                    { "encyclopaedia", "value4" }
+                };
 
-            IReadOnlyCollection<JsonNode> values = jsonObject.GetPropertyValues();
-            Assert.False(values.Contains("value2"));
-            Assert.True(values.Contains("value1"));
-            Assert.True(values.Contains("value3"));
-            Assert.True(values.Contains("value4"));
+                Assert.True(jsonObject.ContainsProperty("ENCYCLOPAEDIA", stringComparison));
+                Assert.True(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", stringComparison, out jsonNode));
+                Assert.Equal("value2", jsonNode);
+                Assert.Equal("value2", jsonObject.GetPropertyValue("ENCYCLOPAEDIA", stringComparison));
+                Assert.True(jsonObject.Remove("ENCYCLOPAEDIA", stringComparison));
+                Assert.Equal(3, jsonObject.Count());
+
+                IReadOnlyCollection<JsonNode> values = jsonObject.GetPropertyValues();
+                Assert.False(values.Contains("value2"));
+                Assert.True(values.Contains("value1"));
+                Assert.True(values.Contains("value3"));
+                Assert.True(values.Contains("value4"));
+            }
+        }
 
-            jsonObject = new JsonObject()
+        [Fact]
+        public static void TestStringComparisonEnumGetArrayOrObjectProperty()
+        {
+            var jsonObject = new JsonObject()
             {
                 { "object first", new JsonObject() },
                 { "object FIRST", new JsonArray() },
@@ -699,34 +771,73 @@ namespace System.Text.Json.Tests
                 { "array FIRST", new JsonObject() }
             };
 
-            Assert.Equal(0, jsonObject.GetJsonObjectPropertyValue("OBJECT first",
-                StringComparison.InvariantCultureIgnoreCase).GetPropertyNames().Count);
-            Assert.True(jsonObject.TryGetJsonObjectPropertyValue("OBJECT first", StringComparison.InvariantCultureIgnoreCase,
-                out JsonObject objectProperty));
-            Assert.Equal(0, objectProperty.GetPropertyNames().Count);
+            Check(jsonObject, StringComparison.CurrentCulture);
+            Check(jsonObject, StringComparison.InvariantCulture);
+            Check(jsonObject, StringComparison.Ordinal);
 
-            Assert.Throws<ArgumentException>(() =>
-                jsonObject.GetJsonArrayPropertyValue("OBJECT first", StringComparison.InvariantCultureIgnoreCase));
-            Assert.False(jsonObject.TryGetJsonArrayPropertyValue("OBJECT first", StringComparison.InvariantCultureIgnoreCase,
-                out JsonArray arrayProperty));
-            Assert.False(jsonObject.TryGetJsonArrayPropertyValue("something different", StringComparison.InvariantCultureIgnoreCase,
-                out arrayProperty));
-
-            Assert.Equal(0, jsonObject.GetJsonArrayPropertyValue("ARRAY first",
-                StringComparison.InvariantCultureIgnoreCase).Count);
-            Assert.True(jsonObject.TryGetJsonArrayPropertyValue("ARRAY first", StringComparison.InvariantCultureIgnoreCase,
-                out arrayProperty));
-            Assert.Equal(0, arrayProperty.Count);
+            CheckIgnoreCase(jsonObject, StringComparison.CurrentCultureIgnoreCase);
+            CheckIgnoreCase(jsonObject, StringComparison.InvariantCultureIgnoreCase);
+            CheckIgnoreCase(jsonObject, StringComparison.OrdinalIgnoreCase);
 
-            Assert.Throws<ArgumentException>(() =>
-                jsonObject.GetJsonObjectPropertyValue("ARRAY first", StringComparison.InvariantCultureIgnoreCase));
-            Assert.False(jsonObject.TryGetJsonObjectPropertyValue("ARRAY first", StringComparison.InvariantCultureIgnoreCase,
-                out objectProperty));
-            Assert.False(jsonObject.TryGetJsonObjectPropertyValue("something different", StringComparison.InvariantCultureIgnoreCase,
-                out objectProperty));
+            void Check(JsonObject jsonObject, StringComparison stringComparison)
+            {
+                Assert.Equal(0, jsonObject.GetJsonObjectPropertyValue("object first",
+                    stringComparison).GetPropertyNames().Count);
+                Assert.True(jsonObject.TryGetJsonObjectPropertyValue("object first", stringComparison,
+                    out JsonObject objectProperty));
+                Assert.Equal(0, objectProperty.GetPropertyNames().Count);
 
-            Assert.Throws<ArgumentNullException>(() =>
-               jsonObject.Remove(null, StringComparison.InvariantCultureIgnoreCase));
+                Assert.Throws<ArgumentException>(() =>
+                    jsonObject.GetJsonObjectPropertyValue("object FIRST", stringComparison));
+                Assert.False(jsonObject.TryGetJsonObjectPropertyValue("object FIRST", stringComparison,
+                    out objectProperty));
+
+                Assert.Equal(0, jsonObject.GetJsonArrayPropertyValue("array first",
+                   stringComparison).Count);
+                Assert.True(jsonObject.TryGetJsonArrayPropertyValue("array first", stringComparison,
+                    out JsonArray arrayProperty));
+                Assert.Equal(0, arrayProperty.Count);
+
+                Assert.Throws<ArgumentException>(() =>
+                    jsonObject.GetJsonArrayPropertyValue("array FIRST", stringComparison));
+                Assert.False(jsonObject.TryGetJsonArrayPropertyValue("array FIRST", stringComparison,
+                    out arrayProperty));
+
+                Assert.Throws<ArgumentNullException>(() =>
+                   jsonObject.Remove(null, stringComparison));
+            }
+
+            void CheckIgnoreCase(JsonObject jsonObject, StringComparison stringComparison)
+            {
+                Assert.Equal(0, jsonObject.GetJsonObjectPropertyValue("OBJECT first",
+                    stringComparison).GetPropertyNames().Count);
+                Assert.True(jsonObject.TryGetJsonObjectPropertyValue("OBJECT first", stringComparison,
+                    out JsonObject objectProperty));
+                Assert.Equal(0, objectProperty.GetPropertyNames().Count);
+
+                Assert.Throws<ArgumentException>(() =>
+                    jsonObject.GetJsonArrayPropertyValue("OBJECT first", stringComparison));
+                Assert.False(jsonObject.TryGetJsonArrayPropertyValue("OBJECT first", stringComparison,
+                    out JsonArray arrayProperty));
+                Assert.False(jsonObject.TryGetJsonArrayPropertyValue("something different", stringComparison,
+                    out arrayProperty));
+
+                Assert.Equal(0, jsonObject.GetJsonArrayPropertyValue("ARRAY first",
+                   stringComparison).Count);
+                Assert.True(jsonObject.TryGetJsonArrayPropertyValue("ARRAY first", stringComparison,
+                    out arrayProperty));
+                Assert.Equal(0, arrayProperty.Count);
+
+                Assert.Throws<ArgumentException>(() =>
+                    jsonObject.GetJsonObjectPropertyValue("ARRAY first", stringComparison));
+                Assert.False(jsonObject.TryGetJsonObjectPropertyValue("ARRAY first", stringComparison,
+                    out objectProperty));
+                Assert.False(jsonObject.TryGetJsonObjectPropertyValue("something different", stringComparison,
+                    out objectProperty));
+
+                Assert.Throws<ArgumentNullException>(() =>
+                   jsonObject.Remove(null, stringComparison));
+            }
         }
 
         [Fact]
index 0a42feb..b1ade10 100644 (file)
@@ -20,7 +20,7 @@ namespace System.Text.Json.Tests
         [InlineData("value")]
         [InlineData("value with some spaces")]
         [InlineData("     leading spaces")]
-        [InlineData("trailing spaces")]
+        [InlineData("trailing spaces     ")]
         [InlineData("new lines\r\n")]
         [InlineData("tabs\ttabs\t")]
         [InlineData("\\u003e\\u003e\\u003e\\u003e\\u003e")]