From 45c74fe7bf347fbc284c363a41626e7e07ea55ca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Katarzyna=20Bu=C5=82at?= Date: Mon, 9 Sep 2019 14:53:38 -0700 Subject: [PATCH] Writable JSON API changes (dotnet/corefx#40673) * implicit operators moved to JsonNode * JsonNull introduced * JsonObject rewritten to use JsonObjectProperty * JsonElement.ObjectEnumerator changed to use _current instead of _enumerator * iterative DeepCopy implemented * code reorganized * parse implemented * JsonDocumentOptions added to JsonNode.Parse * WriteTo and ToJsonString added * specification adjusted * string comparison added * JsosReadonlyDomWriteTests reused * code coverage improved * minor other fixes * review comments included Commit migrated from https://github.com/dotnet/corefx/commit/37b37395481b635c15eaa3454e52b72ab91de4ef --- .../docs/writable_json_dom_spec.md | 98 ++-- .../System.Text.Json/ref/System.Text.Json.cs | 125 ++--- .../System.Text.Json/src/Resources/Strings.resx | 3 + .../System.Text.Json/src/System.Text.Json.csproj | 6 +- .../Json/Document/JsonElement.ArrayEnumerator.cs | 1 - .../Json/Document/JsonElement.ObjectEnumerator.cs | 65 ++- .../src/System/Text/Json/Document/JsonElement.cs | 42 +- .../src/System/Text/Json/Node/JsonArray.cs | 257 +-------- .../System/Text/Json/Node/JsonArrayEnumerator.cs | 7 +- .../src/System/Text/Json/Node/JsonBoolean.cs | 6 - .../System/Text/Json/Node/JsonNode.Traversal.cs | 327 ++++++++++++ .../Text/Json/Node/JsonNode.TraversalHelpers.cs | 81 +++ .../src/System/Text/Json/Node/JsonNode.cs | 163 ++++-- .../src/System/Text/Json/Node/JsonNull.cs | 73 +++ .../src/System/Text/Json/Node/JsonNumber.cs | 77 --- .../src/System/Text/Json/Node/JsonObject.cs | 585 ++++++++++----------- .../System/Text/Json/Node/JsonObjectEnumerator.cs | 50 +- .../System/Text/Json/Node/JsonObjectProperty.cs | 22 + .../src/System/Text/Json/Node/JsonString.cs | 6 - .../System.Text.Json/tests/JsonArrayTests.cs | 269 +++++++++- .../System.Text.Json/tests/JsonBooleanTests.cs | 49 +- .../tests/JsonElementWithNodeParentTests.cs | 295 +++++++++++ .../tests/JsonElementWriteTests.cs | 47 +- ...mentTests.cs => JsonNode.AsJsonElementTests.cs} | 43 +- .../System.Text.Json/tests/JsonNode.CloneTests.cs | 103 ++++ .../tests/JsonNode.TraversalTests.cs | 202 +++++++ .../System.Text.Json/tests/JsonNodeCloneTests.cs | 59 --- .../System.Text.Json/tests/JsonNodeParseTests.cs | 55 -- .../System.Text.Json/tests/JsonNodeTestData.cs | 4 +- .../System.Text.Json/tests/JsonNullTests.cs | 99 ++++ .../System.Text.Json/tests/JsonNumberTests.cs | 12 +- .../System.Text.Json/tests/JsonObjectTests.cs | 348 +++++++----- .../System.Text.Json/tests/JsonStringTests.cs | 55 +- .../tests/System.Text.Json.Tests.csproj | 8 +- 34 files changed, 2444 insertions(+), 1198 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.Traversal.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.TraversalHelpers.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNull.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectProperty.cs create mode 100644 src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs rename src/libraries/System.Text.Json/tests/{JsonNodeToJsonElementTests.cs => JsonNode.AsJsonElementTests.cs} (67%) create mode 100644 src/libraries/System.Text.Json/tests/JsonNode.CloneTests.cs create mode 100644 src/libraries/System.Text.Json/tests/JsonNode.TraversalTests.cs delete mode 100644 src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs delete mode 100644 src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs create mode 100644 src/libraries/System.Text.Json/tests/JsonNullTests.cs diff --git a/src/libraries/System.Text.Json/docs/writable_json_dom_spec.md b/src/libraries/System.Text.Json/docs/writable_json_dom_spec.md index 9e033ea..c1705ba 100644 --- a/src/libraries/System.Text.Json/docs/writable_json_dom_spec.md +++ b/src/libraries/System.Text.Json/docs/writable_json_dom_spec.md @@ -8,6 +8,7 @@ It is the base class for the following concrete types representing all possible * `JsonString` - representing JSON text value * `JsonBoolean` - representing JSON boolean value (`true` or `false`) * `JsonNumber` - representing JSON numeric value, can be created from and converted to all possible built-in numeric types +* `JsonNull` - representing JSON null value * `JsonArray` - representing the array of JSON nodes * `JsonObject` - representing the set of properties - named JSON nodes @@ -93,10 +94,9 @@ var preferences = new JsonObject() { { "colours", new JsonArray { "red", "green", "purple" } }, { "numbers", new JsonArray { 4, 123, 88 } }, - { "prime numbers", new JsonNumber[] { 19, 37 } }, + { "varia", new JsonArray { 1, "value", false } }, { "dishes", new JsonArray(dishes) }, - { "sports", new JsonArray(sports) }, - { "strange words", strangeWords.Where(word => ((JsonString)word).Value.Length < 10) }, + { "sports", new JsonArray(sports) } }; ``` @@ -107,7 +107,7 @@ The main goal of the new API is to allow users to modify existing instance of `J One may change the existing property to have a different value: ```csharp var options = new JsonObject { { "use caching", true } }; - options["use caching"] = (JsonBoolean)false; + options["use caching"] = false; ``` Add a value to existing JSON array or property to existing JSON object: @@ -123,7 +123,7 @@ foreach (KeyValuePair employee in EmployeesDatabase.GetTenBest } ``` -Easily access nested objects: +And easily access nested objects: ```csharp var issues = new JsonObject() { @@ -133,14 +133,7 @@ var issues = new JsonObject() }; issues.GetJsonArrayProperty("bugs").Add("bug 12356"); -((JsonString)issues.GetJsonArrayProperty("features")[0]).Value = "feature 1569"; -``` - -And modify the exisitng property name: -```csharp -JsonObject manager = EmployeesDatabase.GetManager(); -JsonObject reportingEmployees = manager.GetJsonObjectProperty("reporting employees"); -reportingEmployees.ModifyPropertyName("software developers", "software engineers"); +issues.GetJsonArrayProperty("features")[0] = "feature 1569"; ``` ### Transforming to and from JsonElement @@ -198,77 +191,48 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement()); * Higher emphasis on usability over allocations/performance. * No advanced methods for looking up properties like `GetAllValuesByPropertyName` or `GetAllPrimaryTypedValues`, because they would be too specialized. * Support for LINQ style quering capability. -* `null` reference to node instead of `JsonNull` class. - -* Initializing JsonArray with additional constructors accepting `IEnumerables` of all primary types (bool, string, int, double, long...). - - Considered solutions: - - 1. One additional constructor in JsonArray - ```csharp - public JsonArray(IEnumerable jsonValues) { } - ``` - 2. Implicit operator from Array in JsonArray - - 3. More additional constructors in JsonArray (chosen) - ```csharp - public JsonArray(IEnumerable jsonValues) { } - public JsonArray(IEnumerable jsonValues) { } - public JsonArray(IEnumerable jsonValues) { } - ... - public JsonArray(IEnumerable jsonValues) { } - ``` - - | Solution | Pros | Cons | Comment | - |----------|:-------------|:------|--------:| - | 1 | - only one additional method
- accepts collection of different types
- accepts `IEnumerable`
- IntelliSense (autocompletion and showing suggestions) | - accepts collection of types not deriving from `JsonNode`
- needs to check it in runtime | accepts too much,
array of different primary types wouldn't be returned from method | - | 2 | - only one additional method
- accepts collection of different types
| - works only in C#
- no IntelliSense
- users may not be aware of it
- accepts only `Array`
- accepts collection of types not deriving from `JsonNode`
- needs to check it in runtime | from {1,2},
2 seems worse | - | 3 | - accepts IEnumerable
- does not accept collection of types not deriving from `JsonNode`
- no checks in runtime
- IntelliSense | - a lot of additional methods
- does not accept a collection of different types | gives less possibilities than {1,2}, but requiers no additional checks | - -* Implicit operators for `JsonString`, `JsonBoolean` and `JsonNumber` as an additional feature. +* `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). -* No support for duplicates of property names. Possibly, adding an option for the user to choose from: "first value", "last value", or throw-on-duplicate. +* 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. * No support for escaped characters when creating `JsonNumber` from string. -* Transformation API: - * `DeepCopy` method in JsonElement allowing to change JsonElement into JsonNode recursively transforming all of the elements - * `AsJsonElement` method in JsonNode 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)` in JsonNode to be able to parse a JSON string right into JsonNode if the user knows they wants mutable version - * `DeepCopy` in JsonNode to make a copy of the whole tree - * `GetNode` and TryGetNode in JsonNode allowing to retrieve it from JsonElement - * `WriteTo(Utf8JsonWriter)` in JsonNode for writing a JsonNode to a Utf8JsonWriter without having to go through JsonElement * `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. + * `Clone` method to make a copy of the whole JsonNode tree. + * `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`. +* `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. +* `ToString` returns: + * Unescaped string for `JsonString`. + * String representation of number for `JsonNumber`. + * "true" / "false" (JSON representation) for `JsonBoolean`. + * Is not overloaded for `JsonArray` and `JsonObject`. ## Open questions -* Do we want to add recursive equals on `JsonArray` and `JsonObject`? -* Do we want to make `JsonNode` derived types implement `IComparable` (which ones)? -* Do we want `JsonObject` to implement `IDictionary` and `JsonArray` to implement `IList` (currently JsonArray does, but JsonObject not)? * Do we want `JsonArray` to support `Contains`, `IndexOf` and `LastIndexOf` if we keep reference equality for `JsonArray`/`JsonObject` and don't have a good way of comparing numbers? -* Would escaped characters be supported for creating `JsonNumber` from string? -* Is the API for `JsonNode` and `JsonElement` interactions sufficient? -* Do we want to support duplicate and order preservation/control when adding/removing values in `JsonArray`/`JsonObject`? * Should nodes track their own position in the JSON graph? Do we want to allow properties like Parent, Next and Previous? | Solution | Pros | Cons | |----------|:-------------|--------| |current API| - no additional checks need to be made | - creating recursive loop by the user may be problematic | |tracking nodes | - handles recursive loop problem | - when node is added to a parent, it needs to be checked
if it already has a parent and make a copy if it has | -* Do we want to change `JsonNumber`'s backing field to something different than `string`? + +* Do we want to change JsonNumber's backing field to something different than string? + Suggestions: - `Span` or array of `Utf8String`/`Char8` (once they come online in the future) / `byte` - Internal types that are specific to each numeric type in .NET with factories to create JsonNumber - Internal struct field which has all the supported numeric types - Unsigned long field accompanying string to store types that are <= 8 bytes long -* Do we want to support creating `JsonNumber` from `BigInterger` without changing it to string? -* Should `ToString` on `JsonBoolean` and `JsonString` return the .NET or JSON representation? -* Do we want to keep implicit cast operators (even though for `JsonNumber` it would mean throwing in some cases, which is against FDG)? -* Do we want overloads that take a `StringComparison` enum for methods retrieving properties? It would make API easier to use where case is not important. -* Where do we want to enable user to set handling properties manner? Do we want to support it as `JsonElement` does not? -* Do we want to support transforming `JsonObject` into JSON string? Should `ToString` behave this way or do we want an additional method - e.g. `GetJsonString` (it might be confusing as we have a type `JsonString`) or `GetJsonRepresenation`? -* `AsJsonElement` function on `JsonNode` currently does not work for `null` node. Do we want to support null case somehow? -* Do we want both `Clone` and `DeepCopy` methods for `JsonNode`? -* Are we OK with needing to cast `JsonArray` to `JsonNode` in order to add it to `JsonObject`? (there's an ambiguous call between `JsonArray` and `IEnumerable` right now) -* Do we want `IsImmutable` property for `JsonElement`? ## Useful links diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 262999f..0731f94 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -38,47 +38,13 @@ namespace System.Text.Json public bool IsReadOnly { get { throw null; } } public System.Text.Json.JsonNode this[int idx] { get { throw null; } set { } } public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } } - public void Add(bool value) { } - public void Add(byte value) { } - public void Add(decimal value) { } - public void Add(double value) { } - public void Add(short value) { } - public void Add(int value) { } - public void Add(long value) { } - [System.CLSCompliantAttribute(false)] - public void Add(sbyte value) { } - public void Add(float value) { } - public void Add(string value) { } public void Add(System.Text.Json.JsonNode value) { } - [System.CLSCompliantAttribute(false)] - public void Add(ushort value) { } - [System.CLSCompliantAttribute(false)] - public void Add(uint value) { } - [System.CLSCompliantAttribute(false)] - public void Add(ulong value) { } public void Clear() { } public override System.Text.Json.JsonNode Clone() { throw null; } public bool Contains(System.Text.Json.JsonNode value) { throw null; } public System.Text.Json.JsonArrayEnumerator GetEnumerator() { throw null; } public int IndexOf(System.Text.Json.JsonNode item) { throw null; } - public void Insert(int index, bool item) { } - public void Insert(int index, byte item) { } - public void Insert(int index, decimal item) { } - public void Insert(int index, double item) { } - public void Insert(int index, short item) { } - public void Insert(int index, int item) { } - public void Insert(int index, long item) { } - [System.CLSCompliantAttribute(false)] - public void Insert(int index, sbyte item) { } - public void Insert(int index, float item) { } - public void Insert(int index, string item) { } public void Insert(int index, System.Text.Json.JsonNode item) { } - [System.CLSCompliantAttribute(false)] - public void Insert(int index, ushort item) { } - [System.CLSCompliantAttribute(false)] - public void Insert(int index, uint item) { } - [System.CLSCompliantAttribute(false)] - public void Insert(int index, ulong item) { } public int LastIndexOf(System.Text.Json.JsonNode item) { throw null; } public bool Remove(System.Text.Json.JsonNode item) { throw null; } public int RemoveAll(System.Predicate match) { throw null; } @@ -108,7 +74,6 @@ namespace System.Text.Json public bool Equals(System.Text.Json.JsonBoolean other) { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(System.Text.Json.JsonBoolean left, System.Text.Json.JsonBoolean right) { throw null; } - public static implicit operator System.Text.Json.JsonBoolean (bool value) { throw null; } public static bool operator !=(System.Text.Json.JsonBoolean left, System.Text.Json.JsonBoolean right) { throw null; } public override string ToString() { throw null; } } @@ -270,8 +235,42 @@ namespace System.Text.Json public abstract System.Text.Json.JsonNode Clone(); public static System.Text.Json.JsonNode DeepCopy(System.Text.Json.JsonElement jsonElement) { throw null; } public static System.Text.Json.JsonNode GetNode(System.Text.Json.JsonElement jsonElement) { throw null; } - public static System.Text.Json.JsonNode Parse(string json) { throw null; } + public static implicit operator System.Text.Json.JsonNode (bool value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (byte value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (System.DateTime value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (System.DateTimeOffset value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (decimal value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (double value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (System.Guid value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (short value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (int value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (long value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNode (sbyte value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (float value) { throw null; } + public static implicit operator System.Text.Json.JsonNode (string value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNode (ushort value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNode (uint value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static implicit operator System.Text.Json.JsonNode (ulong value) { throw null; } + public static System.Text.Json.JsonNode Parse(string json, System.Text.Json.JsonDocumentOptions options = default(System.Text.Json.JsonDocumentOptions), System.Text.Json.DuplicatePropertyNameHandling duplicatePropertyNameHandling = System.Text.Json.DuplicatePropertyNameHandling.Replace) { throw null; } + public string ToJsonString() { throw null; } public static bool TryGetNode(System.Text.Json.JsonElement jsonElement, out System.Text.Json.JsonNode jsonNode) { throw null; } + public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { } + } + public sealed partial class JsonNull : System.Text.Json.JsonNode, System.IEquatable + { + public JsonNull() { } + public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } } + public override System.Text.Json.JsonNode Clone() { throw null; } + public override bool Equals(object obj) { throw null; } + public bool Equals(System.Text.Json.JsonNull other) { throw null; } + public override int GetHashCode() { throw null; } + public static bool operator ==(System.Text.Json.JsonNull left, System.Text.Json.JsonNull right) { throw null; } + public static bool operator !=(System.Text.Json.JsonNull left, System.Text.Json.JsonNull right) { throw null; } + public override string ToString() { throw null; } } public sealed partial class JsonNumber : System.Text.Json.JsonNode, System.IEquatable { @@ -313,21 +312,6 @@ namespace System.Text.Json [System.CLSCompliantAttribute(false)] public ulong GetUInt64() { throw null; } public static bool operator ==(System.Text.Json.JsonNumber left, System.Text.Json.JsonNumber right) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (byte value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (decimal value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (double value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (short value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (int value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (long value) { throw null; } - [System.CLSCompliantAttribute(false)] - public static implicit operator System.Text.Json.JsonNumber (sbyte value) { throw null; } - public static implicit operator System.Text.Json.JsonNumber (float value) { throw null; } - [System.CLSCompliantAttribute(false)] - public static implicit operator System.Text.Json.JsonNumber (ushort value) { throw null; } - [System.CLSCompliantAttribute(false)] - public static implicit operator System.Text.Json.JsonNumber (uint value) { throw null; } - [System.CLSCompliantAttribute(false)] - public static implicit operator System.Text.Json.JsonNumber (ulong value) { throw null; } public static bool operator !=(System.Text.Json.JsonNumber left, System.Text.Json.JsonNumber right) { throw null; } public void SetByte(byte value) { } public void SetDecimal(decimal value) { } @@ -364,48 +348,34 @@ namespace System.Text.Json } public sealed partial class JsonObject : System.Text.Json.JsonNode, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { - public JsonObject(System.Collections.Generic.IEnumerable> jsonProperties, System.Text.Json.DuplicatePropertyNameHandling duplicatePropertyNameHandling = System.Text.Json.DuplicatePropertyNameHandling.Replace) { } - public JsonObject(System.Text.Json.DuplicatePropertyNameHandling duplicatePropertyNameHandling = System.Text.Json.DuplicatePropertyNameHandling.Replace) { } + public JsonObject() { } + public JsonObject(System.Collections.Generic.IEnumerable> jsonProperties) { } public System.Text.Json.JsonNode this[string propertyName] { get { throw null; } set { } } - public System.Collections.Generic.ICollection PropertyNames { get { throw null; } } - public System.Collections.Generic.ICollection PropertyValues { get { throw null; } } public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } } public void Add(System.Collections.Generic.KeyValuePair jsonProperty) { } - public void Add(string propertyName, bool propertyValue) { } - public void Add(string propertyName, byte propertyValue) { } - public void Add(string propertyName, System.Collections.Generic.IEnumerable propertyValues) { } - public void Add(string propertyName, System.DateTime propertyValue) { } - public void Add(string propertyName, System.DateTimeOffset propertyValue) { } - public void Add(string propertyName, decimal propertyValue) { } - public void Add(string propertyName, double propertyValue) { } - public void Add(string propertyName, System.Guid propertyValue) { } - public void Add(string propertyName, short propertyValue) { } - public void Add(string propertyName, int propertyValue) { } - public void Add(string propertyName, long propertyValue) { } - public void Add(string propertyName, System.ReadOnlySpan propertyValue) { } - [System.CLSCompliantAttribute(false)] - public void Add(string propertyName, sbyte propertyValue) { } - public void Add(string propertyName, float propertyValue) { } - public void Add(string propertyName, string propertyValue) { } public void Add(string propertyName, System.Text.Json.JsonNode propertyValue) { } - [System.CLSCompliantAttribute(false)] - public void Add(string propertyName, ushort propertyValue) { } - [System.CLSCompliantAttribute(false)] - public void Add(string propertyName, uint propertyValue) { } - [System.CLSCompliantAttribute(false)] - public void Add(string propertyName, ulong propertyValue) { } public void AddRange(System.Collections.Generic.IEnumerable> jsonProperties) { } public override System.Text.Json.JsonNode Clone() { throw null; } public bool ContainsProperty(string propertyName) { throw null; } + public bool ContainsProperty(string propertyName, System.StringComparison stringComparison) { throw null; } public System.Text.Json.JsonObjectEnumerator GetEnumerator() { throw null; } public System.Text.Json.JsonArray GetJsonArrayPropertyValue(string propertyName) { throw null; } + public System.Text.Json.JsonArray GetJsonArrayPropertyValue(string propertyName, System.StringComparison stringComparison) { throw null; } public System.Text.Json.JsonObject GetJsonObjectPropertyValue(string propertyName) { throw null; } + public System.Text.Json.JsonObject GetJsonObjectPropertyValue(string propertyName, System.StringComparison stringComparison) { throw null; } + public System.Collections.Generic.IReadOnlyCollection GetPropertyNames() { throw null; } public System.Text.Json.JsonNode GetPropertyValue(string propertyName) { throw null; } + public System.Text.Json.JsonNode GetPropertyValue(string propertyName, System.StringComparison stringComparison) { throw null; } + public System.Collections.Generic.IReadOnlyCollection GetPropertyValues() { throw null; } public bool Remove(string propertyName) { throw null; } + public bool Remove(string propertyName, System.StringComparison stringComparison) { throw null; } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + public bool TryGetJsonArrayPropertyValue(string propertyName, System.StringComparison stringComparison, out System.Text.Json.JsonArray jsonArray) { throw null; } public bool TryGetJsonArrayPropertyValue(string propertyName, out System.Text.Json.JsonArray jsonArray) { throw null; } + public bool TryGetJsonObjectPropertyValue(string propertyName, System.StringComparison stringComparison, out System.Text.Json.JsonObject jsonObject) { throw null; } public bool TryGetJsonObjectPropertyValue(string propertyName, out System.Text.Json.JsonObject jsonObject) { throw null; } + public bool TryGetPropertyValue(string propertyName, System.StringComparison stringComparison, out System.Text.Json.JsonNode jsonNode) { throw null; } public bool TryGetPropertyValue(string propertyName, out System.Text.Json.JsonNode jsonNode) { throw null; } } public partial struct JsonObjectEnumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable @@ -497,7 +467,6 @@ namespace System.Text.Json public System.Guid GetGuid() { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(System.Text.Json.JsonString left, System.Text.Json.JsonString right) { throw null; } - public static implicit operator System.Text.Json.JsonString (string value) { throw null; } public static bool operator !=(System.Text.Json.JsonString left, System.Text.Json.JsonString right) { throw null; } public override string ToString() { throw null; } public bool TryGetDateTime(out System.DateTime value) { throw null; } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 08db5db..122358e 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -447,4 +447,7 @@ The JSON array was modified during iteration. + + This JsonElement instance was not built from JsonNode + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 8ed4ffd..ba0c886 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -213,10 +213,14 @@ - + + + + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs index 34edd8c..3469e9b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs @@ -34,7 +34,6 @@ namespace System.Text.Json else { var jsonArray = (JsonArray)target._parent; - _endIdxOrVersion = jsonArray._version; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ObjectEnumerator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ObjectEnumerator.cs index 92968ba..0035340 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ObjectEnumerator.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ObjectEnumerator.cs @@ -18,27 +18,24 @@ namespace System.Text.Json { private readonly JsonElement _target; private int _curIdx; - private readonly int _endIdx; - private JsonObjectEnumerator _jsonObjectEnumerator; + private readonly int _endIdxOrVersion; + private JsonObjectProperty _current; internal ObjectEnumerator(JsonElement target) { _target = target; _curIdx = -1; + _current = null; if (target._parent is JsonDocument document) { Debug.Assert(target.TokenType == JsonTokenType.StartObject); - - _endIdx = document.GetEndIndex(_target._idx, includeEndElement: false); - _jsonObjectEnumerator = default; + _endIdxOrVersion = document.GetEndIndex(_target._idx, includeEndElement: false); } else { - _endIdx = -1; - var jsonObject = (JsonObject)target._parent; - _jsonObjectEnumerator = new JsonObjectEnumerator(jsonObject); + _endIdxOrVersion = jsonObject._version; } } @@ -47,23 +44,14 @@ namespace System.Text.Json { get { - if (!_target.IsImmutable) + if (_target._parent is JsonNode) { - KeyValuePair propertyPair = _jsonObjectEnumerator.Current; - - // propertyPair.Key is null before first after last call of MoveNext - if (propertyPair.Key == null) + if (_current == null) { return default; } - // null JsonNode case - if (propertyPair.Value == null) - { - return new JsonProperty(new JsonElement(null), propertyPair.Key); - } - - return new JsonProperty(propertyPair.Value.AsJsonElement(), propertyPair.Key); + return new JsonProperty(_current.Value.AsJsonElement(), _current.Name); } if (_curIdx < 0) @@ -105,21 +93,15 @@ namespace System.Text.Json /// public void Dispose() { - _curIdx = _endIdx; - if (!_target.IsImmutable) - { - _jsonObjectEnumerator.Dispose(); - } + _curIdx = _endIdxOrVersion; + _current = null; } /// public void Reset() { _curIdx = -1; - if (!_target.IsImmutable) - { - ((IEnumerator)_jsonObjectEnumerator).Reset(); - } + _current = null; } /// @@ -128,12 +110,29 @@ namespace System.Text.Json /// public bool MoveNext() { - if (!_target.IsImmutable) + if (_target._parent is JsonObject jsonObject) { - return _jsonObjectEnumerator.MoveNext(); + if (jsonObject._version != _endIdxOrVersion) + { + throw new InvalidOperationException(SR.ArrayModifiedDuringIteration); + } + + if (_current == null) + { + _current = jsonObject._first; + return true; + } + + if (_current.Next != null) + { + _current = _current.Next; + return true; + } + + return false; } - if (_curIdx >= _endIdx) + if (_curIdx >= _endIdxOrVersion) { return false; } @@ -151,7 +150,7 @@ namespace System.Text.Json // _curIdx is now pointing at a property name, move one more to get the value _curIdx += JsonDocument.DbRow.Size; - return _curIdx < _endIdx; + return _curIdx < _endIdxOrVersion; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 94bf579..07f6ccc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -64,11 +64,6 @@ namespace System.Text.Json var jsonNode = (JsonNode)_parent; - if (jsonNode == null) - { - return JsonValueKind.Null; - } - return jsonNode.ValueKind; } } @@ -1453,8 +1448,11 @@ namespace System.Text.Json /// Gets the original input data backing this value, returning it as a . /// /// - /// The original input data backing this value, returning it as a . + /// The original input data backing this value, returning it as a . /// + /// + /// For JsonElement built from , the value of is returned. + /// /// /// The parent has been disposed. /// @@ -1468,7 +1466,7 @@ namespace System.Text.Json } var jsonNode = (JsonNode)_parent; - return jsonNode.ToString(); + return jsonNode.ToJsonString(); } internal string GetPropertyRawText() @@ -1601,8 +1599,14 @@ namespace System.Text.Json CheckValidInstance(); - var document = (JsonDocument)_parent; - document.WriteElementTo(_idx, writer); + if (_parent is JsonDocument document) + { + document.WriteElementTo(_idx, writer); + return; + } + + var jsonNode = (JsonNode)_parent; + jsonNode.WriteTo(writer); } /// @@ -1681,6 +1685,7 @@ namespace System.Text.Json /// Gets a string representation for the current value appropriate to the value type. /// /// + /// For JsonElement built from : /// /// For , is returned. /// @@ -1701,6 +1706,9 @@ namespace System.Text.Json /// For other types, the value of () is returned. /// /// + /// + /// For JsonElement built from , the value of is returned. + /// /// /// A string representation for the current value appropriate to the value type. /// @@ -1709,6 +1717,11 @@ namespace System.Text.Json /// public override string ToString() { + if (_parent is JsonNode jsonNode) + { + return jsonNode.ToJsonString(); + } + switch (TokenType) { case JsonTokenType.None: @@ -1724,13 +1737,7 @@ namespace System.Text.Json { // null parent should have hit the None case Debug.Assert(_parent != null); - if (_parent is JsonDocument document) - { - return document.GetRawValueAsString(_idx); - } - - var jsonNode = (JsonNode)_parent; - return jsonNode.ToString(); + return ((JsonDocument)_parent).GetRawValueAsString(_idx); } case JsonTokenType.String: return GetString(); @@ -1756,6 +1763,9 @@ namespace System.Text.Json /// a value contained within another JsonElement which was the output of a previous /// call to Clone, this method results in no additional memory allocation. /// + /// + /// For built from performs . + /// public JsonElement Clone() { CheckValidInstance(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs index 13b7b7a..f7e47eb6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs @@ -38,19 +38,11 @@ namespace System.Text.Json /// Initializes a new instance of the class representing the specified collection of s. /// /// Collection to represent. - /// - /// Some of provided values are null. - /// public JsonArray(IEnumerable values) : this() { foreach (string value in values) { - if (value == null) - { - _list.Add(null); - } - - _list.Add(new JsonString(value)); + _list.Add(value); } } @@ -62,7 +54,7 @@ namespace System.Text.Json { foreach (bool value in values) { - _list.Add(new JsonBoolean(value)); + _list.Add(value); } } @@ -74,7 +66,7 @@ namespace System.Text.Json { foreach (byte value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -86,7 +78,7 @@ namespace System.Text.Json { foreach (short value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -98,7 +90,7 @@ namespace System.Text.Json { foreach (int value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -110,7 +102,7 @@ namespace System.Text.Json { foreach (long value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -125,7 +117,7 @@ namespace System.Text.Json { foreach (float value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -140,7 +132,7 @@ namespace System.Text.Json { foreach (double value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -153,7 +145,7 @@ namespace System.Text.Json { foreach (sbyte value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -166,7 +158,7 @@ namespace System.Text.Json { foreach (ushort value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -179,7 +171,7 @@ namespace System.Text.Json { foreach (uint value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -192,7 +184,7 @@ namespace System.Text.Json { foreach (ulong value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -204,7 +196,7 @@ namespace System.Text.Json { foreach (decimal value in values) { - _list.Add(new JsonNumber(value)); + _list.Add(value); } } @@ -215,12 +207,13 @@ namespace System.Text.Json /// /// Index is less than 0. /// + /// Null value is allowed and will be converted to the instance. public JsonNode this[int idx] { get => _list[idx]; set { - _list[idx] = value; + _list[idx] = value ?? new JsonNull(); _version++; } } @@ -229,226 +222,26 @@ namespace System.Text.Json /// Adds the specified value as the last item in this collection. /// /// The value to add. - /// Null value is allowed and represents the JSON null value. + /// Null value is allowed and will be converted to the instance. public void Add(JsonNode value) { - _list.Add(value); + _list.Add(value ?? new JsonNull()); _version++; } /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - /// Null value is allowed and represents the JSON null value. - public void Add(string value) - { - if (value == null) - { - Add((JsonNode)null); - } - else - { - Add(new JsonString(value)); - } - } - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(bool value) => Add(new JsonBoolean(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(byte value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(short value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(int value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(long value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - /// - /// Provided value is not in a legal JSON number format. - /// - public void Add(float value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - /// - /// Provided value is not in a legal JSON number format. - /// - public void Add(double value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - [CLSCompliant(false)] - public void Add(sbyte value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - [CLSCompliant(false)] - public void Add(ushort value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - [CLSCompliant(false)] - public void Add(uint value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - [CLSCompliant(false)] - public void Add(ulong value) => Add(new JsonNumber(value)); - - /// - /// Adds the specified value as a as the last item in this collection. - /// - /// The value to add. - public void Add(decimal value) => Add(new JsonNumber(value)); - - /// /// Inserts the specified item at the specified index of the JSON array. /// /// The zero-based index at which should be inserted. /// The item to add. - /// The parameter may be , which represents the JSON null value. + /// Null value is allowed and will be converted to the instance. public void Insert(int index, JsonNode item) { - _list.Insert(index, item); + _list.Insert(index, item ?? new JsonNull()); _version++; } /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, string item) => Insert(index, new JsonString(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, bool item) => Insert(index, new JsonBoolean(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, byte item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, short item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, int item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, long item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - /// - /// Provided value is not in a legal JSON number format. - /// - public void Insert(int index, float item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - /// - /// Provided value is not in a legal JSON number format. - /// - public void Insert(int index, double item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - [CLSCompliant(false)] - public void Insert(int index, sbyte item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - [CLSCompliant(false)] - public void Insert(int index, ushort item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - [CLSCompliant(false)] - public void Insert(int index, uint item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - [CLSCompliant(false)] - public void Insert(int index, ulong item) => Insert(index, new JsonNumber(item)); - - /// - /// Inserts the specified item as a at the specified index of the collection. - /// - /// The zero-based index at which should be inserted. - /// The item to add. - public void Insert(int index, decimal item) => Insert(index, new JsonNumber(item)); - - /// /// Determines whether a specified element is in a collection. /// /// Value to check. @@ -456,7 +249,8 @@ namespace System.Text.Json /// if the value is successfully found in a collection, /// otherwise. /// - public bool Contains(JsonNode value) => _list.Contains(value); + /// Null value is allowed and will be converted to the instance. + public bool Contains(JsonNode value) => _list.Contains(value ?? new JsonNull()); /// /// Gets the number of elements contained in the collection. @@ -473,14 +267,16 @@ namespace System.Text.Json /// /// Item to find. /// The zero-based starting index of the search. 0 (zero) is valid in an empty collection. - public int IndexOf(JsonNode item) => _list.IndexOf(item); + /// Null value is allowed and will be converted to the instance. + public int IndexOf(JsonNode item) => _list.IndexOf(item ?? new JsonNull()); /// /// Returns the zero-based index of the last occurrence of a specified item in the collection. /// /// Item to find. /// The zero-based starting index of the search. 0 (zero) is valid in an empty collection. - public int LastIndexOf(JsonNode item) => _list.LastIndexOf(item); + /// Null value is allowed and will be converted to the instance. + public int LastIndexOf(JsonNode item) => _list.LastIndexOf(item ?? new JsonNull()); /// /// Removes all elements from the JSON array. @@ -501,10 +297,11 @@ namespace System.Text.Json /// if the item is successfully found in a collection and removed, /// otherwise. /// + /// Null value is allowed and will be converted to the instance. public bool Remove(JsonNode item) { _version++; - return _list.Remove(item); + return _list.Remove(item ?? new JsonNull()); } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs index 1dc16a2..a3a34c4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs @@ -40,6 +40,11 @@ namespace System.Text.Json /// /// Sets the enumerator to its initial position, which is before the first element in the JSON array. /// - void IEnumerator.Reset() => ((IEnumerator)_enumerator).Reset(); + void IEnumerator.Reset() + { + IEnumerator enumerator = _enumerator; + enumerator.Reset(); + _enumerator = (List.Enumerator)enumerator; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs index 7a358dd..d98e1d4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs @@ -31,12 +31,6 @@ namespace System.Text.Json public override string ToString() => Value ? "true" : "false"; /// - /// Converts a to a . - /// - /// The value to convert. - public static implicit operator JsonBoolean(bool value) => new JsonBoolean(value); - - /// /// Compares to the value of this instance. /// /// The object to compare against. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.Traversal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.Traversal.cs new file mode 100644 index 0000000..7f84345 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.Traversal.cs @@ -0,0 +1,327 @@ +// 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. + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace System.Text.Json +{ + /// + /// The base class that represents a single node within a mutable JSON document. + /// + public abstract partial class JsonNode + { + /// + /// Performs a deep copy operation on returning corresponding modifiable tree structure of JSON nodes. + /// Operations performed on returned does not modify . + /// + /// to copy. + /// representing . + public static JsonNode DeepCopy(JsonElement jsonElement) + { + if (!jsonElement.IsImmutable) + { + return GetNode(jsonElement).Clone(); + } + + // Iterative DFS: + + var currentNodes = new Stack>(); // objects/arrays currently being created + var recursionStack = new Stack>(); // null JsonElement represents end of current object/array + JsonNode toReturn = null; + + recursionStack.Push(new KeyValuePair(null, jsonElement)); + + while (recursionStack.Any()) + { + KeyValuePair currentPair = recursionStack.Peek(); + JsonElement? currentJsonElement = currentPair.Value; + recursionStack.Pop(); + + if (!currentJsonElement.HasValue) + { + // Current object/array is finished and can be added to its parent: + + KeyValuePair nodePair = currentNodes.Peek(); + currentNodes.Pop(); + + Debug.Assert(nodePair.Value is JsonArray || nodePair.Value is JsonObject); + + AddToParent(nodePair, ref currentNodes, ref toReturn); + + continue; + } + + switch (currentJsonElement.Value.ValueKind) + { + case JsonValueKind.Object: + var jsonObject = new JsonObject(); + + // Add jsonObject to current nodes: + currentNodes.Push(new KeyValuePair(currentPair.Key, jsonObject)); + + // Add end of object marker: + recursionStack.Push(new KeyValuePair(null, null)); + + // Add properties to recursion stack. Reverse enumerator to keep properties order: + foreach (JsonProperty property in currentJsonElement.Value.EnumerateObject().Reverse()) + { + recursionStack.Push(new KeyValuePair(property.Name, property.Value)); + } + break; + case JsonValueKind.Array: + var jsonArray = new JsonArray(); + + // Add jsonArray to current nodes: + currentNodes.Push(new KeyValuePair(currentPair.Key, jsonArray)); + + // Add end of array marker: + recursionStack.Push(new KeyValuePair(null, null)); + + // Add elements to recursion stack. Reverse enumerator to keep items order: + foreach (JsonElement element in currentJsonElement.Value.EnumerateArray().Reverse()) + { + recursionStack.Push(new KeyValuePair(null, element)); + } + break; + case JsonValueKind.Number: + var jsonNumber = new JsonNumber(currentJsonElement.Value.GetRawText()); + AddToParent(new KeyValuePair(currentPair.Key, jsonNumber), ref currentNodes, ref toReturn); + break; + case JsonValueKind.String: + var jsonString = new JsonString(currentJsonElement.Value.GetString()); + AddToParent(new KeyValuePair(currentPair.Key, jsonString), ref currentNodes, ref toReturn); + break; + case JsonValueKind.True: + var jsonBooleanTrue = new JsonBoolean(true); + AddToParent(new KeyValuePair(currentPair.Key, jsonBooleanTrue), ref currentNodes, ref toReturn); + break; + case JsonValueKind.False: + var jsonBooleanFalse = new JsonBoolean(false); + AddToParent(new KeyValuePair(currentPair.Key, jsonBooleanFalse), ref currentNodes, ref toReturn); + break; + case JsonValueKind.Null: + var jsonNull = new JsonNull(); + AddToParent(new KeyValuePair(currentPair.Key, jsonNull), ref currentNodes, ref toReturn); + break; + default: + Debug.Assert(jsonElement.ValueKind == JsonValueKind.Undefined, "No handler for JsonValueKind.{jsonElement.ValueKind}"); + throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Undefined, jsonElement.ValueKind); + } + } + + Debug.Assert(toReturn != null); + + return toReturn; + } + + /// + /// Parses a string representing JSON document into . + /// + /// JSON to parse. + /// Options to control the reader behavior during parsing. + /// Specifies the way of handling duplicate property names. + /// representation of . + public static JsonNode Parse(string json, JsonDocumentOptions options = default, DuplicatePropertyNameHandling duplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace) + { + Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), options.GetReaderOptions()); + + var currentNodes = new Stack>(); // nodes currently being created + JsonNode toReturn = null; + + while (reader.Read()) + { + JsonTokenType tokenType = reader.TokenType; + KeyValuePair currentPair = new KeyValuePair(); + if (currentNodes.Any()) + { + currentPair = currentNodes.Peek(); + } + + void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false) + { + KeyValuePair newProperty; + + if (currentPair.Value == null) + { + // If previous token was property name, + // it was added to stack with not null name and null value, + // otherwise, this is first JsonNode added + if (currentPair.Key != null) + { + // Create as property, keep name, replace null with new JsonNode: + currentNodes.Pop(); + newProperty = new KeyValuePair(currentPair.Key, jsonNode); + } + else + { + // Add first JsonNode: + newProperty = new KeyValuePair(null, jsonNode); + } + } + else + { + // Create as value: + newProperty = new KeyValuePair(null, jsonNode); + } + + if (keepInCurrentNodes) + { + // If after adding property, it should be kept in currentNodes, it must be JsonObject or JsonArray + Debug.Assert(jsonNode.ValueKind == JsonValueKind.Object || jsonNode.ValueKind == JsonValueKind.Array); + + currentNodes.Push(newProperty); + } + else + { + AddToParent(newProperty, ref currentNodes, ref toReturn, duplicatePropertyNameHandling); + } + } + + switch (tokenType) + { + case JsonTokenType.StartObject: + AddNewPair(new JsonObject(), true); + break; + case JsonTokenType.EndObject: + Debug.Assert(currentPair.Value is JsonObject); + + currentNodes.Pop(); + AddToParent(currentPair, ref currentNodes, ref toReturn, duplicatePropertyNameHandling); + break; + case JsonTokenType.StartArray: + AddNewPair(new JsonArray(), true); + break; + case JsonTokenType.EndArray: + Debug.Assert(currentPair.Value is JsonArray); + + currentNodes.Pop(); + AddToParent(currentPair, ref currentNodes, ref toReturn, duplicatePropertyNameHandling); + break; + case JsonTokenType.PropertyName: + currentNodes.Push(new KeyValuePair(reader.GetString(), null)); + break; + case JsonTokenType.Number: + AddNewPair(new JsonNumber(JsonHelpers.Utf8GetString(reader.ValueSpan))); + break; + case JsonTokenType.String: + AddNewPair(new JsonString(reader.GetString())); + break; + case JsonTokenType.True: + AddNewPair(new JsonBoolean(true)); + break; + case JsonTokenType.False: + AddNewPair(new JsonBoolean(false)); + break; + case JsonTokenType.Null: + AddNewPair(new JsonNull()); + break; + } + } + + Debug.Assert(toReturn != null); + return toReturn; + } + + /// + /// Writes this instance to provided writer. + /// + /// Writer to wrtire this instance to. + public void WriteTo(Utf8JsonWriter writer) + { + var recursionStack = new Stack(); + recursionStack.Push(new RecursionStackFrame(null, this)); + + while (recursionStack.Any()) + { + RecursionStackFrame currentFrame = recursionStack.Peek(); + recursionStack.Pop(); + + if (currentFrame.PropertyValue == null) + { + // Current object/array is finished. + // PropertyValue is null, so we need to check ValueKind: + + Debug.Assert(currentFrame.ValueKind == JsonValueKind.Object || currentFrame.ValueKind == JsonValueKind.Array); + + if (currentFrame.ValueKind == JsonValueKind.Object) + { + writer.WriteEndObject(); + } + if (currentFrame.ValueKind == JsonValueKind.Array) + { + writer.WriteEndArray(); + } + + continue; + } + + if (currentFrame.PropertyName != null) + { + writer.WritePropertyName(currentFrame.PropertyName); + } + + switch (currentFrame.PropertyValue) + { + case JsonObject jsonObject: + writer.WriteStartObject(); + + // Add end of object marker: + recursionStack.Push(new RecursionStackFrame(null, null, JsonValueKind.Object)); + + // Add properties to recursion stack. Reverse enumerator to keep properties order: + foreach (KeyValuePair jsonProperty in jsonObject.Reverse()) + { + recursionStack.Push(new RecursionStackFrame(jsonProperty.Key, jsonProperty.Value)); + } + break; + case JsonArray jsonArray: + writer.WriteStartArray(); + + // Add end of array marker: + recursionStack.Push(new RecursionStackFrame(null, null, JsonValueKind.Array)); + + // Add items to recursion stack. Reverse enumerator to keep items order: + foreach (JsonNode item in jsonArray.Reverse()) + { + recursionStack.Push(new RecursionStackFrame(null, item)); + } + break; + case JsonString jsonString: + writer.WriteStringValue(jsonString.Value); + break; + case JsonNumber jsonNumber: + writer.WriteNumberValue(Encoding.UTF8.GetBytes(jsonNumber.ToString())); + break; + case JsonBoolean jsonBoolean: + writer.WriteBooleanValue(jsonBoolean.Value); + break; + case JsonNull _: + writer.WriteNullValue(); + break; + } + + writer.Flush(); + } + + writer.Flush(); + } + + /// + /// Converts the current instance to string in JSON format. + /// + /// JSON representation of current instance. + public string ToJsonString() + { + var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + WriteTo(writer); + return JsonHelpers.Utf8GetString(stream.ToArray()); + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.TraversalHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.TraversalHelpers.cs new file mode 100644 index 0000000..9448d9a --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.TraversalHelpers.cs @@ -0,0 +1,81 @@ +// 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. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace System.Text.Json +{ + /// + /// The base class that represents a single node within a mutable JSON document. + /// + public abstract partial class JsonNode + { + private static void AddToParent( + KeyValuePair nodePair, + ref Stack> currentNodes, + ref JsonNode toReturn, + DuplicatePropertyNameHandling duplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace) + { + if (currentNodes.Any()) + { + KeyValuePair parentPair = currentNodes.Peek(); + + // Parent needs to be JsonObject or JsonArray + Debug.Assert(parentPair.Value is JsonObject || parentPair.Value is JsonArray); + + if (parentPair.Value is JsonObject jsonObject) + { + Debug.Assert(nodePair.Key != null); + + // Handle duplicate properties accordingly to duplicatePropertyNameHandling: + + if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Replace) + { + jsonObject[nodePair.Key] = nodePair.Value; + } + else if (jsonObject._dictionary.ContainsKey(nodePair.Key)) + { + if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Error) + throw new ArgumentException(SR.JsonObjectDuplicateKey); + + Debug.Assert(duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Ignore); + } + else + { + jsonObject.Add(nodePair); + } + } + else if (parentPair.Value is JsonArray jsonArray) + { + Debug.Assert(nodePair.Key == null); + jsonArray.Add(nodePair.Value); + } + } + 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 + + 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) + { + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs index c075840..b37205a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs @@ -2,14 +2,12 @@ // 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.Diagnostics; - namespace System.Text.Json { /// /// The base class that represents a single node within a mutable JSON document. /// - public abstract class JsonNode + public abstract partial class JsonNode { private protected JsonNode() { } @@ -31,7 +29,10 @@ namespace System.Text.Json /// /// to get the from. /// represented by . - public static JsonNode GetNode(JsonElement jsonElement) => (JsonNode)jsonElement._parent; + /// + /// Provided was not build from . + /// + public static JsonNode GetNode(JsonElement jsonElement) => !jsonElement.IsImmutable ? (JsonNode)jsonElement._parent : throw new ArgumentException(SR.NotNodeJsonElementParent); /// /// Gets the represented by the . @@ -57,67 +58,133 @@ namespace System.Text.Json } /// - /// Parses a string representiong JSON document into . + /// Performs a deep copy operation on this instance. + /// + /// which is a clone of this instance. + public abstract JsonNode Clone(); + + /// + /// Converts a to a . /// - /// JSON to parse. - /// representation of . - public static JsonNode Parse(string json) + /// The value to convert. + public static implicit operator JsonNode(string value) { - using (JsonDocument document = JsonDocument.Parse(json)) + if (value == null) { - return DeepCopy(document.RootElement); + return new JsonNull(); } + + return new JsonString(value); } /// - /// Performs a deep copy operation on this instance. + /// Converts a to a . /// - /// which is a clone of this instance. - public abstract JsonNode Clone(); + /// The value to convert. + public static implicit operator JsonNode(DateTime value) => new JsonString(value); + + /// + /// Converts a to a . + /// + /// The value to convert. + public static implicit operator JsonNode(DateTimeOffset value) => new JsonString(value); + + /// + /// Converts a to a . + /// + /// The value to convert. + public static implicit operator JsonNode(Guid value) => new JsonString(value); + + /// + /// Converts a to a . + /// + /// The value to convert. + public static implicit operator JsonNode(bool value) => new JsonBoolean(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(byte value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(short value) => new JsonNumber(value); + + /// + /// Converts an to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(int value) => new JsonNumber(value); /// - /// Performs a deep copy operation on returning corresponding modifiable tree structure of JSON nodes. - /// Operations performed on returned does not modify . + /// Converts a to a JSON number. /// - /// to copy. - /// representing . - public static JsonNode DeepCopy(JsonElement jsonElement) + /// The value to convert. + public static implicit operator JsonNode(long value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(float value) { - if (!jsonElement.IsImmutable) + if (float.IsInfinity(value) || float.IsNaN(value)) { - return GetNode(jsonElement).Clone(); + return new JsonString(value.ToString()); } - switch (jsonElement.ValueKind) + return new JsonNumber(value); + } + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(double value) + { + if (double.IsInfinity(value) || double.IsNaN(value)) { - case JsonValueKind.Object: - JsonObject jsonObject = new JsonObject(); - foreach (JsonProperty property in jsonElement.EnumerateObject()) - { - jsonObject.Add(property.Name, DeepCopy(property.Value)); - } - return jsonObject; - case JsonValueKind.Array: - JsonArray jsonArray = new JsonArray(); - foreach (JsonElement element in jsonElement.EnumerateArray()) - { - jsonArray.Add(DeepCopy(element)); - } - return jsonArray; - case JsonValueKind.Number: - return new JsonNumber(jsonElement.GetRawText()); - case JsonValueKind.String: - return new JsonString(jsonElement.GetString()); - case JsonValueKind.True: - return new JsonBoolean(true); - case JsonValueKind.False: - return new JsonBoolean(false); - case JsonValueKind.Null: - return null; - default: - Debug.Assert(jsonElement.ValueKind == JsonValueKind.Undefined, "No handler for JsonValueKind.{jsonElement.ValueKind}"); - throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Undefined, jsonElement.ValueKind); + return new JsonString(value.ToString()); } + + return new JsonNumber(value); } + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNode(sbyte value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNode(ushort value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNode(uint value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + [CLSCompliant(false)] + public static implicit operator JsonNode(ulong value) => new JsonNumber(value); + + /// + /// Converts a to a JSON number. + /// + /// The value to convert. + public static implicit operator JsonNode(decimal value) => new JsonNumber(value); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNull.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNull.cs new file mode 100644 index 0000000..82b8e73 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNull.cs @@ -0,0 +1,73 @@ +// 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 +{ + /// + /// Represents the null JSON value. + /// + public sealed class JsonNull : JsonNode, IEquatable + { + /// + /// Initializes a new instance of the class representing the value . + /// + public JsonNull() { } + + /// + /// Converts the null value to the string in JSON format. + /// + /// The string representation of the null value. + public override string ToString() => "null"; + + /// + /// Compares to the value of this instance. + /// + /// The object to compare against. + /// + /// if the is , + /// otherwise. + /// + public override bool Equals(object obj) => obj is JsonNull; + + /// + /// Calculates a hash code of this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() => -1; + + /// + /// Compares other JSON null to the value of this instance. + /// + /// The JSON null to compare against. + /// + public bool Equals(JsonNull other) => true; + + /// + /// Compares values of two JSON nulls. + /// + /// The JSON null to compare. + /// The JSON null to compare. + /// + public static bool operator ==(JsonNull left, JsonNull right) => true; + + /// + /// Compares values of two JSON nulls. + /// + /// The JSON null to compare. + /// The JSON null to compare. + /// + public static bool operator !=(JsonNull left, JsonNull right) => false; + + /// + /// Creates a new JSON null that is a copy of the current instance. + /// + /// A new JSON null that is a copy of this instance. + public override JsonNode Clone() => new JsonNull(); + + /// + /// Returns . + /// + public override JsonValueKind ValueKind { get => JsonValueKind.Null; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs index 041131c..2935e25 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs @@ -549,83 +549,6 @@ namespace System.Text.Json /// The value to represent as a JSON number. public void SetDecimal(decimal value) => _value = value.ToString(CultureInfo.InvariantCulture); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - public static implicit operator JsonNumber(byte value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - public static implicit operator JsonNumber(short value) => new JsonNumber(value); - - /// - /// Converts an to a JSON number. - /// - /// The value to convert. - public static implicit operator JsonNumber(int value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - public static implicit operator JsonNumber(long value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - /// - /// Provided value is not in a legal JSON number format. - /// - public static implicit operator JsonNumber(float value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - /// - /// Provided value is not in a legal JSON number format. - /// - public static implicit operator JsonNumber(double value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - [CLSCompliant(false)] - public static implicit operator JsonNumber(sbyte value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - [CLSCompliant(false)] - public static implicit operator JsonNumber(ushort value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - [CLSCompliant(false)] - public static implicit operator JsonNumber(uint value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - [CLSCompliant(false)] - public static implicit operator JsonNumber(ulong value) => new JsonNumber(value); - - /// - /// Converts a to a JSON number. - /// - /// The value to convert. - public static implicit operator JsonNumber(decimal value) => new JsonNumber(value); - /// /// Compares to the value of this instance. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs index 90e456b..1ce9144 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; namespace System.Text.Json { @@ -13,39 +13,25 @@ namespace System.Text.Json /// public sealed class JsonObject : JsonNode, IEnumerable> { - internal readonly Dictionary _dictionary; - private readonly DuplicatePropertyNameHandling _duplicatePropertyNameHandling; + internal readonly Dictionary _dictionary; + internal JsonObjectProperty _first; + internal JsonObjectProperty _last; + internal int _version; /// /// Initializes a new instance of the class representing the empty object. /// - /// Specifies the way of handling duplicate property names. - /// - /// Provided manner of handling duplicates does not exist. - /// - public JsonObject(DuplicatePropertyNameHandling duplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace) + public JsonObject() { - if ((uint)duplicatePropertyNameHandling > (uint)DuplicatePropertyNameHandling.Error) - { - throw new ArgumentOutOfRangeException(SR.InvalidDuplicatePropertyNameHandling); - } - - _dictionary = new Dictionary(); - _duplicatePropertyNameHandling = duplicatePropertyNameHandling; + _dictionary = new Dictionary(); + _version = 0; } - /// /// Initializes a new instance of the class representing provided set of JSON properties. /// /// >Properties to represent as a JSON object. - /// Specifies the way of handling duplicate property names. - /// - /// Provided collection contains duplicates if handling duplicates is set to . - /// - public JsonObject( - IEnumerable> jsonProperties, - DuplicatePropertyNameHandling duplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace) - : this(duplicatePropertyNameHandling) + public JsonObject(IEnumerable> jsonProperties) + : this() => AddRange(jsonProperties); /// @@ -61,9 +47,20 @@ namespace System.Text.Json set { if (propertyName == null) + { throw new ArgumentNullException(nameof(propertyName)); + } - _dictionary[propertyName] = value; + if (_dictionary.ContainsKey(propertyName)) + { + _dictionary[propertyName].Value = value ?? new JsonNull(); + } + else + { + Add(propertyName, value); + } + + _version++; } } @@ -72,7 +69,7 @@ namespace System.Text.Json /// /// The property to add. /// - /// Property name to set already exists if handling duplicates is set to . + /// Property name to add already exists. /// public void Add(KeyValuePair jsonProperty) => Add(jsonProperty.Key, jsonProperty.Value); @@ -85,11 +82,9 @@ namespace System.Text.Json /// Provided property name is null. /// /// - /// Property name to set already exists if handling duplicates is set to . + /// Property name to add already exists. /// - /// - /// Null property value is allowed and represents a null JSON node. - /// + /// Null value is allowed and will be converted to the instance. public void Add(string propertyName, JsonNode propertyValue) { if (propertyName == null) @@ -99,307 +94,163 @@ namespace System.Text.Json if (_dictionary.ContainsKey(propertyName)) { - switch (_duplicatePropertyNameHandling) - { - case DuplicatePropertyNameHandling.Ignore: - return; - case DuplicatePropertyNameHandling.Error: - throw new ArgumentException(SR.Format(SR.JsonObjectDuplicateKey, propertyName)); - } - - Debug.Assert(_duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Replace); + throw new ArgumentException(SR.Format(SR.JsonObjectDuplicateKey, propertyName)); } - _dictionary[propertyName] = propertyValue; - } - - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided value or property name is null. - /// - public void Add(string propertyName, string propertyValue) => Add(propertyName, new JsonString(propertyValue)); - - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, ReadOnlySpan propertyValue) => Add(propertyName, new JsonString(propertyValue)); - - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, Guid propertyValue) => Add(propertyName, new JsonString(propertyValue)); - - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, DateTime propertyValue) => Add(propertyName, new JsonString(propertyValue)); + // Add property to linked list: + if (_last == null) + { + _last = new JsonObjectProperty(propertyName, propertyValue ?? new JsonNull(), null, null); + _first = _last; + } + else + { + var newJsonObjectProperty = new JsonObjectProperty(propertyName, propertyValue ?? new JsonNull(), _last, null); + _last.Next = newJsonObjectProperty; + _last = newJsonObjectProperty; + } - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, DateTimeOffset propertyValue) => Add(propertyName, new JsonString(propertyValue)); + // Add property to dictionary: + _dictionary[propertyName] = _last; - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, bool propertyValue) => Add(propertyName, new JsonBoolean(propertyValue)); + _version++; + } /// - /// Adds the specified property as a to the JSON object. + /// Adds the properties from the specified collection to the JSON object. /// - /// Name of the property to add. - /// value of the property to add. + /// Properties to add. /// - /// Property name to set already exists if handling duplicates is set to . + /// Provided collection contains duplicates. /// /// - /// Provided property name is null. + /// Some of property names are null. /// - public void Add(string propertyName, byte propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + public void AddRange(IEnumerable> jsonProperties) + { + foreach (KeyValuePair property in jsonProperties) + { + Add(property); + } + } /// - /// Adds the specified property as a to the JSON object. + /// Removes the property with the specified name. /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// + /// >Name of a property to remove. + /// + /// if the property is successfully found in a JSON object and removed, + /// otherwise. + /// /// /// Provided property name is null. /// - public void Add(string propertyName, short propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + public bool Remove(string propertyName) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, int propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); +#if BUILDING_INBOX_LIBRARY + if (_dictionary.Remove(propertyName, out JsonObjectProperty value)) + { + AdjustLinkedListPointers(value); + _version++; - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, long propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + return true; + } +#else + if (_dictionary.TryGetValue(propertyName, out JsonObjectProperty value)) + { + AdjustLinkedListPointers(value); + _dictionary.Remove(propertyName); + _version++; - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided value is not in a legal JSON number format. - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, float propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + return true; + } +#endif - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided value is not in a legal JSON number format. - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, double propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + return false; + } /// - /// Adds the specified property as a to the JSON object. + /// Removes the property with the specified name. /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// + /// >Name of a property to remove. + /// The culture and case to be used when comparing string value. + /// + /// if the property is successfully found in a JSON object and removed, + /// otherwise. + /// /// /// Provided property name is null. /// - [CLSCompliant(false)] - public void Add(string propertyName, sbyte propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + public bool Remove(string propertyName, StringComparison stringComparison) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - [CLSCompliant(false)] - public void Add(string propertyName, ushort propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + JsonObjectProperty _current = _first; - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - [CLSCompliant(false)] - public void Add(string propertyName, uint propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + while (_current != null && !string.Equals(_current.Name, propertyName, stringComparison)) + { + _current = _current.Next; + } - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - [CLSCompliant(false)] - public void Add(string propertyName, ulong propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + if (_current != null) + { + AdjustLinkedListPointers(_current); + _dictionary.Remove(_current.Name); + return true; + } - /// - /// Adds the specified property as a to the JSON object. - /// - /// Name of the property to add. - /// value of the property to add. - /// - /// Property name to set already exists if handling duplicates is set to . - /// - /// - /// Provided property name is null. - /// - public void Add(string propertyName, decimal propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + return false; + } - /// - /// Adds the properties from the specified collection to the JSON object. - /// - /// Properties to add. - /// - /// Provided collection contains duplicates if handling duplicates is set to . - /// - /// - /// Some of property names are null. - /// - public void AddRange(IEnumerable> jsonProperties) + private void AdjustLinkedListPointers(JsonObjectProperty propertyToRemove) { - foreach (KeyValuePair property in jsonProperties) + // Adjust linked list pointers: + + if (propertyToRemove.Prev == null) { - Add(property); + _first = propertyToRemove.Next; + } + else + { + propertyToRemove.Prev.Next = propertyToRemove.Next; } - } - /// - /// Adds the property values from the specified collection as a property to the JSON object. - /// - /// Name of the property to add. - /// Properties to add. - /// - /// Provided collection contains duplicates if handling duplicates is set to . - /// - /// - /// Some of property names are null. - /// - public void Add(string propertyName, IEnumerable propertyValues) - { - var jsonArray = new JsonArray(); - foreach (JsonNode value in propertyValues) + if (propertyToRemove.Next == null) + { + _last = propertyToRemove.Prev; + } + else { - jsonArray.Add(value); + propertyToRemove.Next.Prev = propertyToRemove.Prev; } - Add(propertyName, (JsonNode)jsonArray); } /// - /// Removes the property with the specified name. + /// Determines whether a property is in a JSON object. /// - /// + /// Name of the property to check. /// - /// if the property is successfully found in a JSON object and removed, + /// if the property is successfully found in a JSON object, /// otherwise. /// /// /// Provided property name is null. /// - public bool Remove(string propertyName) => propertyName != null ? _dictionary.Remove(propertyName) : throw new ArgumentNullException(nameof(propertyName)); + public bool ContainsProperty(string propertyName) => propertyName != null ? _dictionary.ContainsKey(propertyName) : throw new ArgumentNullException(nameof(propertyName)); /// /// Determines whether a property is in a JSON object. /// /// Name of the property to check. + /// The culture and case to be used when comparing string value. /// /// if the property is successfully found in a JSON object, /// otherwise. @@ -407,7 +258,18 @@ namespace System.Text.Json /// /// Provided property name is null. /// - public bool ContainsProperty(string propertyName) => propertyName != null ? _dictionary.ContainsKey(propertyName) : throw new ArgumentNullException(nameof(propertyName)); + public bool ContainsProperty(string propertyName, StringComparison stringComparison) + { + foreach (KeyValuePair property in this) + { + if (string.Equals(property.Key, propertyName, stringComparison)) + { + return true; + } + } + + return false; + } /// /// Returns the value of a property with the specified name. @@ -431,6 +293,50 @@ namespace System.Text.Json /// Returns the value of a property with the specified name. /// /// Name of the property to return. + /// The culture and case to be used when comparing string value. + /// Value of the property with the specified name. + /// + /// Property with specified name is not found in JSON object. + /// + public JsonNode GetPropertyValue(string propertyName, StringComparison stringComparison) + { + if (!TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode)) + { + throw new KeyNotFoundException(SR.Format(SR.PropertyNotFound, propertyName)); + } + + return jsonNode; + } + + /// + /// Returns the value of a property with the specified name. + /// + /// Name of the property to return. + /// Value of the property with specified name. + /// + /// if property with specified name was found; + /// otherwise, + /// + /// + /// When returns , the value of is meaningless. + /// + public bool TryGetPropertyValue(string propertyName, out JsonNode jsonNode) + { + if (_dictionary.TryGetValue(propertyName, out JsonObjectProperty jsonObjectProperty)) + { + jsonNode = jsonObjectProperty.Value; + return true; + } + + jsonNode = null; + return false; + } + + /// + /// Returns the value of a property with the specified name. + /// + /// Name of the property to return. + /// The culture and case to be used when comparing string value. /// Value of the property with specified name. /// /// if property with specified name was found; @@ -438,9 +344,21 @@ namespace System.Text.Json /// /// /// When returns , the value of is meaningless. - /// Null doesn't mean the property value was "null" unless is returned. /// - public bool TryGetPropertyValue(string propertyName, out JsonNode jsonNode) => _dictionary.TryGetValue(propertyName, out jsonNode); + public bool TryGetPropertyValue(string propertyName, StringComparison stringComparison, out JsonNode jsonNode) + { + foreach (KeyValuePair property in this) + { + if (string.Equals(property.Key, propertyName, stringComparison)) + { + jsonNode = property.Value; + return true; + } + } + + jsonNode = null; + return false; + } /// /// Returns the JSON object value of a property with the specified name. @@ -467,6 +385,28 @@ namespace System.Text.Json /// Returns the JSON object value of a property with the specified name. /// /// Name of the property to return. + /// The culture and case to be used when comparing string value. + /// JSON objectvalue of a property with the specified name. + /// + /// Property with specified name is not found in JSON object. + /// + /// + /// Property with specified name is not a JSON object. + /// + public JsonObject GetJsonObjectPropertyValue(string propertyName, StringComparison stringComparison) + { + if (GetPropertyValue(propertyName, stringComparison) is JsonObject jsonObject) + { + return jsonObject; + } + + throw new ArgumentException(SR.Format(SR.PropertyTypeMismatch, propertyName)); + } + + /// + /// Returns the JSON object value of a property with the specified name. + /// + /// Name of the property to return. /// JSON object value of the property with specified name. /// /// if JSON object property with specified name was found; @@ -485,6 +425,28 @@ namespace System.Text.Json } /// + /// Returns the JSON object value of a property with the specified name. + /// + /// Name of the property to return. + /// The culture and case to be used when comparing string value. + /// JSON object value of the property with specified name. + /// + /// if JSON object property with specified name was found; + /// otherwise, + /// + public bool TryGetJsonObjectPropertyValue(string propertyName, StringComparison stringComparison, out JsonObject jsonObject) + { + if (TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode)) + { + jsonObject = jsonNode as JsonObject; + return jsonObject != null; + } + + jsonObject = null; + return false; + } + + /// /// Returns the JSON array value of a property with the specified name. /// /// Name of the property to return. @@ -509,6 +471,28 @@ namespace System.Text.Json /// Returns the JSON array value of a property with the specified name. /// /// Name of the property to return. + /// The culture and case to be used when comparing string value. + /// JSON objectvalue of a property with the specified name. + /// + /// Property with specified name is not found in JSON array. + /// + /// + /// Property with specified name is not a JSON array. + /// + public JsonArray GetJsonArrayPropertyValue(string propertyName, StringComparison stringComparison) + { + if (GetPropertyValue(propertyName, stringComparison) is JsonArray jsonArray) + { + return jsonArray; + } + + throw new ArgumentException(SR.Format(SR.PropertyTypeMismatch, propertyName)); + } + + /// + /// Returns the JSON array value of a property with the specified name. + /// + /// Name of the property to return. /// JSON array value of the property with specified name. /// /// if JSON array property with specified name was found; @@ -527,40 +511,53 @@ namespace System.Text.Json } /// + /// Returns the JSON array value of a property with the specified name. + /// + /// Name of the property to return. + /// The culture and case to be used when comparing string value. + /// JSON array value of the property with specified name. + /// + /// if JSON array property with specified name was found; + /// otherwise, + /// + public bool TryGetJsonArrayPropertyValue(string propertyName, StringComparison stringComparison, out JsonArray jsonArray) + { + if (TryGetPropertyValue(propertyName, stringComparison, out JsonNode jsonNode)) + { + jsonArray = jsonNode as JsonArray; + return jsonArray != null; + } + + jsonArray = null; + return false; + } + + /// /// A collection containing the property names of JSON object. /// - public ICollection PropertyNames => _dictionary.Keys; + public IReadOnlyCollection GetPropertyNames() => _dictionary.Keys; /// /// A collection containing the property values of JSON object. /// - public ICollection PropertyValues => _dictionary.Values; + public IReadOnlyCollection GetPropertyValues() => _dictionary.Values.Select(jsonObjectProperty => jsonObjectProperty.Value).ToList(); /// /// Returns an enumerator that iterates through the JSON object properties. /// /// An enumerator structure for the . - /// - /// Property name to set already exists if handling duplicates is set to . - /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Returns an enumerator that iterates through the JSON object properties. /// /// An enumerator structure for the JSON object. - /// - /// Property name to set already exists if handling duplicates is set to . - /// IEnumerator> IEnumerable>.GetEnumerator() => new JsonObjectEnumerator(this); /// /// Returns an enumerator that iterates through the JSON object properties. /// /// An enumerator structure for the JSON object. - /// - /// Property name to set already exists if handling duplicates is set to . - /// public JsonObjectEnumerator GetEnumerator() => new JsonObjectEnumerator(this); /// @@ -569,9 +566,9 @@ namespace System.Text.Json /// A new JSON object that is a copy of this instance. public override JsonNode Clone() { - var jsonObject = new JsonObject(_duplicatePropertyNameHandling); + var jsonObject = new JsonObject(); - foreach (KeyValuePair property in _dictionary) + foreach (KeyValuePair property in this) { jsonObject.Add(property.Key, property.Value.Clone()); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs index 9b7a7b7..856c3be 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs @@ -8,38 +8,74 @@ namespace System.Text.Json /// public struct JsonObjectEnumerator : IEnumerator> { - private Dictionary.Enumerator _enumerator; + private JsonObjectProperty _first; + private JsonObjectProperty _current; /// /// Initializes a new instance of the class supporting an interation over provided JSON object. /// /// JSON object to iterate over. - public JsonObjectEnumerator(JsonObject jsonObject) => _enumerator = jsonObject._dictionary.GetEnumerator(); + public JsonObjectEnumerator(JsonObject jsonObject) + { + _first = jsonObject._first; + _current = null; + } /// /// Gets the property in the JSON object at the current position of the enumerator. /// - public KeyValuePair Current => _enumerator.Current; + public KeyValuePair Current + { + get + { + if (_current == null) + { + return default; + } + + return new KeyValuePair(_current.Name, _current.Value); + } + } /// /// Gets the property in the JSON object at the current position of the enumerator. /// - object IEnumerator.Current => _enumerator.Current; + object IEnumerator.Current => Current; /// /// Releases all resources used by the . /// - public void Dispose() => _enumerator.Dispose(); + public void Dispose() => _current = null; /// /// Advances the enumerator to the next property of the JSON object. /// /// - public bool MoveNext() => _enumerator.MoveNext(); + public bool MoveNext() + { + if (_first == null) + { + return false; + } + + if (_current == null) + { + _current = _first; + return true; + } + + if (_current.Next != null) + { + _current = _current.Next; + return true; + } + + return false; + } /// /// Sets the enumerator to its initial position, which is before the first element in the JSON object. /// - void IEnumerator.Reset() => ((IEnumerator)_enumerator).Reset(); + void IEnumerator.Reset() => _current = null; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectProperty.cs new file mode 100644 index 0000000..7e40518 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectProperty.cs @@ -0,0 +1,22 @@ +// 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 +{ + internal class JsonObjectProperty + { + internal string Name { get; } + internal JsonNode Value { get; set; } + internal JsonObjectProperty Prev { get; set; } + internal JsonObjectProperty Next { get; set; } + + public JsonObjectProperty(string name, JsonNode value, JsonObjectProperty prev, JsonObjectProperty next) + { + Name = name; + Value = value; + Prev = prev; + Next = next; + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs index de5538f..f1c7b6a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs @@ -140,12 +140,6 @@ namespace System.Text.Json public bool TryGetGuid(out Guid value) => Guid.TryParseExact(_value, "D", out value); /// - /// Converts a to a . - /// - /// The value to convert. - public static implicit operator JsonString(string value) => new JsonString(value); - - /// /// Compares to the value of this instance. /// /// The object to compare against. diff --git a/src/libraries/System.Text.Json/tests/JsonArrayTests.cs b/src/libraries/System.Text.Json/tests/JsonArrayTests.cs index 89ff9b6..d9be2d4 100644 --- a/src/libraries/System.Text.Json/tests/JsonArrayTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonArrayTests.cs @@ -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.Collections; using System.Collections.Generic; using System.Linq; using Xunit; @@ -13,9 +14,6 @@ namespace System.Text.Json.Tests private static void TestArray(T value1, T value2, Func getter, Func nodeCtor) { - var value1Casted = value1 as dynamic; - var value2Casted = value2 as dynamic; - JsonNode value1Node = nodeCtor(value1); JsonNode value2Node = nodeCtor(value2); @@ -42,7 +40,7 @@ namespace System.Text.Json.Tests Assert.Equal(0, jsonArray.Count); - jsonArray.Add(value2Casted); + jsonArray.Add(value2 as dynamic); Assert.Equal(1, jsonArray.Count); Assert.True(jsonArray.Contains(value2Node)); Assert.Equal(value2, getter(jsonArray)); @@ -193,8 +191,53 @@ namespace System.Text.Json.Tests for (int i = 0; i < dishesJsonArray.Count; i++) { - Assert.IsType(dishesJsonArray[i]); - Assert.Equal(expected[i], dishesJsonArray[i] as JsonString); + Assert.Equal(expected[i], ((JsonString)dishesJsonArray[i]).Value); + } + } + + [Fact] + public static void TestCreatingJsonArrayFromIEnumerableOfStrings() + { + var sportsExperienceYears = new JsonObject() + { + { "skiing", 5 }, + { "cycling", 8 }, + { "hiking", 6 }, + { "chess", 2 }, + { "skating", 1 }, + }; + + // choose only sports with > 2 experience years + IEnumerable sports = sportsExperienceYears.Where(sport => ((JsonNumber)sport.Value).GetInt32() > 2).Select(sport => sport.Key); + + var sportsJsonArray = new JsonArray(sports); + Assert.Equal(3, sportsJsonArray.Count); + + for (int i = 0; i < sportsJsonArray.Count; i++) + { + Assert.Equal(sports.ElementAt(i), ((JsonString)sportsJsonArray[i]).Value); + } + } + + [Fact] + public static void TestCreatingJsonArrayFromIEnumerableOfJsonNodes() + { + var strangeWords = new JsonArray() + { + "supercalifragilisticexpialidocious", + "gladiolus", + "albumen", + "smaragdine" + }; + + var strangeWordsJsonArray = new JsonArray(strangeWords.Where(word => ((JsonString)word).Value.Length < 10)); + Assert.Equal(2, strangeWordsJsonArray.Count); + + string[] expected = { "gladiolus", "albumen" }; + + for (int i = 0; i < strangeWordsJsonArray.Count; i++) + { + Assert.Equal(expected[i], ((JsonString)strangeWordsJsonArray[i]).Value); } } @@ -231,9 +274,16 @@ namespace System.Text.Json.Tests }, }; - var innerJsonArray = (JsonArray)vertices[0]; - innerJsonArray = (JsonArray)innerJsonArray[0]; - Assert.IsType(innerJsonArray[0]); + var jsonArray = (JsonArray)vertices[0]; + Assert.Equal(2, jsonArray.Count()); + jsonArray = (JsonArray)jsonArray[1]; + Assert.Equal(2, jsonArray.Count()); + jsonArray = (JsonArray)jsonArray[0]; + Assert.Equal(3, jsonArray.Count()); + + Assert.Equal(0, jsonArray[0]); + Assert.Equal(1, jsonArray[1]); + Assert.Equal(0, jsonArray[2]); } [Fact] @@ -284,22 +334,56 @@ namespace System.Text.Json.Tests } [Fact] + public static void TestHandlingNulls() + { + var jsonArray = new JsonArray() { "to be replaced" }; + + jsonArray[0] = null; + Assert.Equal(1, jsonArray.Count()); + Assert.IsType(jsonArray[0]); + + jsonArray.Add(null); + Assert.Equal(2, jsonArray.Count()); + Assert.IsType(jsonArray[1]); + + jsonArray.Add(new JsonNull()); + Assert.Equal(3, jsonArray.Count()); + Assert.IsType(jsonArray[2]); + + jsonArray.Insert(3, null); + Assert.Equal(4, jsonArray.Count()); + Assert.IsType(jsonArray[3]); + + jsonArray.Insert(4, new JsonNull()); + Assert.Equal(5, jsonArray.Count()); + Assert.IsType(jsonArray[4]); + + Assert.True(jsonArray.Contains(null)); + + Assert.Equal(0, jsonArray.IndexOf(null)); + Assert.Equal(4, jsonArray.LastIndexOf(null)); + + jsonArray.Remove(null); + Assert.Equal(4, jsonArray.Count()); + } + + [Fact] public static void TestAccesingNestedJsonArrayGetPropertyMethod() { var issues = new JsonObject() { - { "features", new JsonString [] { "new functionality 1", "new functionality 2" } }, - { "bugs", new JsonString [] { "bug 123", "bug 4566", "bug 821" } }, - { "tests", new JsonString [] { "code coverage" } }, + { "features", new JsonArray { "new functionality 1", "new functionality 2" } }, + { "bugs", new JsonArray { "bug 123", "bug 4566", "bug 821" } }, + { "tests", new JsonArray { "code coverage" } }, }; issues.GetJsonArrayPropertyValue("bugs").Add("bug 12356"); ((JsonString)issues.GetJsonArrayPropertyValue("features")[0]).Value = "feature 1569"; ((JsonString)issues.GetJsonArrayPropertyValue("features")[1]).Value = "feature 56134"; - Assert.Equal((JsonString)"bug 12356", ((JsonArray)issues["bugs"])[3]); - Assert.Equal("feature 1569", (JsonString)((JsonArray)issues["features"])[0]); - Assert.Equal("feature 56134", (JsonString)((JsonArray)issues["features"])[1]); + Assert.Equal("bug 12356", ((JsonString)((JsonArray)issues["bugs"])[3]).Value); + Assert.Equal("feature 1569", ((JsonString)((JsonArray)issues["features"])[0]).Value); + Assert.Equal("feature 56134", ((JsonString)((JsonArray)issues["features"])[1]).Value); } [Fact] @@ -307,12 +391,59 @@ namespace System.Text.Json.Tests { var issues = new JsonObject() { - { "features", new JsonString [] { "new functionality 1", "new functionality 2" } }, + { "features", new JsonArray { "new functionality 1", "new functionality 2" } }, }; Assert.True(issues.TryGetJsonArrayPropertyValue("features", out JsonArray featuresArray)); - Assert.Equal("new functionality 1", (JsonString)featuresArray[0]); - Assert.Equal("new functionality 2", (JsonString)featuresArray[1]); + Assert.Equal("new functionality 1", ((JsonString)featuresArray[0]).Value); + Assert.Equal("new functionality 2", ((JsonString)featuresArray[1]).Value); + } + + [Fact] + public static void TestInsert() + { + var jsonArray = new JsonArray() { 1 }; + Assert.Equal(1, jsonArray.Count); + + jsonArray.Insert(0, 0); + + Assert.Equal(2, jsonArray.Count); + Assert.Equal(0, jsonArray[0]); + Assert.Equal(1, jsonArray[1]); + + jsonArray.Insert(2, 3); + + Assert.Equal(3, jsonArray.Count); + Assert.Equal(0, jsonArray[0]); + Assert.Equal(1, jsonArray[1]); + Assert.Equal(3, jsonArray[2]); + + jsonArray.Insert(2, 2); + + Assert.Equal(4, jsonArray.Count); + Assert.Equal(0, jsonArray[0]); + Assert.Equal(1, jsonArray[1]); + Assert.Equal(2, jsonArray[2]); + Assert.Equal(3, jsonArray[3]); + } + + [Fact] + public static void TestHeterogeneousArray() + { + var mixedTypesArray = new JsonArray { 1, "value", true }; + + Assert.Equal(1, mixedTypesArray[0]); + Assert.Equal("value", mixedTypesArray[1]); + Assert.Equal(true, mixedTypesArray[2]); + + mixedTypesArray.Add(17); + mixedTypesArray.Insert(4, "another"); + mixedTypesArray.Add(new JsonNull()); + + Assert.Equal(17, mixedTypesArray[3]); + Assert.Equal("another", mixedTypesArray[4]); + Assert.IsType(mixedTypesArray[5]); + } [Fact] @@ -324,6 +455,16 @@ namespace System.Text.Json.Tests Assert.Throws(() => new JsonArray()[0] = new JsonString()); Assert.Throws(() => new JsonArray()[1]); Assert.Throws(() => new JsonArray()[1] = new JsonString()); + Assert.Throws(() => + { + var jsonArray = new JsonArray { 1, 2, 3 }; + jsonArray.Insert(4, 17); + }); + Assert.Throws(() => + { + var jsonArray = new JsonArray { 1, 2, 3 }; + jsonArray.Insert(-1, 17); + }); } [Fact] @@ -333,17 +474,17 @@ namespace System.Text.Json.Tests } [Fact] - public static void TestClean() + public static void TestClear() { var jsonArray = new JsonArray { 1, 2, 3 }; - + Assert.Equal(3, jsonArray.Count); - Assert.Equal((JsonNumber)1, jsonArray[0]); - Assert.Equal((JsonNumber)2, jsonArray[1]); - Assert.Equal((JsonNumber)3, jsonArray[2]); + Assert.Equal(1, ((JsonNumber)jsonArray[0]).GetInt32()); + Assert.Equal(2, ((JsonNumber)jsonArray[1]).GetInt32()); + Assert.Equal(3, ((JsonNumber)jsonArray[2]).GetInt32()); jsonArray.Clear(); - + Assert.Equal(0, jsonArray.Count); } @@ -353,14 +494,84 @@ namespace System.Text.Json.Tests var jsonArray = new JsonArray { 1, 2, 3 }; Assert.Equal(3, jsonArray.Count); - Assert.Equal((JsonNumber)1, jsonArray[0]); - Assert.Equal((JsonNumber)2, jsonArray[1]); - Assert.Equal((JsonNumber)3, jsonArray[2]); + Assert.Equal(1, ((JsonNumber)jsonArray[0]).GetInt32()); + Assert.Equal(2, ((JsonNumber)jsonArray[1]).GetInt32()); + Assert.Equal(3, ((JsonNumber)jsonArray[2]).GetInt32()); jsonArray.RemoveAll(v => ((JsonNumber)v).GetInt32() <= 2); Assert.Equal(1, jsonArray.Count); - Assert.Equal((JsonNumber)3, jsonArray[0]); - } + Assert.Equal(3, ((JsonNumber)jsonArray[0]).GetInt32()); + } + + [Fact] + public static void TestValueKind() + { + Assert.Equal(JsonValueKind.Array, new JsonArray().ValueKind); + } + + [Fact] + public static void TestJsonArrayIEnumerator() + { + var jsonArray = new JsonArray() { 1, "value" }; + + // Test generic IEnumerator: + IEnumerator jsonArrayEnumerator = new JsonArrayEnumerator(jsonArray); + + Assert.Null(jsonArrayEnumerator.Current); + + jsonArrayEnumerator.MoveNext(); + Assert.Equal(1, jsonArrayEnumerator.Current); + jsonArrayEnumerator.MoveNext(); + Assert.Equal("value", jsonArrayEnumerator.Current); + + jsonArrayEnumerator.Reset(); + + jsonArrayEnumerator.MoveNext(); + Assert.Equal(1, jsonArrayEnumerator.Current); + jsonArrayEnumerator.MoveNext(); + Assert.Equal("value", jsonArrayEnumerator.Current); + + // Test non-generic IEnumerator: + IEnumerator jsonArrayEnumerator2 = new JsonArrayEnumerator(jsonArray); + + Assert.Null(jsonArrayEnumerator2.Current); + + jsonArrayEnumerator2.MoveNext(); + Assert.Equal((JsonNumber)1, jsonArrayEnumerator2.Current); + jsonArrayEnumerator2.MoveNext(); + Assert.Equal((JsonString)"value", jsonArrayEnumerator2.Current); + + jsonArrayEnumerator2.Reset(); + + jsonArrayEnumerator2.MoveNext(); + Assert.Equal((JsonNumber)1, jsonArrayEnumerator2.Current); + jsonArrayEnumerator2.MoveNext(); + Assert.Equal((JsonString)"value", jsonArrayEnumerator2.Current); + } + + [Fact] + public static void TestGetJsonArrayIEnumerable() + { + IEnumerable jsonArray = new JsonArray() { 1, "value" }; + IEnumerator jsonArrayEnumerator = jsonArray.GetEnumerator(); + + Assert.Null(jsonArrayEnumerator.Current); + + jsonArrayEnumerator.MoveNext(); + Assert.Equal((JsonNumber)1, jsonArrayEnumerator.Current); + jsonArrayEnumerator.MoveNext(); + Assert.Equal((JsonString)"value", jsonArrayEnumerator.Current); + } + + [Fact] + public static void TestJsonArrayEmptyArrayEnumerator() + { + var jsonArray = new JsonArray(); + var jsonArrayEnumerator = new JsonArrayEnumerator(jsonArray); + + Assert.Null(jsonArrayEnumerator.Current); + Assert.False(jsonArrayEnumerator.MoveNext()); + } } } diff --git a/src/libraries/System.Text.Json/tests/JsonBooleanTests.cs b/src/libraries/System.Text.Json/tests/JsonBooleanTests.cs index 586aa6e..f2f072b 100644 --- a/src/libraries/System.Text.Json/tests/JsonBooleanTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonBooleanTests.cs @@ -28,10 +28,12 @@ namespace System.Text.Json.Tests [Fact] public static void TestImplicitOperator() { - JsonBoolean jsonBoolean = true; + JsonNode jsonNode = true; + JsonBoolean jsonBoolean = (JsonBoolean)jsonNode; Assert.True(jsonBoolean.Value); - jsonBoolean = false; + jsonNode = false; + jsonBoolean = (JsonBoolean)jsonNode; Assert.False(jsonBoolean.Value); } @@ -87,17 +89,17 @@ namespace System.Text.Json.Tests Assert.False(jsonBooleanTrue.Equals(null)); - object jsonNumberCopyTrue = jsonBooleanTrue; - object jsonNumberObjectTrue = new JsonBoolean(true); - Assert.True(jsonNumberCopyTrue.Equals(jsonNumberObjectTrue)); - Assert.True(jsonNumberCopyTrue.Equals(jsonNumberObjectTrue)); - Assert.True(jsonNumberObjectTrue.Equals(jsonBooleanTrue)); + object jsonBooleanCopyTrue = jsonBooleanTrue; + object jsonBooleanObjectTrue = new JsonBoolean(true); + Assert.True(jsonBooleanCopyTrue.Equals(jsonBooleanObjectTrue)); + Assert.True(jsonBooleanCopyTrue.Equals(jsonBooleanObjectTrue)); + Assert.True(jsonBooleanObjectTrue.Equals(jsonBooleanTrue)); - object jsonNumberCopyFalse = jsonBooleanFalse; - object jsonNumberObjectFalse = new JsonBoolean(false); - Assert.True(jsonNumberCopyFalse.Equals(jsonNumberObjectFalse)); - Assert.True(jsonNumberCopyFalse.Equals(jsonBooleanFalse)); - Assert.True(jsonNumberObjectFalse.Equals(jsonBooleanFalse)); + object jsonBooleanCopyFalse = jsonBooleanFalse; + object jsonBooleanObjectFalse = new JsonBoolean(false); + Assert.True(jsonBooleanCopyFalse.Equals(jsonBooleanObjectFalse)); + Assert.True(jsonBooleanCopyFalse.Equals(jsonBooleanFalse)); + Assert.True(jsonBooleanObjectFalse.Equals(jsonBooleanFalse)); Assert.False(jsonBooleanTrue.Equals(new Exception())); @@ -138,18 +140,25 @@ namespace System.Text.Json.Tests IEquatable jsonBooleanIEquatableFalse = jsonBooleanFalse; Assert.Equal(jsonBooleanIEquatableFalse.GetHashCode(), jsonBooleanFalse.GetHashCode()); - object jsonNumberCopyTrue = jsonBooleanTrue; - object jsonNumberObjectTrue = new JsonBoolean(true); - Assert.Equal(jsonBooleanTrue.GetHashCode(), jsonNumberCopyTrue.GetHashCode()); - Assert.Equal(jsonBooleanTrue.GetHashCode(), jsonNumberObjectTrue.GetHashCode()); + object jsonBooleanCopyTrue = jsonBooleanTrue; + object jsonBooleanObjectTrue = new JsonBoolean(true); + Assert.Equal(jsonBooleanTrue.GetHashCode(), jsonBooleanCopyTrue.GetHashCode()); + Assert.Equal(jsonBooleanTrue.GetHashCode(), jsonBooleanObjectTrue.GetHashCode()); - object jsonNumberCopyFalse = jsonBooleanFalse; - object jsonNumberObjectFalse = new JsonBoolean(false); - Assert.Equal(jsonNumberCopyFalse.GetHashCode(), jsonBooleanFalse.GetHashCode()); - Assert.Equal(jsonNumberObjectFalse.GetHashCode(), jsonBooleanFalse.GetHashCode()); + object jsonBooleanCopyFalse = jsonBooleanFalse; + object jsonBooleanObjectFalse = new JsonBoolean(false); + Assert.Equal(jsonBooleanCopyFalse.GetHashCode(), jsonBooleanFalse.GetHashCode()); + Assert.Equal(jsonBooleanObjectFalse.GetHashCode(), jsonBooleanFalse.GetHashCode()); Assert.Equal(0, jsonBooleanFalse.GetHashCode()); Assert.Equal(1, jsonBooleanTrue.GetHashCode()); } + + [Fact] + public static void TestValueKind() + { + Assert.Equal(JsonValueKind.True, new JsonBoolean(true).ValueKind); + Assert.Equal(JsonValueKind.False, new JsonBoolean(false).ValueKind); + } } } diff --git a/src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs b/src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs new file mode 100644 index 0000000..52fb218 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs @@ -0,0 +1,295 @@ +// 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. + +using System.Globalization; +using System.IO; +using Xunit; + +namespace System.Text.Json.Tests +{ + public static class JsonElementWithNodeParentTests + { + [Fact] + public static void TestArray() + { + var jsonArray = new JsonArray() { 1, 2, 3 }; + JsonElement jsonArrayElement = jsonArray.AsJsonElement(); + + Assert.Equal(2, jsonArrayElement[1].GetInt32()); + Assert.Equal(3, jsonArrayElement.GetArrayLength()); + + var notJsonArray = new JsonString(); + JsonElement notJsonArrayElement = notJsonArray.AsJsonElement(); + + Assert.Throws(() => notJsonArrayElement[1]); + Assert.Throws(() => notJsonArrayElement.GetArrayLength()); + } + + [Fact] + public static void TestArrayEnumerator() + { + var jsonArray = new JsonArray() { 1, 2, 3 }; + JsonElement jsonArrayElement = jsonArray.AsJsonElement(); + + JsonElement.ArrayEnumerator arrayEnumerator = jsonArrayElement.EnumerateArray(); + + for (int i = 1; i <= 3; i++) + { + Assert.True(arrayEnumerator.MoveNext()); + Assert.Equal(i, arrayEnumerator.Current.GetInt32()); + } + + Assert.False(arrayEnumerator.MoveNext()); + Assert.False(arrayEnumerator.MoveNext()); + + JsonElement notArray = new JsonObject().AsJsonElement(); + Assert.Throws(() => notArray.EnumerateArray()); + } + + [Fact] + public static void TestObject() + { + var jsonObject = new JsonObject() + { + ["existing property"] = "value", + ["different property"] = 14 + }; + + JsonElement jsonObjectElement = jsonObject.AsJsonElement(); + Assert.True(jsonObjectElement.TryGetProperty("existing property", out JsonElement propertyValue)); + Assert.Equal("value", propertyValue.GetString()); + Assert.True(jsonObjectElement.TryGetProperty(Encoding.UTF8.GetBytes("existing property"), out propertyValue)); + Assert.Equal("value", propertyValue.GetString()); + + Assert.Throws(() => propertyValue.TryGetProperty("property of not JsonObject", out _)); + Assert.Throws(() => propertyValue.TryGetProperty(Encoding.UTF8.GetBytes("property of not JsonObject"), out _)); + + Assert.False(jsonObjectElement.TryGetProperty("not existing property", out propertyValue)); + Assert.Equal(default, propertyValue); + + Assert.False(jsonObjectElement.TryGetProperty(Encoding.UTF8.GetBytes("not existing property"), out propertyValue)); + Assert.Equal(default, propertyValue); + } + + [Fact] + public static void TestObjectEnumerator() + { + var jsonObject = new JsonObject() + { + ["1"] = 1, + ["2"] = 2, + ["3"] = 3 + }; + JsonElement jsonObjectElement = jsonObject.AsJsonElement(); + + JsonElement.ObjectEnumerator objectEnumerator = jsonObjectElement.EnumerateObject(); + + for (int i = 1; i <= 3; i++) + { + Assert.True(objectEnumerator.MoveNext()); + Assert.Equal(i.ToString(), objectEnumerator.Current.Name); + Assert.Equal(i, objectEnumerator.Current.Value.GetInt32()); + } + + Assert.False(objectEnumerator.MoveNext()); + Assert.False(objectEnumerator.MoveNext()); + + jsonObject["2"] = 4; + Assert.Throws(() => objectEnumerator.MoveNext()); + + + JsonElement notObject = new JsonArray().AsJsonElement(); + Assert.Throws(() => notObject.EnumerateObject()); + } + + [Fact] + public static void TestBoolean() + { + Assert.True(new JsonBoolean(true).AsJsonElement().GetBoolean()); + Assert.Throws(() => new JsonString().AsJsonElement().GetBoolean()); + } + + [Fact] + public static void TestString() + { + Assert.Equal("value", new JsonString("value").AsJsonElement().GetString()); + Assert.Throws(() => new JsonBoolean().AsJsonElement().GetString()); + } + + [Fact] + public static void TestByte() + { + Assert.Equal(byte.MaxValue, new JsonNumber(byte.MaxValue).AsJsonElement().GetByte()); + Assert.Throws(() => new JsonString().AsJsonElement().GetByte()); + } + + [Fact] + public static void TestInt16() + { + Assert.Equal(short.MaxValue, new JsonNumber(short.MaxValue).AsJsonElement().GetInt16()); + Assert.Throws(() => new JsonString().AsJsonElement().GetInt16()); + } + + [Fact] + public static void TestInt32() + { + Assert.Equal(int.MaxValue, new JsonNumber(int.MaxValue).AsJsonElement().GetInt32()); + Assert.Throws(() => new JsonString().AsJsonElement().GetInt32()); + } + + [Fact] + public static void TestInt64() + { + Assert.Equal(long.MaxValue, new JsonNumber(long.MaxValue).AsJsonElement().GetInt64()); + Assert.Throws(() => new JsonString().AsJsonElement().GetInt64()); + } + + [Fact] + public static void TestSingle() + { + Assert.Equal(3.14f, new JsonNumber(3.14f).AsJsonElement().GetSingle()); + Assert.Throws(() => new JsonString().AsJsonElement().GetSingle()); + } + + [Fact] + public static void TestDouble() + { + Assert.Equal(3.14, new JsonNumber(3.14).AsJsonElement().GetDouble()); + Assert.Throws(() => new JsonString().AsJsonElement().GetDouble()); + } + + [Fact] + public static void TestSByte() + { + Assert.Equal(sbyte.MaxValue, new JsonNumber(sbyte.MaxValue).AsJsonElement().GetSByte()); + Assert.Throws(() => new JsonString().AsJsonElement().GetSByte()); + } + + [Fact] + public static void TestUInt16() + { + Assert.Equal(ushort.MaxValue, new JsonNumber(ushort.MaxValue).AsJsonElement().GetUInt16()); + Assert.Throws(() => new JsonString().AsJsonElement().GetUInt16()); + } + + [Fact] + public static void TestUInt32() + { + Assert.Equal(uint.MaxValue, new JsonNumber(uint.MaxValue).AsJsonElement().GetUInt32()); + Assert.Throws(() => new JsonString().AsJsonElement().GetUInt32()); + } + + [Fact] + public static void TestUInt64() + { + Assert.Equal(ulong.MaxValue, new JsonNumber(ulong.MaxValue).AsJsonElement().GetUInt64()); + Assert.Throws(() => new JsonString().AsJsonElement().GetUInt64()); + } + + [Fact] + public static void TestDecimal() + { + Assert.Equal(decimal.One, new JsonNumber(decimal.One).AsJsonElement().GetDecimal()); + Assert.Throws(() => new JsonString().AsJsonElement().GetDecimal()); + } + + [Fact] + public static void TestDateTime() + { + var dateTime = new DateTime(2019, 1, 1); + Assert.Equal(dateTime, new JsonString(dateTime).AsJsonElement().GetDateTime()); + Assert.Throws(() => new JsonBoolean().AsJsonElement().GetDateTime()); + } + + [Fact] + public static void TestDateOffset() + { + var dateTimeOffset = DateTimeOffset.ParseExact("2019-01-01T00:00:00", "s", CultureInfo.InvariantCulture); + Assert.Equal(dateTimeOffset, new JsonString(dateTimeOffset).AsJsonElement().GetDateTimeOffset()); + Assert.Throws(() => new JsonBoolean().AsJsonElement().GetDateTimeOffset()); + } + + [Fact] + public static void TestGuid() + { + Guid guid = Guid.ParseExact("ca761232-ed42-11ce-bacd-00aa0057b223", "D"); + Assert.Equal(guid, new JsonString(guid).AsJsonElement().GetGuid()); + Assert.Throws(() => new JsonBoolean().AsJsonElement().GetGuid()); + } + + [Fact] + public static void TestGetRawText() + { + string jsonToParse = @"{""property name"":""value""}"; + string rawText = JsonNode.Parse(jsonToParse).AsJsonElement().GetRawText(); + Assert.Equal(jsonToParse, rawText); + } + + [Fact] + public static void TestWriteTo() + { + var jsonObject = new JsonObject() + { + ["property"] = "value", + ["array"] = new JsonArray() { 1, 2 } + }; + + var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + jsonObject.AsJsonElement().WriteTo(writer); + string result = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(jsonObject.ToJsonString(), result); + } + } + + [Fact] + public static void TestToString() + { + var jsonObject = new JsonObject() + { + ["text"] = "value", + ["boolean"] = true, + ["null"] = null, + ["array"] = new JsonArray() { 1, 2 } + }; + + string toStringResult = jsonObject.AsJsonElement().ToString(); + Assert.Equal(jsonObject.ToJsonString(), toStringResult); + } + + [Fact] + public static void TestClone() + { + var jsonObject = new JsonObject + { + ["text"] = "value", + ["boolean"] = true, + ["null"] = null, + ["array"] = new JsonArray() { 1, 2 } + }; + + JsonElement jsonElementCopy = jsonObject.AsJsonElement().Clone(); + Assert.Equal("value", jsonElementCopy.GetProperty("text").GetString()); + Assert.True(jsonElementCopy.GetProperty("boolean").GetBoolean()); + Assert.Equal(JsonValueKind.Null, jsonElementCopy.GetProperty("null").ValueKind); + + JsonElement.ArrayEnumerator arrayEnumerator = jsonElementCopy.GetProperty("array").EnumerateArray(); + + Assert.True(arrayEnumerator.MoveNext()); + Assert.Equal(1, arrayEnumerator.Current.GetInt32()); + Assert.True(arrayEnumerator.MoveNext()); + Assert.Equal(2, arrayEnumerator.Current.GetInt32()); + Assert.False(arrayEnumerator.MoveNext()); + + var jsonObjectFromCopy = (JsonObject)JsonNode.GetNode(jsonElementCopy); + + jsonObject["text"] = "different value"; + Assert.Equal("value", jsonObjectFromCopy["text"]); + + jsonObjectFromCopy["boolean"] = false; + Assert.Equal(true, jsonObject["boolean"]); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs b/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs index f3f7b55..0150f73 100644 --- a/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs @@ -9,11 +9,12 @@ using System.Text.Encodings.Web; namespace System.Text.Json.Tests { - public sealed class JsonDocumentWriteTests : JsonReadonlyDomWriteTests + public sealed class JsonDocumentWriteTests : JsonDomWriteTests { protected override JsonDocument PrepareDocument(string jsonIn) { - return JsonDocument.Parse(jsonIn, s_options); + var jsonDocument = JsonDocument.Parse(jsonIn, s_options); + return jsonDocument; } protected override void WriteSingleValue(JsonDocument document, Utf8JsonWriter writer) @@ -36,7 +37,45 @@ namespace System.Text.Json.Tests } } - public sealed class JsonElementWriteTests : JsonReadonlyDomWriteTests + public sealed class JsonNodeWriteTests : JsonDomWriteTests + { + protected override JsonDocument PrepareDocument(string jsonIn) + { + JsonNode jsonNode = JsonNode.Parse(jsonIn, s_options); + + using (MemoryStream stream = new MemoryStream()) + { + using (Utf8JsonWriter writer = new Utf8JsonWriter(stream)) + { + jsonNode.WriteTo(writer); + stream.Seek(0, SeekOrigin.Begin); + var jsonDocument = JsonDocument.Parse(stream, s_options); + return jsonDocument; + } + } + } + + protected override void WriteSingleValue(JsonDocument document, Utf8JsonWriter writer) + { + document.WriteTo(writer); + } + + protected override void WriteDocument(JsonDocument document, Utf8JsonWriter writer) + { + document.WriteTo(writer); + } + + [Fact] + public static void CheckByPassingNullWriter() + { + using (JsonDocument doc = JsonDocument.Parse("true", default)) + { + AssertExtensions.Throws("writer", () => doc.WriteTo(null)); + } + } + } + + public sealed class JsonElementWriteTests : JsonDomWriteTests { protected override JsonDocument PrepareDocument(string jsonIn) { @@ -168,7 +207,7 @@ namespace System.Text.Json.Tests } } - public abstract class JsonReadonlyDomWriteTests + public abstract class JsonDomWriteTests { protected static readonly JsonDocumentOptions s_options = new JsonDocumentOptions diff --git a/src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs b/src/libraries/System.Text.Json/tests/JsonNode.AsJsonElementTests.cs similarity index 67% rename from src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs rename to src/libraries/System.Text.Json/tests/JsonNode.AsJsonElementTests.cs index dc222e4..cfd4629 100644 --- a/src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonNode.AsJsonElementTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace System.Text.Json.Tests { - public static class JsonNodeToJsonElementTests + public static partial class JsonNodeTests { [Fact] public static void TestAsJsonElement() @@ -18,7 +18,7 @@ namespace System.Text.Json.Tests { "boolean", true }, { "number", 15 }, { "null node", (JsonNode) null }, - { "array", new JsonString[] { "value1", "value2"} } + { "array", new JsonArray { "value1", "value2"} } }; JsonElement jsonElement = jsonObject.AsJsonElement(); @@ -30,12 +30,12 @@ namespace System.Text.Json.Tests Assert.Equal("text", enumerator.Current.Name); Assert.Equal(JsonValueKind.String, enumerator.Current.Value.ValueKind); Assert.Equal("property value", enumerator.Current.Value.GetString()); - + enumerator.MoveNext(); Assert.Equal("boolean", enumerator.Current.Name); Assert.Equal(JsonValueKind.True, enumerator.Current.Value.ValueKind); Assert.True(enumerator.Current.Value.GetBoolean()); - + enumerator.MoveNext(); Assert.Equal("number", enumerator.Current.Name); Assert.Equal(15, enumerator.Current.Value.GetInt32()); @@ -54,7 +54,7 @@ namespace System.Text.Json.Tests innerEnumerator.MoveNext(); Assert.Equal(JsonValueKind.String, innerEnumerator.Current.ValueKind); Assert.Equal("value1", innerEnumerator.Current.GetString()); - + innerEnumerator.MoveNext(); Assert.Equal(JsonValueKind.String, innerEnumerator.Current.ValueKind); Assert.Equal("value2", innerEnumerator.Current.GetString()); @@ -90,23 +90,42 @@ namespace System.Text.Json.Tests { "boolean", true }, { "number", 15 }, { "null node", (JsonNode) null }, - { "array", new JsonString[] { "value1", "value2"} } + { "array", new JsonArray { "value1", "value2"} } }; JsonElement jsonElement = jsonObject.AsJsonElement(); - JsonObject jsonObjectFromElement = (JsonObject)JsonNode.GetNode(jsonElement); + JsonObject jsonObjectFromElementGetNode = (JsonObject)JsonNode.GetNode(jsonElement); + + Assert.True(JsonNode.TryGetNode(jsonElement, out JsonNode jsonNodeFromElementTryGetNode)); + var jsonObjectFromElementTryGetNode = (JsonObject)jsonNodeFromElementTryGetNode; - Assert.Equal("property value", (JsonString)jsonObjectFromElement["text"]); + Assert.Equal("property value", jsonObjectFromElementGetNode["text"]); + Assert.Equal("property value", jsonObjectFromElementTryGetNode["text"]); - // Modifying JsonObject will change JsonObjectFromElement: + // Modifying JsonObject will change jsonObjectFromElementGetNode and jsonObjectFromElementTryGetNode: jsonObject["text"] = new JsonString("something different"); - Assert.Equal("something different", (JsonString)jsonObjectFromElement["text"]); + Assert.Equal("something different", jsonObjectFromElementGetNode["text"]); + Assert.Equal("something different", jsonObjectFromElementTryGetNode["text"]); - // Modifying JsonObjectFromElement will change JsonObject: + // Modifying JsonObjectFromElementGetNode will change JsonObject and jsonObjectFromElementTryGetNode: - ((JsonBoolean)jsonObjectFromElement["boolean"]).Value = false; + ((JsonBoolean)jsonObjectFromElementGetNode["boolean"]).Value = false; Assert.False(((JsonBoolean)jsonObject["boolean"]).Value); + Assert.False(((JsonBoolean)jsonObjectFromElementTryGetNode["boolean"]).Value); + } + + [Fact] + public static void TestGetNodeFails() + { + JsonDocument jsonDocument = JsonDocument.Parse("{}"); + JsonElement jsonElement = jsonDocument.RootElement; + Assert.False(JsonNode.TryGetNode(jsonElement, out JsonNode _)); + Assert.Throws(() => JsonNode.GetNode(jsonElement)); + + jsonElement = default; + Assert.False(JsonNode.TryGetNode(jsonElement, out JsonNode _)); + Assert.Throws(() => JsonNode.GetNode(jsonElement)); } } } diff --git a/src/libraries/System.Text.Json/tests/JsonNode.CloneTests.cs b/src/libraries/System.Text.Json/tests/JsonNode.CloneTests.cs new file mode 100644 index 0000000..50c694e --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonNode.CloneTests.cs @@ -0,0 +1,103 @@ +// 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. + +using Xunit; + +namespace System.Text.Json.Tests +{ + public static partial class JsonNodeTests + { + [Fact] + public static void TestCloneJsonArray() + { + var jsonArray = new JsonArray { "value1", "value2" }; + var jsonArrayCopy = jsonArray.Clone() as JsonArray; + Assert.Equal(2, jsonArrayCopy.Count); + jsonArray.Add("value3"); + Assert.Equal(2, jsonArrayCopy.Count); + } + + [Fact] + public static void TestDeepCloneJsonArray() + { + JsonArray inner = new JsonArray { 1, 2, 3 }; + JsonArray outer = new JsonArray { inner }; + JsonArray outerClone = (JsonArray)outer.Clone(); + ((JsonArray) outerClone[0]).Add(4); + + Assert.Equal(3, inner.Count); + } + + [Fact] + public static void TestCloneJsonNode() + { + var jsonObject = new JsonObject + { + { "text", "property value" }, + { "boolean", true }, + { "number", 15 }, + { "array", new JsonArray { "value1", "value2"} }, + { "null", null } + }; + + var jsonObjectCopy = (JsonObject)jsonObject.Clone(); + Assert.Equal(5, jsonObjectCopy.GetPropertyNames().Count); + Assert.Equal(5, jsonObjectCopy.GetPropertyValues().Count); + + jsonObject["text"] = "something different"; + Assert.Equal("property value", jsonObjectCopy["text"]); + + ((JsonBoolean)jsonObject["boolean"]).Value = false; + Assert.True(((JsonBoolean)jsonObjectCopy["boolean"]).Value); + + Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count); + jsonObject.GetJsonArrayPropertyValue("array").Add("value3"); + Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count); + + Assert.IsType(jsonObjectCopy["null"]); + + jsonObject.Add("new one", 123); + Assert.Equal(5, jsonObjectCopy.GetPropertyNames().Count); + } + + [Fact] + public static void TestCloneJsonNodeInJsonElement() + { + var jsonObject = new JsonObject + { + { "text", "value" }, + { "boolean", true }, + { "array", new JsonArray { "value1", "value2"} } + }; + + JsonElement jsonElement = jsonObject.AsJsonElement(); + + var jsonObjectCloneFromElement = (JsonObject)JsonNode.DeepCopy(jsonElement); + + Assert.Equal(3, jsonObjectCloneFromElement.GetPropertyNames().Count); + Assert.Equal(3, jsonObjectCloneFromElement.GetPropertyValues().Count); + + Assert.Equal("value", jsonObjectCloneFromElement["text"]); + Assert.Equal(true, jsonObjectCloneFromElement["boolean"]); + + // Modifying should not change the clone and vice versa: + + jsonObjectCloneFromElement["boolean"] = false; + Assert.Equal(false, jsonObjectCloneFromElement["boolean"]); + Assert.Equal(true, jsonObject["boolean"]); + + jsonObjectCloneFromElement.GetJsonArrayPropertyValue("array").Add("value3"); + Assert.Equal(3, jsonObjectCloneFromElement.GetJsonArrayPropertyValue("array").Count); + Assert.Equal(2, jsonObject.GetJsonArrayPropertyValue("array").Count); + + jsonObject["text"] = "different value"; + Assert.Equal("different value", jsonObject["text"]); + Assert.Equal("value", jsonObjectCloneFromElement["text"]); + + jsonObject.GetJsonArrayPropertyValue("array").Remove("value2"); + Assert.Equal(1, jsonObject.GetJsonArrayPropertyValue("array").Count); + Assert.Equal(3, jsonObjectCloneFromElement.GetJsonArrayPropertyValue("array").Count); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/JsonNode.TraversalTests.cs b/src/libraries/System.Text.Json/tests/JsonNode.TraversalTests.cs new file mode 100644 index 0000000..8d84084 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonNode.TraversalTests.cs @@ -0,0 +1,202 @@ +// 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. + +using Xunit; + +namespace System.Text.Json.Tests +{ + public static partial class JsonNodeTests + { + private static string jsonSampleString = @" + { + ""text"": ""property value"", + ""boolean true"": true, + ""boolean false"": false, + ""null"": null, + ""int"": 17, + ""combo array"": + [ + { + ""inner property"" : ""value"", + ""simple array"" : [0, 2.2, 3.14], + ""empty object"": {}, + ""nested object"": + { + ""empty array"" : [], + ""nested empty array"" : [[],[]] + } + } + ], + ""double"": 3.14, + ""scientific"": 3e100, + ""simple array"" : [1,2,3], + ""inner object"" : + { + ""inner property"" : ""value"" + } + }"; + + [Fact] + public static void TestParseStringToJsonNode() + { + JsonNode node = JsonNode.Parse(jsonSampleString); + CheckNode(node); + } + + [Fact] + public static void TestDeepCopy() + { + using (JsonDocument document = JsonDocument.Parse(jsonSampleString)) + { + JsonNode node = JsonNode.DeepCopy(document.RootElement); + CheckNode(node); + } + } + + private static void CheckNode(JsonNode node) + { + var jsonObject = (JsonObject)node; + Assert.Equal(10, jsonObject.GetPropertyNames().Count); + Assert.Equal(10, jsonObject.GetPropertyValues().Count); + Assert.Equal("property value", jsonObject["text"]); + Assert.True(((JsonBoolean)jsonObject["boolean true"]).Value); + Assert.False(((JsonBoolean)jsonObject["boolean false"]).Value); + Assert.IsType(jsonObject["null"]); + Assert.Equal(17, jsonObject["int"]); + Assert.Equal(3.14, jsonObject["double"]); + Assert.Equal("3e100", ((JsonNumber)jsonObject["scientific"]).ToString()); + + var innerArray = (JsonArray)jsonObject["simple array"]; + Assert.Equal(3, innerArray.Count); + Assert.Equal(1, ((JsonNumber)innerArray[0]).GetInt32()); + Assert.Equal(2, ((JsonNumber)innerArray[1]).GetInt32()); + Assert.Equal(3, ((JsonNumber)innerArray[2]).GetInt32()); + + var innerObject = (JsonObject)jsonObject["inner object"]; + Assert.Equal(1, innerObject.GetPropertyNames().Count); + Assert.Equal(1, innerObject.GetPropertyValues().Count); + Assert.Equal("value", innerObject["inner property"]); + + var comboObject = (JsonObject)jsonObject.GetJsonArrayPropertyValue("combo array")[0]; + Assert.Equal(4, comboObject.GetPropertyNames().Count); + Assert.Equal(4, comboObject.GetPropertyValues().Count); + Assert.Equal("value", comboObject["inner property"]); + Assert.Equal(0, comboObject.GetJsonObjectPropertyValue("empty object").GetPropertyNames().Count); + Assert.Equal(3, comboObject.GetJsonArrayPropertyValue("simple array").Count); + var nestedObject = (JsonObject)comboObject["nested object"]; + Assert.Equal(0, nestedObject.GetJsonArrayPropertyValue("empty array").Count); + Assert.Equal(2, nestedObject.GetJsonArrayPropertyValue("nested empty array").Count); + } + + [Fact] + public static void TestParseDoesNotOverflow() + { + var builder = new StringBuilder(); + for (int i = 0; i < 2_000; i++) + { + builder.Append("["); + } + + for (int i = 0; i < 2_000; i++) + { + builder.Append("]"); + } + + var options = new JsonDocumentOptions { MaxDepth = 5_000 }; + JsonNode jsonNode = JsonNode.Parse(builder.ToString(), options); + } + + [Fact] + public static void TestDeepCopyDoesNotOverflow() + { + var builder = new StringBuilder(); + for (int i = 0; i < 2_000; i++) + { + builder.Append("["); + } + + for (int i = 0; i < 2_000; i++) + { + builder.Append("]"); + } + + var options = new JsonDocumentOptions { MaxDepth = 5_000 }; + using (JsonDocument dom = JsonDocument.Parse(builder.ToString(), options)) + { + JsonNode node = JsonNode.DeepCopy(dom.RootElement); + } + } + + [Fact] + public static void TestToJsonString() + { + var jsonObject = new JsonObject() + { + { "text", "property value" }, + { "boolean true", true }, + { "boolean false", false }, + { "null", null }, + { "int", 17 }, + { + "combo array", new JsonArray() + { + new JsonObject() + { + { "inner property", "value" }, + { "simple array", new JsonArray() { 0, 2.2, 3.14 } }, + { "empty object", new JsonObject() }, + { "nested object", new JsonObject + { + { "empty array", new JsonArray() }, + { "nested empty array", new JsonArray() { new JsonArray(), new JsonArray()} } + } + } + } + } + }, + { "double", 3.14 }, + { "scientific", new JsonNumber("3e100") }, + { "simple array", new JsonArray() { 1,2,3 } }, + { "inner object", new JsonObject() + { + { "inner property", "value" } + } + } + }; + + string json = jsonObject.ToJsonString(); + JsonNode node = JsonNode.Parse(json); + CheckNode(node); + } + + [Fact] + public static void TestParseWithDuplicates() + { + var stringWithDuplicates = @" + { + ""property"": ""first value"", + ""different property"": ""value"", + ""property"": ""duplicate value"", + ""property"": ""last duplicate value"" + }"; + + var jsonObject = (JsonObject)JsonNode.Parse(stringWithDuplicates); + Assert.Equal(2, jsonObject.GetPropertyNames().Count); + Assert.Equal(2, jsonObject.GetPropertyValues().Count); + Assert.Equal("last duplicate value", jsonObject["property"]); + + jsonObject = (JsonObject) JsonNode.Parse(stringWithDuplicates, default, DuplicatePropertyNameHandling.Replace); + Assert.Equal(2, jsonObject.GetPropertyNames().Count); + Assert.Equal(2, jsonObject.GetPropertyValues().Count); + Assert.Equal("last duplicate value", jsonObject["property"]); + + jsonObject = (JsonObject)JsonNode.Parse(stringWithDuplicates, default, DuplicatePropertyNameHandling.Ignore); + Assert.Equal(2, jsonObject.GetPropertyNames().Count); + Assert.Equal(2, jsonObject.GetPropertyValues().Count); + Assert.Equal("first value", jsonObject["property"]); + + Assert.Throws(() => JsonNode.Parse(stringWithDuplicates, default, DuplicatePropertyNameHandling.Error)); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs b/src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs deleted file mode 100644 index 6ed89ed..0000000 --- a/src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -using Xunit; - -namespace System.Text.Json.Tests -{ - public static class JsonNodeCloneTests - { - [Fact] - public static void TestCloneJsonArray() - { - var jsonArray = new JsonArray { "value1", "value2" }; - var jsonArrayCopy = jsonArray.Clone() as JsonArray; - Assert.Equal(2, jsonArrayCopy.Count); - jsonArray.Add("value3"); - Assert.Equal(2, jsonArrayCopy.Count); - } - - [Fact] - public static void TestDeepCloneJsonArray() - { - JsonArray inner = new JsonArray { 1, 2, 3 }; - JsonArray outer = new JsonArray { inner }; - JsonArray outerClone = (JsonArray)outer.Clone(); - ((JsonArray) outerClone[0]).Add(4); - - Assert.Equal(3, inner.Count); - } - - [Fact] - public static void TestCloneJsonNode() - { - var jsonObject = new JsonObject - { - { "text", "property value" }, - { "boolean", true }, - { "number", 15 }, - { "array", new JsonString[] { "value1", "value2"} } - }; - - var jsonObjectCopy = (JsonObject)jsonObject.Clone(); - - jsonObject["text"] = (JsonString)"something different"; - Assert.Equal("property value", (JsonString)jsonObjectCopy["text"]); - - ((JsonBoolean)jsonObject["boolean"]).Value = false; - Assert.True(((JsonBoolean)jsonObjectCopy["boolean"]).Value); - - Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count); - jsonObject.GetJsonArrayPropertyValue("array").Add("value3"); - Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count); - - jsonObject.Add("new one", 123); - Assert.Equal(4, jsonObjectCopy.PropertyNames.Count); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs b/src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs deleted file mode 100644 index 426772e..0000000 --- a/src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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. - -using Xunit; - -namespace System.Text.Json.Tests -{ - public static class JsonNodeParseTests - { - private static string jsonSampleString = @" - { - ""text"": ""property value"", - ""boolean true"": true, - ""boolean false"": false, - ""null"": null, - ""int"": 17, - ""double"": 3.14, - ""scientific"": 3e100, - ""array"" : [1,2,3], - ""inner object"" : - { - ""inner property"" : ""value"" - } - }"; - - [Fact] - public static void TestParseStringToJsonNode() - { - JsonNode node = JsonNode.Parse(jsonSampleString); - - var jsonObject = (JsonObject)node; - Assert.Equal(9, jsonObject.PropertyNames.Count); - Assert.Equal(9, jsonObject.PropertyValues.Count); - Assert.Equal("property value", (JsonString)jsonObject["text"]); - Assert.True(((JsonBoolean)jsonObject["boolean true"]).Value); - Assert.False(((JsonBoolean)jsonObject["boolean false"]).Value); - Assert.Null(jsonObject["null"]); - Assert.Equal(17, ((JsonNumber)jsonObject["int"]).GetInt32()); - Assert.Equal(3.14, ((JsonNumber)jsonObject["double"]).GetDouble()); - Assert.Equal("3e100", ((JsonNumber)jsonObject["scientific"]).ToString()); - - var innerArray = (JsonArray)jsonObject["array"]; - Assert.Equal(3, innerArray.Count); - Assert.Equal(1, ((JsonNumber)innerArray[0]).GetInt32()); - Assert.Equal(2, ((JsonNumber)innerArray[1]).GetInt32()); - Assert.Equal(3, ((JsonNumber)innerArray[2]).GetInt32()); - - var innerObject = (JsonObject)jsonObject["inner object"]; - Assert.Equal(1, innerObject.PropertyNames.Count); - Assert.Equal(1, innerObject.PropertyValues.Count); - Assert.Equal("value", (JsonString)innerObject["inner property"]); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs b/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs index 39d443c..2df29b1 100644 --- a/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs +++ b/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs @@ -40,8 +40,8 @@ namespace System.Text.Json.Tests /// { /// "software developers" : /// { - /// "full time employees" : /JsonObject of 3 employees fromk database/ - /// "intern employees" : /JsonObject of 2 employees fromk database/ + /// "full time employees" : /JsonObject of 3 employees from database/ + /// "intern employees" : /JsonObject of 2 employees from database/ /// }, /// "HR" : /JsonObject of 10 employees fromk database/ /// } diff --git a/src/libraries/System.Text.Json/tests/JsonNullTests.cs b/src/libraries/System.Text.Json/tests/JsonNullTests.cs new file mode 100644 index 0000000..0e9ca76 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonNullTests.cs @@ -0,0 +1,99 @@ +// 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. + +using Xunit; + +namespace System.Text.Json.Tests +{ + public static class JsonNullTests + { + [Fact] + public static void TestToString() + { + Assert.Equal("null", new JsonNull().ToString()); + } + + [Fact] + public static void TestEquals() + { + var jsonNull1 = new JsonNull(); + var jsonNull2 = new JsonNull(); + + Assert.True(jsonNull1.Equals(jsonNull2)); + Assert.True(jsonNull1 == jsonNull2); + Assert.False(jsonNull1 != jsonNull2); + + JsonNode jsonNodeJsonNull = new JsonNull(); + Assert.True(jsonNull1.Equals(jsonNodeJsonNull)); + Assert.True(jsonNodeJsonNull.Equals(jsonNull1)); + + IEquatable jsonNullIEquatable = jsonNull2; + Assert.True(jsonNullIEquatable.Equals(jsonNull1)); + Assert.True(jsonNull1.Equals(jsonNullIEquatable)); + + object jsonNullCopy = jsonNull1; + + Assert.True(jsonNullCopy.Equals(jsonNull1)); + Assert.True(jsonNull1.Equals(jsonNullCopy)); + + object jsonNullObject = new JsonNull(); + + Assert.True(jsonNullObject.Equals(jsonNull1)); + Assert.True(jsonNull1.Equals(jsonNullObject)); + + Assert.False(jsonNull1.Equals(new JsonString("null"))); + Assert.False(jsonNull1.Equals(new Exception())); + + // Null comparisons behave this way because of implicit conversion from null to JsonNull: + + Assert.True(jsonNull1.Equals(null)); + Assert.True(jsonNull1 == null); + Assert.False(jsonNull1 != null); + + JsonNull jsonNullNull = null; + + Assert.True(jsonNull1.Equals(jsonNullNull)); + Assert.True(jsonNull1 == jsonNullNull); + Assert.False(jsonNull1 != jsonNullNull); + + JsonNull otherJsonNullNull = null; + Assert.True(jsonNullNull == otherJsonNullNull); + + // Only for null JsonNode / different derived type this will return false: + + JsonNode jsonNodeNull = null; + Assert.False(jsonNull1.Equals(jsonNodeNull)); + + JsonArray jsonArrayNull = null; + Assert.False(jsonNull1.Equals(jsonArrayNull)); + } + + [Fact] + public static void TestGetHashCode() + { + var jsonNull = new JsonNull(); + + Assert.Equal(jsonNull.GetHashCode(), new JsonNull().GetHashCode()); + + JsonNode jsonNodeNull = new JsonNull(); + Assert.Equal(jsonNull.GetHashCode(), jsonNodeNull.GetHashCode()); + + IEquatable jsonNullIEquatable = new JsonNull(); + Assert.Equal(jsonNull.GetHashCode(), jsonNullIEquatable.GetHashCode()); + + object jsonNullCopy= jsonNull; + object jsonNullObject = new JsonNull(); + Assert.Equal(jsonNull.GetHashCode(), jsonNullCopy.GetHashCode()); + Assert.Equal(jsonNull.GetHashCode(), jsonNullObject.GetHashCode()); + + Assert.Equal(-1, jsonNull.GetHashCode()); + } + + [Fact] + public static void TestValueKind() + { + Assert.Equal(JsonValueKind.Null, new JsonNull().ValueKind); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/JsonNumberTests.cs b/src/libraries/System.Text.Json/tests/JsonNumberTests.cs index 8cfc337..5db0329 100644 --- a/src/libraries/System.Text.Json/tests/JsonNumberTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonNumberTests.cs @@ -18,7 +18,7 @@ namespace System.Text.Json.Tests Action setter, Func getter, TryGetValue tryGetter, - Func implicitCaster) + Func implicitCaster) { // Default constructor: JsonNumber number = new JsonNumber(); @@ -41,7 +41,7 @@ namespace System.Text.Json.Tests AssertValue(value, number, getter, tryGetter); // Implicit cast: - number = implicitCaster(value); + number = (JsonNumber)implicitCaster(value); AssertValue(value, number, getter, tryGetter); } @@ -525,6 +525,7 @@ namespace System.Text.Json.Tests public static void TestFloatInfinities(float value) { Assert.Throws(() => new JsonNumber(value)); + Assert.Equal(value.ToString(), (JsonNode)value); } [InlineData(double.PositiveInfinity)] @@ -533,6 +534,7 @@ namespace System.Text.Json.Tests public static void TestDoubleIninities(double value) { Assert.Throws (() => new JsonNumber(value)); + Assert.Equal(value.ToString(), (JsonNode)value); } [Fact] @@ -677,5 +679,11 @@ namespace System.Text.Json.Tests Assert.Equal(new JsonNumber().GetHashCode(), new JsonNumber().GetHashCode()); } + + [Fact] + public static void TestValueKind() + { + Assert.Equal(JsonValueKind.Number, new JsonNumber().ValueKind); + } } } diff --git a/src/libraries/System.Text.Json/tests/JsonObjectTests.cs b/src/libraries/System.Text.Json/tests/JsonObjectTests.cs index 392eaf5..6cf9fa7 100644 --- a/src/libraries/System.Text.Json/tests/JsonObjectTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonObjectTests.cs @@ -15,8 +15,8 @@ namespace System.Text.Json.Tests public static void TestDefaultConstructor() { var jsonObject = new JsonObject(); - Assert.Equal(0, jsonObject.PropertyNames.Count); - Assert.Equal(0, jsonObject.PropertyValues.Count); + Assert.Equal(0, jsonObject.GetPropertyNames().Count); + Assert.Equal(0, jsonObject.GetPropertyValues().Count); } [Fact] @@ -34,40 +34,21 @@ namespace System.Text.Json.Tests Assert.True(((JsonBoolean)jsonObject["boolean"]).Value); } - private static void TestDuplicates(DuplicatePropertyNameHandling duplicatePropertyNameHandling, string previousValue, string newValue, string expectedValue, bool useDefaultCtor = false) - { - JsonObject jsonObject = useDefaultCtor ? new JsonObject() : new JsonObject(duplicatePropertyNameHandling); - jsonObject.Add("property", new JsonString(previousValue)); - - Assert.Equal(previousValue, ((JsonString)jsonObject["property"]).Value); - - jsonObject.Add("property", new JsonString(newValue)); - - Assert.Equal(expectedValue, ((JsonString) jsonObject["property"]).Value); - - // with indexer, property should change no matter which duplicates handling option is chosen: - jsonObject["property"] = (JsonString)"indexer value"; - Assert.Equal("indexer value", (JsonString)jsonObject["property"]); - } - - - [Theory] - [InlineData(DuplicatePropertyNameHandling.Replace, "value1", "value2", "value2")] - [InlineData(DuplicatePropertyNameHandling.Replace, "value1", "value2", "value2", true)] - [InlineData(DuplicatePropertyNameHandling.Ignore, "value1", "value2", "value1")] - public static void TestDuplicatesReplaceAndIgnore(DuplicatePropertyNameHandling duplicatePropertyNameHandling, string previousValue, string newValue, string expectedValue, bool useDefaultCtor = false) - { - TestDuplicates(duplicatePropertyNameHandling, previousValue, newValue, expectedValue, useDefaultCtor); - } - [Fact] - public static void TestDuplicatesError() + public static void TestDuplicates() { - Assert.Throws(() => TestDuplicates(DuplicatePropertyNameHandling.Error, "", "", "")); + Assert.Throws(() => + { + JsonObject jsonObject = new JsonObject(); + jsonObject.Add("property", "value1"); + jsonObject.Add("property", "value2"); + }); + + JsonObject jsonObject = new JsonObject() { { "property", "value" } }; + Assert.Equal("value", jsonObject["property"]); - JsonObject jsonObject = new JsonObject(DuplicatePropertyNameHandling.Error) { { "property", "" } }; - jsonObject["property"] = (JsonString) "indexer value"; - Assert.Equal("indexer value", (JsonString) jsonObject["property"]); + jsonObject["property"] = "indexer value"; + Assert.Equal("indexer value", jsonObject["property"]); } [Fact] @@ -110,27 +91,12 @@ namespace System.Text.Json.Tests } [Fact] - public static void TestReadonlySpan() - { - var jsonObject = new JsonObject(); - - var spanValue = new ReadOnlySpan(new char[] { 's', 'p', 'a', 'n' }); - jsonObject.Add("span", spanValue); - Assert.Equal("span", (JsonString)jsonObject["span"]); - - string property = null; - spanValue = property.AsSpan(); - jsonObject.Add("span", spanValue); - Assert.Equal("", (JsonString)jsonObject["span"]); - } - - [Fact] public static void TestGuid() { var guidString = "ca761232-ed42-11ce-bacd-00aa0057b223"; Guid guid = new Guid(guidString); - var jsonObject = new JsonObject{ { "guid", guid } }; - Assert.Equal(guidString, (JsonString)jsonObject["guid"]); + var jsonObject = new JsonObject { { "guid", guid } }; + Assert.Equal(guidString, ((JsonString)jsonObject["guid"]).Value); } public static IEnumerable DateTimeData => @@ -147,7 +113,7 @@ namespace System.Text.Json.Tests public static void TestDateTime(DateTime dateTime) { var jsonObject = new JsonObject { { "dateTime", dateTime } }; - Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), (JsonString)jsonObject["dateTime"]); + Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), ((JsonString)jsonObject["dateTime"]).Value); } [Theory] @@ -155,7 +121,7 @@ namespace System.Text.Json.Tests public static void TestDateTimeOffset(DateTimeOffset dateTimeOffset) { var jsonObject = new JsonObject { { "dateTimeOffset", dateTimeOffset } }; - Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), (JsonString)jsonObject["dateTimeOffset"]); + Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), ((JsonString)jsonObject["dateTimeOffset"]).Value); } [Fact] @@ -174,7 +140,7 @@ namespace System.Text.Json.Tests Assert.Equal("Kasia", ((JsonString)developer["n\\u0061me"]).Value); Assert.Equal(22, ((JsonNumber)developer["age"]).GetInt32()); Assert.True(((JsonBoolean)developer["is developer"]).Value); - Assert.Null(developer["null property"]); + Assert.IsType(developer["null property"]); } [Fact] @@ -184,12 +150,31 @@ namespace System.Text.Json.Tests { { "name", new JsonString("Kasia") }, { "age", new JsonNumber(22) }, - { "is developer", new JsonBoolean(true) } + { "is developer", new JsonBoolean(true) }, + { "null property", new JsonNull() } }; Assert.Equal("Kasia", ((JsonString)developer["name"]).Value); Assert.Equal(22, ((JsonNumber)developer["age"]).GetInt32()); Assert.True(((JsonBoolean)developer["is developer"]).Value); + Assert.IsType(developer["null property"]); + } + + [Fact] + public static void TestCreatingJsonObjectDictionaryInitializerSyntax() + { + var developer = new JsonObject + { + ["name"] = "Kasia", + ["age"] = 22, + ["is developer"] = true, + ["null property"] = null + }; + + Assert.Equal("Kasia", ((JsonString)developer["name"]).Value); + Assert.Equal(22, ((JsonNumber)developer["age"]).GetInt32()); + Assert.True(((JsonBoolean)developer["is developer"]).Value); + Assert.IsType(developer["null property"]); } [Fact] @@ -306,11 +291,11 @@ namespace System.Text.Json.Tests } [Fact] - public static void TestAddingJsonArrayFromJsonNumberArray() + public static void TestAddingJsonArray() { var preferences = new JsonObject() { - { "prime numbers", new JsonNumber[] { 19, 37 } } + { "prime numbers", new JsonArray { 19, 37 } } }; var primeNumbers = (JsonArray)preferences["prime numbers"]; @@ -320,87 +305,44 @@ namespace System.Text.Json.Tests for (int i = 0; i < primeNumbers.Count; i++) { - Assert.IsType(primeNumbers[i]); - Assert.Equal(expected[i], primeNumbers[i] as JsonNumber); + Assert.Equal(expected[i], ((JsonNumber)primeNumbers[i]).GetInt32()); } } [Fact] - public static void TestAddingJsonArray() + public static void TestGetJsonArrayPropertyValue() { - var preferences = new JsonObject() + var jsonObject = new JsonObject() { - { "colours", (JsonNode) new JsonArray{ "red", "green", "blue" } } + { "array", new JsonArray() { 1, 2 } } }; - var colours = (JsonArray)preferences["colours"]; - Assert.Equal(3, colours.Count); - - string[] expected = { "red", "green", "blue" }; - - for (int i = 0; i < colours.Count; i++) - { - Assert.IsType(colours[i]); - Assert.Equal(expected[i], colours[i] as JsonString); - } + JsonArray jsonArray = jsonObject.GetJsonArrayPropertyValue("array"); + Assert.Equal(2, jsonArray.Count); + Assert.Equal(1, jsonArray[0]); + Assert.Equal(2, jsonArray[1]); } [Fact] - public static void TestAddingJsonArrayFromIEnumerableOfStrings() + public static void TestAddingNull() { - var sportsExperienceYears = new JsonObject() + var jsonObject = new JsonObject { - { "skiing", 5 }, - { "cycling", 8 }, - { "hiking", 6 }, - { "chess", 2 }, - { "skating", 1 }, + { "null1", null }, + { "null2", (JsonNode)null }, + { "null3", (JsonNull)null }, + { "null4", new JsonNull() }, + { "null5", (string)null }, }; - // choose only sports with > 2 experience years - IEnumerable sports = sportsExperienceYears.Where(sport => ((JsonNumber)sport.Value).GetInt32() > 2).Select(sport => sport.Key); + Assert.IsType(jsonObject["null1"]); + Assert.IsType(jsonObject["null2"]); + Assert.IsType(jsonObject["null3"]); + Assert.IsType(jsonObject["null4"]); + Assert.IsType(jsonObject["null5"]); - var preferences = new JsonObject() - { - { "sports", (JsonNode) new JsonArray(sports) } - }; - - var sportsJsonArray = (JsonArray)preferences["sports"]; - Assert.Equal(3, sportsJsonArray.Count); - - for (int i = 0; i < sportsJsonArray.Count; i++) - { - Assert.IsType(sportsJsonArray[i]); - Assert.Equal(sports.ElementAt(i), sportsJsonArray[i] as JsonString); - } - } - - [Fact] - public static void TestAddingJsonArrayFromIEnumerableOfJsonNodes() - { - var strangeWords = new JsonArray() - { - "supercalifragilisticexpialidocious", - "gladiolus", - "albumen", - "smaragdine" - }; - - var preferences = new JsonObject() - { - { "strange words", strangeWords.Where(word => ((JsonString)word).Value.Length < 10) } - }; - - var strangeWordsJsonArray = (JsonArray)preferences["strange words"]; - Assert.Equal(2, strangeWordsJsonArray.Count); - - string[] expected = { "gladiolus", "albumen" }; - - for (int i = 0; i < strangeWordsJsonArray.Count; i++) - { - Assert.IsType(strangeWordsJsonArray[i]); - Assert.Equal(expected[i], strangeWordsJsonArray[i] as JsonString); - } + jsonObject["null1"] = null; + Assert.IsType(jsonObject["null1"]); } [Fact] @@ -413,7 +355,7 @@ namespace System.Text.Json.Tests }; Assert.True(person.ContainsProperty("ssn")); - Assert.Equal("123456789", (JsonString)person["ssn"]); + Assert.Equal("123456789", ((JsonString)person["ssn"]).Value); Assert.False(person.ContainsProperty("surname")); } @@ -421,10 +363,10 @@ namespace System.Text.Json.Tests public static void TestAquiringAllValues() { var employees = new JsonObject(EmployeesDatabase.GetTenBestEmployees()); - ICollection employeesWithoutId = employees.PropertyValues; + IReadOnlyCollection employeesWithoutId = employees.GetPropertyValues(); - Assert.Equal(10, employees.PropertyNames.Count); - Assert.Equal(10, employees.PropertyValues.Count); + Assert.Equal(10, employees.GetPropertyNames().Count); + Assert.Equal(10, employees.GetPropertyValues().Count); foreach (JsonNode employee in employeesWithoutId) { @@ -444,13 +386,13 @@ namespace System.Text.Json.Tests person1["name"] = new JsonString("Bob"); - Assert.Equal("Bob", (JsonString)person1["name"]); + Assert.Equal("Bob", ((JsonString)person1["name"]).Value); person1["age"] = new JsonNumber(55); - Assert.Equal(55, (JsonNumber)person1["age"]); + Assert.Equal(55, ((JsonNumber)person1["age"]).GetInt32()); person1["is_married"] = new JsonBoolean(false); - Assert.Equal(false, (JsonBoolean)person1["is_married"]); + Assert.False(((JsonBoolean)person1["is_married"]).Value); var person2 = new JsonObject { @@ -462,7 +404,7 @@ namespace System.Text.Json.Tests // Copy property from another JsonObject person1["age"] = person2["age"]; - Assert.Equal(33, (JsonNumber) person1["age"]); + Assert.Equal(33, ((JsonNumber)person1["age"]).GetInt32()); // Copy property of different typoe person1["name"] = person2["name"]; @@ -600,7 +542,7 @@ namespace System.Text.Json.Tests } [Fact] - public static void TestTryGetProperty () + public static void TestTryGetProperty() { var jsonObject = new JsonObject() { @@ -608,7 +550,7 @@ namespace System.Text.Json.Tests }; Assert.True(jsonObject.TryGetPropertyValue("name", out JsonNode property)); - Assert.Equal("value", (JsonString)property); + Assert.Equal("value", ((JsonString)property).Value); Assert.False(jsonObject.TryGetPropertyValue("other", out property)); Assert.Null(property); } @@ -712,11 +654,147 @@ namespace System.Text.Json.Tests } [Fact] - public static void TestDuplicatesEnumOutOfRange() + public static void TestStringComparisonEnum() + { + var jsonObject = new JsonObject() + { + { "not encyclopaedia", "value1" }, + { "Encyclopaedia", "value2" }, + { "NOT encyclopaedia", "value3" }, + { "encyclopaedia", "value4" } + }; + + Assert.Equal(4, jsonObject.Count()); + + Assert.False(jsonObject.ContainsProperty("ENCYCLOPAEDIA")); + Assert.False(jsonObject.TryGetPropertyValue("ENCYCLOPAEDIA", out JsonNode jsonNode)); + Assert.Null(jsonNode); + Assert.Throws(() => jsonObject.GetPropertyValue("ENCYCLOPAEDIA")); + 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(() => jsonObject.GetPropertyValue("ENCYCLOPAEDIA", StringComparison.CurrentCulture)); + jsonObject.Remove("ENCYCLOPAEDIA", StringComparison.CurrentCulture); + 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()); + + IReadOnlyCollection 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() + { + { "object first", new JsonObject() }, + { "object FIRST", new JsonArray() }, + { "array first", new JsonArray() }, + { "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); + + Assert.Throws(() => + 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); + + Assert.Throws(() => + 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)); + + Assert.Throws(() => + jsonObject.Remove(null, StringComparison.InvariantCultureIgnoreCase)); + } + + [Fact] + public static void TestValueKind() { - Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)123)); - Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)(-1))); - Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)3)); + Assert.Equal(JsonValueKind.Object, new JsonObject().ValueKind); + } + + [Fact] + public static void TestRemoveLastProperty() + { + var jsonObject = new JsonObject() + { + { "first", "value1" }, + { "middle", "value2" }, + { "last", "" } + }; + + jsonObject.Remove("last"); + Assert.Equal(2, jsonObject.GetPropertyNames().Count()); + Assert.Equal(2, jsonObject.GetPropertyValues().Count()); + Assert.Equal("value1", jsonObject["first"]); + Assert.Equal("value2", jsonObject["middle"]); + } + + [Fact] + public static void TestJsonObjectIEnumerator() + { + var jsonObject = new JsonObject() + { + ["first"] = 17, + ["second"] = "value" + }; + + // Test generic IEnumerator: + IEnumerator> jsonObjectEnumerator = new JsonObjectEnumerator(jsonObject); + + Assert.Null(jsonObjectEnumerator.Current.Key); + Assert.Null(jsonObjectEnumerator.Current.Value); + + jsonObjectEnumerator.MoveNext(); + Assert.Equal("first", jsonObjectEnumerator.Current.Key); + Assert.Equal(17, jsonObjectEnumerator.Current.Value); + jsonObjectEnumerator.MoveNext(); + Assert.Equal("second", jsonObjectEnumerator.Current.Key); + Assert.Equal("value", jsonObjectEnumerator.Current.Value); + + jsonObjectEnumerator.Reset(); + + jsonObjectEnumerator.MoveNext(); + Assert.Equal("first", jsonObjectEnumerator.Current.Key); + Assert.Equal(17, jsonObjectEnumerator.Current.Value); + jsonObjectEnumerator.MoveNext(); + Assert.Equal("second", jsonObjectEnumerator.Current.Key); + Assert.Equal("value", jsonObjectEnumerator.Current.Value); + } + + [Fact] + public static void TestJsonObjectEmptyObjectEnumerator() + { + var jsonObject = new JsonObject(); + var jsonObjectEnumerator = new JsonObjectEnumerator(jsonObject); + + Assert.Null(jsonObjectEnumerator.Current.Key); + Assert.Null(jsonObjectEnumerator.Current.Value); + Assert.False(jsonObjectEnumerator.MoveNext()); } } } diff --git a/src/libraries/System.Text.Json/tests/JsonStringTests.cs b/src/libraries/System.Text.Json/tests/JsonStringTests.cs index 7644fd9..b4b2a4b 100644 --- a/src/libraries/System.Text.Json/tests/JsonStringTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonStringTests.cs @@ -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")] @@ -44,7 +44,8 @@ namespace System.Text.Json.Tests Assert.Equal(value, new JsonString(value).Value); // Implicit operator: - JsonString implicitlyInitializiedJsonString = value; + JsonNode jsonNode = value; + JsonString implicitlyInitializiedJsonString = (JsonString)jsonNode; Assert.Equal(value, implicitlyInitializiedJsonString.Value); // Casted to span: @@ -76,18 +77,21 @@ namespace System.Text.Json.Tests public static void TestGuid() { var guidString = "ca761232-ed42-11ce-bacd-00aa0057b223"; - Guid guid = new Guid(guidString); + Guid guid = Guid.ParseExact(guidString, "D"); var jsonString = new JsonString(guid); - Assert.Equal(guidString, jsonString); + Assert.Equal(guidString, jsonString.Value); + Assert.Equal(guid, jsonString.GetGuid()); + Assert.True(jsonString.TryGetGuid(out Guid guid2)); + Assert.Equal(guid, guid2); } public static IEnumerable DateTimeData => new List { - new object[] { new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc) }, - new object[] { new DateTime(2019, 1, 1) }, - new object[] { new DateTime(2019, 1, 1, new GregorianCalendar()) }, - new object[] { new DateTime(2019, 1, 1, new ChineseLunisolarCalendar()) } + new object[] { new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc) }, + new object[] { new DateTime(2019, 1, 1) }, + new object[] { new DateTime(2019, 1, 1, new GregorianCalendar()) }, + new object[] { new DateTime(2019, 1, 1, new ChineseLunisolarCalendar()) } }; [Theory] @@ -95,15 +99,22 @@ namespace System.Text.Json.Tests public static void TestDateTime(DateTime dateTime) { var jsonString = new JsonString(dateTime); - Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), jsonString); + Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), jsonString.Value); + Assert.Equal(dateTime, jsonString.GetDateTime()); + Assert.True(jsonString.TryGetDateTime(out DateTime dateTime2)); + Assert.Equal(dateTime, dateTime2); } [Theory] [MemberData(nameof(DateTimeData))] - public static void TestDateTimeOffset(DateTimeOffset dateTimeOffset) + public static void TestDateTimeOffset(DateTime dateTime) { + var dateTimeOffset = DateTimeOffset.ParseExact(dateTime.ToString("s"), "s", CultureInfo.InvariantCulture); var jsonString = new JsonString(dateTimeOffset); - Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), jsonString); + Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), jsonString.Value); + Assert.Equal(dateTimeOffset, jsonString.GetDateTimeOffset()); + Assert.True(jsonString.TryGetDateTimeOffset(out DateTimeOffset dateTimeOffset2)); + Assert.Equal(dateTimeOffset, dateTimeOffset2); } [Fact] @@ -115,6 +126,20 @@ namespace System.Text.Json.Tests jsonString.Value = "different property value"; Assert.Equal("different property value", jsonString.Value); } + + [Fact] + public static void TestGettersFail() + { + var jsonString = new JsonString("value"); + + Assert.Throws(() => jsonString.GetDateTime()); + Assert.Throws(() => jsonString.GetDateTimeOffset()); + Assert.Throws(() => jsonString.GetGuid()); + + Assert.False(jsonString.TryGetDateTime(out DateTime _)); + Assert.False(jsonString.TryGetDateTimeOffset(out DateTimeOffset _)); + Assert.False(jsonString.TryGetGuid(out Guid _)); + } [Fact] public static void TestEquals() @@ -181,7 +206,7 @@ namespace System.Text.Json.Tests Assert.NotEqual(jsonString.GetHashCode(), new JsonString("SOMETHING COMPLETELY DIFFERENT").GetHashCode()); Assert.NotEqual(jsonString.GetHashCode(), new JsonString("").GetHashCode()); Assert.NotEqual(jsonString.GetHashCode(), new JsonString().GetHashCode()); - + JsonNode jsonNode = new JsonString("json property value"); Assert.Equal(jsonString.GetHashCode(), jsonNode.GetHashCode()); @@ -196,5 +221,11 @@ namespace System.Text.Json.Tests Assert.Equal(new JsonString().GetHashCode(), new JsonString().GetHashCode()); } + + [Fact] + public static void TestValueKind() + { + Assert.Equal(JsonValueKind.String, new JsonString().ValueKind); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index 14b63c2..b1d02c5 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -20,8 +20,6 @@ - - @@ -120,8 +118,12 @@ + - + + + + -- 2.7.4