提交 36b6c52d 编写于 作者: K Katarzyna Bułat 提交者: Ahson Khan

JSON DOM refactor (dotnet/corefx#41041)

* 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
上级 a0d608dc
......@@ -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
......
......@@ -445,6 +445,6 @@
<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
......@@ -210,7 +210,8 @@
<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" />
......
......@@ -1602,11 +1602,12 @@ public void WriteTo(Utf8JsonWriter writer)
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 @@ public ObjectEnumerator EnumerateObject()
/// 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 @@ public ObjectEnumerator EnumerateObject()
/// <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 @@ public override string ToString()
/// 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()
{
......
......@@ -93,6 +93,24 @@ internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
return false;
#else
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
}
}
......
......@@ -213,7 +213,7 @@ public JsonArray(IEnumerable<decimal> values) : this()
get => _list[idx];
set
{
_list[idx] = value ?? new JsonNull();
_list[idx] = value ?? JsonNull.Instance;
_version++;
}
}
......@@ -225,7 +225,7 @@ public JsonArray(IEnumerable<decimal> values) : this()
/// <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 @@ public void Add(JsonNode value)
/// <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 @@ public void Insert(int index, JsonNode item)
/// <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 @@ public void Insert(int index, JsonNode item)
/// <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 @@ public void Insert(int index, JsonNode item)
/// <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 @@ public void Clear()
public bool Remove(JsonNode item)
{
_version++;
return _list.Remove(item ?? new JsonNull());
return _list.Remove(item ?? JsonNull.Instance);
}
/// <summary>
......
// 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)
{
}
}
}
}
......@@ -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 @@ public static JsonNode DeepCopy(JsonElement jsonElement)
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 @@ void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false)
AddNewPair(new JsonBoolean(false));
break;
case JsonTokenType.Null:
AddNewPair(new JsonNull());
AddNewPair(JsonNull.Instance);
break;
}
}
......@@ -219,9 +220,9 @@ void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false)
}
/// <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 @@ public void WriteTo(Utf8JsonWriter writer)
{
writer.WriteEndObject();
}
if (currentFrame.ValueKind == JsonValueKind.Array)
else if (currentFrame.ValueKind == JsonValueKind.Array)
{
writer.WriteEndArray();
}
......@@ -292,8 +293,6 @@ public void WriteTo(Utf8JsonWriter writer)
writer.WriteNullValue();
break;
}
writer.Flush();
}
writer.Flush();
......@@ -305,12 +304,12 @@ public void WriteTo(Utf8JsonWriter writer)
/// <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);
}
}
}
......@@ -53,25 +53,9 @@ public abstract partial class JsonNode
}
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;
}
}
}
......
......@@ -26,11 +26,12 @@ public abstract partial class JsonNode
/// <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 @@ public static bool TryGetNode(JsonElement jsonElement, out JsonNode jsonNode)
/// 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 @@ public static bool TryGetNode(JsonElement jsonElement, out JsonNode jsonNode)
/// <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 @@ public static bool TryGetNode(JsonElement jsonElement, out JsonNode jsonNode)
/// <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());
}
......
......@@ -14,6 +14,8 @@ public sealed class JsonNull : JsonNode, IEquatable<JsonNull>
/// </summary>
public JsonNull() { }
internal static JsonNull Instance { get; } = new JsonNull();
/// <summary>
/// Converts the null value to the string in JSON format.
/// </summary>
......
......@@ -52,7 +52,7 @@ public JsonObject(IEnumerable<KeyValuePair<string, JsonNode>> jsonProperties)
if (_dictionary.ContainsKey(propertyName))
{
_dictionary[propertyName].Value = value ?? new JsonNull();
_dictionary[propertyName].Value = value ?? JsonNull.Instance;
}
else
{
......@@ -96,15 +96,17 @@ public void Add(string propertyName, JsonNode propertyValue)
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 @@ public void Add(string propertyName, JsonNode propertyValue)
/// 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 @@ public void AddRange(IEnumerable<KeyValuePair<string, JsonNode>> jsonProperties)
/// <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 @@ public bool Remove(string propertyName)
/// <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 @@ public bool Remove(string propertyName)
/// <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 @@ private void AdjustLinkedListPointers(JsonObjectProperty propertyToRemove)
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 @@ public bool ContainsProperty(string propertyName, StringComparison stringCompari
/// 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 @@ public JsonNode GetPropertyValue(string propertyName)
/// 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 @@ public JsonNode GetPropertyValue(string propertyName, StringComparison stringCom
/// 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 @@ public bool TryGetPropertyValue(string propertyName, out JsonNode jsonNode)
/// 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 @@ public bool TryGetPropertyValue(string propertyName, StringComparison stringComp
/// 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 @@ public JsonObject GetJsonObjectPropertyValue(string propertyName)
/// 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 @@ public JsonObject GetJsonObjectPropertyValue(string propertyName, StringComparis
/// 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 @@ public bool TryGetJsonObjectPropertyValue(string propertyName, out JsonObject js
/// 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 @@ public bool TryGetJsonObjectPropertyValue(string propertyName, StringComparison
/// 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 @@ public JsonArray GetJsonArrayPropertyValue(string propertyName)
/// 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 @@ public JsonArray GetJsonArrayPropertyValue(string propertyName, StringComparison
/// 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 @@ public bool TryGetJsonArrayPropertyValue(string propertyName, out JsonArray 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))
......
......@@ -55,11 +55,7 @@ public static void ValidateBytes(ReadOnlySpan<byte> bytes)
[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 @@ public static void ValidateDouble(double value)
[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();
}
......
......@@ -430,20 +430,22 @@ public static void TestInsert()
[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 @@ public static void TestJsonArrayIEnumerator()
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 @@ public static void TestJsonArrayIEnumerator()
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 @@ public static void TestGetJsonArrayIEnumerable()
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);
}
......
......@@ -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 @@ public static void TestObjectEnumerator()
jsonObject["2"] = 4;
Assert.Throws<InvalidOperationException>(() => objectEnumerator.MoveNext());
JsonElement notObject = new JsonArray().AsJsonElement();
Assert.Throws<InvalidOperationException>(() => notObject.EnumerateObject());
}
......@@ -258,13 +258,13 @@ public static void TestWriteTo()
["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 @@ public static void TestToString()
{
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 }
......
......@@ -54,7 +54,7 @@ protected override JsonDocument PrepareDocument(string jsonIn)
{
jsonNode.WriteTo(writer);
stream.Seek(0, SeekOrigin.Begin);
var jsonDocument = JsonDocument.Parse(stream, s_options);
JsonDocument jsonDocument = JsonDocument.Parse(stream, s_options);
return jsonDocument;
}
}
......
......@@ -90,7 +90,7 @@ private static void CheckNode(JsonNode node)
}
[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 @@ public static void TestParseDoesNotOverflow()
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 @@ public static void TestDeepCopyDoesNotOverflow()
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 @@ public static void TestParseWithDuplicates()
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"]);
}
}
}
......@@ -82,7 +82,7 @@ public static void TestGetHashCode()
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());
......
......@@ -343,6 +343,19 @@ public static void TestAddingNull()
Assert.IsType<JsonNull>(jsonObject["null1"]);
}
[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()
{
......@@ -567,6 +580,23 @@ public static void TestGetJsonObjectPropertyThrows()
});
}
[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()
{
......@@ -596,6 +626,22 @@ public static void TestGetJsonArrayPropertyThrows()
});
}
[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()
{
......@@ -652,7 +698,7 @@ public static void TestArgumentNullValidation()
}
[Fact]
public static void TestStringComparisonEnum()
public static void TestStringComparisonEnumGetPropertyRemoveContains()
{
var jsonObject = new JsonObject()
{
......@@ -668,30 +714,56 @@ public static void TestStringComparisonEnum()
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 @@ public static void TestStringComparisonEnum()
{ "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]
......
......@@ -20,7 +20,7 @@ public static void TestDefaultCtor()
[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")]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册