diff --git a/src/System.Text.Json/docs/writable_json_dom_spec.md b/src/System.Text.Json/docs/writable_json_dom_spec.md index 927119eaba28..8a3288a3456c 100644 --- a/src/System.Text.Json/docs/writable_json_dom_spec.md +++ b/src/System.Text.Json/docs/writable_json_dom_spec.md @@ -242,6 +242,7 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement()); ## 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)? * 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`? @@ -260,6 +261,8 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement()); * 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? ## Useful links diff --git a/src/System.Text.Json/ref/System.Text.Json.cs b/src/System.Text.Json/ref/System.Text.Json.cs index 07586b953d66..2c9c06d4451b 100644 --- a/src/System.Text.Json/ref/System.Text.Json.cs +++ b/src/System.Text.Json/ref/System.Text.Json.cs @@ -7,6 +7,12 @@ namespace System.Text.Json { + public enum DuplicatePropertyNameHandling + { + Replace = 0, + Ignore = 1, + Error = 2, + } public sealed partial class JsonBoolean : System.Text.Json.JsonNode, System.IEquatable { public JsonBoolean() { } @@ -260,6 +266,56 @@ public void SetUInt64(ulong value) { } [System.CLSCompliantAttribute(false)] public bool TryGetUInt64(out ulong value) { throw null; } } + 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 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 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.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 bool ContainsProperty(string propertyName) { throw null; } + public System.Collections.Generic.IEnumerator> GetEnumerator() { throw null; } + public System.Text.Json.JsonObject GetJsonObjectPropertyValue(string propertyName) { throw null; } + public System.Text.Json.JsonNode GetPropertyValue(string propertyName) { throw null; } + public bool Remove(string propertyName) { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + public bool TryGetJsonObjectPropertyValue(string propertyName, out System.Text.Json.JsonObject jsonObject) { 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 + { + private object _dummy; + public JsonObjectEnumerator(System.Text.Json.JsonObject jsonObject) { throw null; } + public System.Collections.Generic.KeyValuePair Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public bool MoveNext() { throw null; } + public void Reset() { } + } public readonly partial struct JsonProperty { private readonly object _dummy; @@ -324,6 +380,9 @@ public JsonSerializerOptions() { } public sealed partial class JsonString : System.Text.Json.JsonNode, System.IEquatable { public JsonString() { } + public JsonString(System.DateTime value) { } + public JsonString(System.DateTimeOffset value) { } + public JsonString(System.Guid value) { } public JsonString(System.ReadOnlySpan value) { } public JsonString(string value) { } public string Value { get { throw null; } set { } } diff --git a/src/System.Text.Json/src/Resources/Strings.resx b/src/System.Text.Json/src/Resources/Strings.resx index 898e45978013..0b044d0effe1 100644 --- a/src/System.Text.Json/src/Resources/Strings.resx +++ b/src/System.Text.Json/src/Resources/Strings.resx @@ -429,4 +429,16 @@ Expected a number, but instead got empty string. - + + Property with name '{0}' already exists. + + + Property with name '{0}' not found. + + + Property with name '{0}' has a different type than expected. + + + The DuplicatePropertyNameHandling enum must be set to one of the supported values. + + \ No newline at end of file diff --git a/src/System.Text.Json/src/System.Text.Json.csproj b/src/System.Text.Json/src/System.Text.Json.csproj index a0f937e70812..88f49ff23519 100644 --- a/src/System.Text.Json/src/System.Text.Json.csproj +++ b/src/System.Text.Json/src/System.Text.Json.csproj @@ -209,9 +209,12 @@ + - + + + diff --git a/src/System.Text.Json/src/System/Text/Json/Node/DuplicatePropertyNameHandling.cs b/src/System.Text.Json/src/System/Text/Json/Node/DuplicatePropertyNameHandling.cs new file mode 100644 index 000000000000..df4840318ba0 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Node/DuplicatePropertyNameHandling.cs @@ -0,0 +1,21 @@ +namespace System.Text.Json +{ + /// + /// Specifies how duplicate property names are handled when added to JSON object. + /// + public enum DuplicatePropertyNameHandling + { + /// + /// Replace the existing value when there is a duplicate property. The value of the last property in the JSON object will be used. + /// + Replace, + /// + /// Ignore the new value when there is a duplicate property. The value of the first property in the JSON object will be used. + /// + Ignore, + /// + /// Throw an when a duplicate property is encountered. + /// + Error, + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs index a1af8734e0cb..985ed4843def 100644 --- a/src/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs @@ -5,7 +5,7 @@ namespace System.Text.Json { /// - /// Represents a boolean JSON value. + /// Represents a mutable boolean JSON value. /// public sealed class JsonBoolean : JsonNode, IEquatable { diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs index 8b40fec4b3c8..ec16a6642c76 100644 --- a/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs @@ -5,7 +5,7 @@ namespace System.Text.Json { /// - /// The base class that represents a single node within a JSON document. + /// The base class that represents a single node within a mutable JSON document. /// public abstract class JsonNode { diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs index 57b4c754bcfc..a4c639d6da35 100644 --- a/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs @@ -7,7 +7,7 @@ namespace System.Text.Json { /// - /// Represents a numeric JSON value. + /// Represents a mutable numeric JSON value. /// public sealed class JsonNumber : JsonNode, IEquatable { diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs new file mode 100644 index 000000000000..19a35a163fc1 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs @@ -0,0 +1,494 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Text.Json +{ + /// + /// Represents a mutable JSON object. + /// + public sealed class JsonObject : JsonNode, IEnumerable> + { + internal readonly Dictionary _dictionary; + private readonly DuplicatePropertyNameHandling _duplicatePropertyNameHandling; + + /// + /// 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) + { + if ((uint)duplicatePropertyNameHandling > (uint)DuplicatePropertyNameHandling.Error) + { + throw new ArgumentOutOfRangeException(SR.InvalidDuplicatePropertyNameHandling); + } + + _dictionary = new Dictionary(); + _duplicatePropertyNameHandling = duplicatePropertyNameHandling; + } + + /// + /// 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) + => AddRange(jsonProperties); + + /// + /// Gets or sets the value of the specified property. + /// + /// The property name of the value to get or set. + /// + /// Provided property name is null. + /// + public JsonNode this[string propertyName] + { + get => propertyName != null ? GetPropertyValue(propertyName) : throw new ArgumentNullException(nameof(propertyName)); + set + { + if (propertyName == null) + throw new ArgumentNullException(nameof(propertyName)); + + _dictionary[propertyName] = value; + } + } + + /// + /// 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 IEnumerator> GetEnumerator() => new JsonObjectEnumerator(this); + + /// + /// Adds the specified property to the JSON object. + /// + /// The property to add. + /// + /// Property name to set already exists if handling duplicates is set to . + /// + public void Add(KeyValuePair jsonProperty) => Add(jsonProperty.Key, jsonProperty.Value); + + /// + /// Adds the specified property to the JSON object. + /// + /// Name of the property to add. + /// Value of the property to add. + /// + /// Provided property name is null. + /// + /// + /// Property name to set already exists if handling duplicates is set to . + /// + /// + /// Null property value is allowed and represents a null JSON node. + /// + public void Add(string propertyName, JsonNode propertyValue) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + 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); + } + + _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)); + + /// + /// 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)); + + /// + /// 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)); + + /// + /// 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, byte propertyValue) => Add(propertyName, new JsonNumber(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, short propertyValue) => Add(propertyName, new JsonNumber(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, int propertyValue) => Add(propertyName, new JsonNumber(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, long propertyValue) => Add(propertyName, new JsonNumber(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 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)); + + /// + /// 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)); + + /// + /// 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, sbyte propertyValue) => Add(propertyName, new JsonNumber(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. + /// + [CLSCompliant(false)] + public void Add(string propertyName, ushort propertyValue) => Add(propertyName, new JsonNumber(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. + /// + [CLSCompliant(false)] + public void Add(string propertyName, uint propertyValue) => Add(propertyName, new JsonNumber(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. + /// + [CLSCompliant(false)] + public void Add(string propertyName, ulong propertyValue) => Add(propertyName, new JsonNumber(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, decimal propertyValue) => Add(propertyName, new JsonNumber(propertyValue)); + + /// + /// 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) + { + foreach (KeyValuePair property in jsonProperties) + { + Add(property); + } + } + + /// + /// Removes the property with the specified name. + /// + /// + /// + /// if the property is successfully found in a JSON object and removed, + /// otherwise. + /// + /// + /// Provided property name is null. + /// + public bool Remove(string propertyName) => propertyName != null ? _dictionary.Remove(propertyName) : throw new ArgumentNullException(nameof(propertyName)); + + /// + /// 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, + /// otherwise. + /// + /// + /// Provided property name is null. + /// + public bool ContainsProperty(string propertyName) => propertyName != null ? _dictionary.ContainsKey(propertyName) : throw new ArgumentNullException(nameof(propertyName)); + + /// + /// Returns the value of a property with the specified name. + /// + /// Name of the property to return. + /// Value of the property with the specified name. + /// + /// Property with specified name is not found in JSON object. + /// + public JsonNode GetPropertyValue(string propertyName) + { + if (!TryGetPropertyValue(propertyName, 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. + /// 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); + + /// + /// Returns the JSON object value of a property with the specified name. + /// + /// Name of the property to return. + /// 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) + { + if (GetPropertyValue(propertyName) is JsonObject jsonObject) + { + return jsonObject; + } + + throw new InvalidCastException(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; + /// otherwise, + /// + public bool TryGetJsonObjectPropertyValue(string propertyName, out JsonObject jsonObject) + { + if (TryGetPropertyValue(propertyName, out JsonNode jsonNode)) + { + if (jsonNode is JsonObject jsonNodeCasted) + { + jsonObject = jsonNodeCasted; + return true; + } + } + + jsonObject = null; + return false; + } + + /// + /// A collection containing the property names of JSON object. + /// + public ICollection PropertyNames => _dictionary.Keys; + + /// + /// A collection containing the property values of JSON object. + /// + public ICollection PropertyValues => _dictionary.Values; + + /// + /// Returns an enumerator that iterates through the JSON object properties. + /// + /// An enumerator structure for the . + IEnumerator IEnumerable.GetEnumerator() => new JsonObjectEnumerator(this); + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs new file mode 100644 index 000000000000..2723e21ccc3d --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs @@ -0,0 +1,45 @@ +using System.Collections; +using System.Collections.Generic; + +namespace System.Text.Json +{ + /// + /// Supports an iteration over a JSON object. + /// + public struct JsonObjectEnumerator : IEnumerator> + { + private readonly IEnumerator> _enumerator; + + /// + /// 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(); + + /// + /// Gets the property in the JSON object at the current position of the enumerator. + /// + public KeyValuePair Current => _enumerator.Current; + + /// + /// Gets the property in the JSON object at the current position of the enumerator. + /// + object IEnumerator.Current => _enumerator.Current; + + /// + /// Releases all resources used by the . + /// + public void Dispose() => _enumerator.Dispose(); + + /// + /// Advances the enumerator to the next property of the JSON object. + /// + /// + public bool MoveNext() => _enumerator.MoveNext(); + + /// + /// Sets the enumerator to its initial position, which is before the first element in the JSON object. + /// + public void Reset() => _enumerator.Reset(); + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs b/src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs index 601272d43164..bcf459c7e30b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs +++ b/src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs @@ -5,7 +5,7 @@ namespace System.Text.Json { /// - /// Represents a text JSON value. + /// Represents a mutable text JSON value. /// public sealed class JsonString : JsonNode, IEquatable { @@ -31,6 +31,24 @@ public sealed class JsonString : JsonNode, IEquatable /// The value to represent as a JSON string. public JsonString(ReadOnlySpan value) => Value = value.ToString(); + /// + /// Initializes a new instance of the with a string representation of the structure. + /// + /// The value to represent as a JSON string. + public JsonString(Guid value) => Value = value.ToString(); + + /// + /// Initializes a new instance of the with a string representation of the structure. + /// + /// The value to represent as a JSON string. + public JsonString(DateTime value) => Value = value.ToString(); + + /// + /// Initializes a new instance of the with a string representation of the structure. + /// + /// The value to represent as a JSON string. + public JsonString(DateTimeOffset value) => Value = value.ToString(); + /// /// Gets or sets the text value represented by the instance. /// @@ -40,7 +58,7 @@ public sealed class JsonString : JsonNode, IEquatable public string Value { get => _value; - set => _value = value ?? throw new ArgumentNullException(); + set => _value = value ?? throw new ArgumentNullException(nameof(value)); } /// diff --git a/src/System.Text.Json/tests/JsonObjectTests.TestData.cs b/src/System.Text.Json/tests/JsonObjectTests.TestData.cs new file mode 100644 index 000000000000..fb324ed49400 --- /dev/null +++ b/src/System.Text.Json/tests/JsonObjectTests.TestData.cs @@ -0,0 +1,120 @@ +// 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; + +namespace System.Text.Json.Tests +{ + public static partial class JsonObjectTests + { + /// + /// Helper class simulating external library + /// + private static class EmployeesDatabase + { + private static int s_id = 0; + public static KeyValuePair GetNextEmployee() + { + var employee = new JsonObject() + { + { "name", "John" } , + { "surname", "Smith"}, + { "age", 45 } + }; + + return new KeyValuePair("employee" + s_id++, employee); + } + + public static IEnumerable> GetTenBestEmployees() + { + for (int i = 0; i < 10; i++) + yield return GetNextEmployee(); + } + + /// + /// Returns following JsonObject: + /// { + /// { "name" : "John" } + /// { "phone numbers" : { "work" : "123-456-7890", "home": "123-456-7890" } } + /// { + /// "reporting employees" : + /// { + /// "software developers" : + /// { + /// "full time employees" : /JsonObject of 3 employees fromk database/ + /// "intern employees" : /JsonObject of 2 employees fromk database/ + /// }, + /// "HR" : /JsonObject of 10 employees fromk database/ + /// } + /// + /// + public static JsonObject GetManager() + { + var manager = GetNextEmployee().Value as JsonObject; + + manager.Add + ( + "phone numbers", + new JsonObject() + { + { "work", "123-456-7890" }, { "home", "123-456-7890" } + } + ); + + manager.Add + ( + "reporting employees", new JsonObject() + { + { + "software developers", new JsonObject() + { + { + "full time employees", new JsonObject() + { + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + } + }, + { + "intern employees", new JsonObject() + { + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + } + } + } + }, + { + "HR", new JsonObject() + { + { + "full time employees", new JsonObject(EmployeesDatabase.GetTenBestEmployees()) + } + } + } + } + ); + + return manager; + } + public static void PerformHeavyOperations(JsonElement employee) { } + } + + private static class HealthCare + { + public static void CreateMedicalAppointment(string personName) { } + } + + /// + /// Helper class simulating enum + /// + private enum AvailableStateCodes + { + WA, + CA, + NY, + } + } +} diff --git a/src/System.Text.Json/tests/JsonObjectTests.cs b/src/System.Text.Json/tests/JsonObjectTests.cs new file mode 100644 index 000000000000..776b5e559850 --- /dev/null +++ b/src/System.Text.Json/tests/JsonObjectTests.cs @@ -0,0 +1,584 @@ +// 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 Xunit; + +namespace System.Text.Json.Tests +{ + public static partial class JsonObjectTests + { + [Fact] + public static void TestDefaultConstructor() + { + var jsonObject = new JsonObject(); + Assert.Equal(0, jsonObject.PropertyNames.Count); + Assert.Equal(0, jsonObject.PropertyValues.Count); + } + + [Fact] + public static void TestIEnumerableKVPConstructor() + { + var jsonProperties = new List>(); + jsonProperties.Add(new KeyValuePair("number", new JsonNumber(17))); + jsonProperties.Add(new KeyValuePair("string", new JsonString("property value"))); + jsonProperties.Add(new KeyValuePair("boolean", new JsonBoolean(true))); + + var jsonObject = new JsonObject(jsonProperties); + + Assert.Equal(17, ((JsonNumber)jsonObject["number"]).GetInt32()); + Assert.Equal("property value", ((JsonString)jsonObject["string"]).Value); + 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() + { + Assert.Throws(() => TestDuplicates(DuplicatePropertyNameHandling.Error, "", "", "")); + + JsonObject jsonObject = new JsonObject(DuplicatePropertyNameHandling.Error) { { "property", "" } }; + jsonObject["property"] = (JsonString) "indexer value"; + Assert.Equal("indexer value", (JsonString) jsonObject["property"]); + } + + [Fact] + public static void TestNumerics() + { + var jsonObject = new JsonObject(); + + jsonObject.Add("byte", byte.MaxValue); + Assert.Equal(byte.MaxValue, ((JsonNumber)jsonObject["byte"]).GetByte()); + + jsonObject.Add("short", short.MaxValue); + Assert.Equal(short.MaxValue, ((JsonNumber)jsonObject["short"]).GetInt16()); + + jsonObject.Add("int", int.MaxValue); + Assert.Equal(int.MaxValue, ((JsonNumber)jsonObject["int"]).GetInt32()); + + jsonObject.Add("long", long.MaxValue); + Assert.Equal(long.MaxValue, ((JsonNumber)jsonObject["long"]).GetInt64()); + + jsonObject.Add("float", 3.14f); + Assert.Equal(3.14f, ((JsonNumber)jsonObject["float"]).GetSingle()); + + jsonObject.Add("double", 3.14); + Assert.Equal(3.14, ((JsonNumber)jsonObject["double"]).GetDouble()); + + jsonObject.Add("sbyte", sbyte.MaxValue); + Assert.Equal(sbyte.MaxValue, ((JsonNumber)jsonObject["sbyte"]).GetSByte()); + + jsonObject.Add("ushort", ushort.MaxValue); + Assert.Equal(ushort.MaxValue, ((JsonNumber)jsonObject["ushort"]).GetUInt16()); + + jsonObject.Add("uint", uint.MaxValue); + Assert.Equal(uint.MaxValue, ((JsonNumber)jsonObject["uint"]).GetUInt32()); + + jsonObject.Add("ulong", ulong.MaxValue); + Assert.Equal(ulong.MaxValue, ((JsonNumber)jsonObject["ulong"]).GetUInt64()); + + jsonObject.Add("decimal", decimal.One); + Assert.Equal(decimal.One, ((JsonNumber)jsonObject["decimal"]).GetDecimal()); + } + + [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"]); + } + + [Fact] + public static void TestDateTime() + { + DateTime dateTime = new DateTime(DateTime.MinValue.Ticks); + var jsonObject = new JsonObject { { "dateTime", dateTime } }; + Assert.Equal(dateTime.ToString(), (JsonString)jsonObject["dateTime"]); + } + + [Fact] + public static void TestDateTimeOffset() + { + DateTimeOffset dateTimeOffset = new DateTime(DateTime.MinValue.Ticks); + var jsonObject = new JsonObject { { "dateTimeOffset", dateTimeOffset } }; + Assert.Equal(dateTimeOffset.ToString(), (JsonString)jsonObject["dateTimeOffset"]); + } + + [Fact] + public static void TestCreatingJsonObject() + { + var developer = new JsonObject + { + { "name", "Kasia" }, + { "n\\u0061me", "Kasia" }, // different property name than above one + { "age", 22 }, + { "is developer", true }, + { "null property", (JsonNode) null } + }; + + Assert.Equal("Kasia", ((JsonString)developer["name"]).Value); + 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"]); + } + + [Fact] + public static void TestCreatingJsonObjectNewMethods() + { + var developer = new JsonObject + { + { "name", new JsonString("Kasia") }, + { "age", new JsonNumber(22) }, + { "is developer", new JsonBoolean(true) } + }; + + Assert.Equal("Kasia", ((JsonString)developer["name"]).Value); + Assert.Equal(22, ((JsonNumber)developer["age"]).GetInt32()); + Assert.True(((JsonBoolean)developer["is developer"]).Value); + } + + [Fact] + public static void TestCreatingNestedJsonObject() + { + var person = new JsonObject + { + { "name", "John" }, + { "surname", "Smith" }, + { + "phone numbers", new JsonObject() + { + { "work", "123-456-7890" }, + { "home", "123-456-7890" } + } + }, + { + "addresses", new JsonObject() + { + { + "office", new JsonObject() + { + { "address line 1", "One Microsoft Way" }, + { "city" , "Redmond" } , + { "zip code" , 98052 } , + { "state" , (int) AvailableStateCodes.WA } + } + }, + { + "home", new JsonObject() + { + { "address line 1", "Pear Ave" }, + { "address line 2", "1288" }, + { "city" , "Mountain View" } , + { "zip code" , 94043 } , + { "state" , (int) AvailableStateCodes.CA } + } + } + } + } + }; + + Assert.IsType(person["phone numbers"]); + var phoneNumbers = person["phone numbers"] as JsonObject; + Assert.IsType(phoneNumbers["work"]); + Assert.IsType(phoneNumbers["home"]); + + Assert.IsType(person["addresses"]); + var addresses = person["addresses"] as JsonObject; + Assert.IsType(addresses["office"]); + Assert.IsType(addresses["home"]); + } + + [Fact] + public static void TestAssignmentDefinition() + { + JsonNode employee = EmployeesDatabase.GetNextEmployee().Value; + Assert.IsType(employee); + } + + private static void CheckEmployeesAreDifferent(JsonObject employees) + { + string prevId = ""; + foreach (KeyValuePair employee in employees) + { + Assert.NotEqual(prevId, employee.Key); + prevId = employee.Key; + + Assert.IsType(employee.Value); + } + } + + [Fact] + public static void TestAddingKeyValuePair() + { + var employees = new JsonObject + { + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + EmployeesDatabase.GetNextEmployee(), + }; + + CheckEmployeesAreDifferent(employees); + } + + [Fact] + public static void TestAddingKeyValuePairAfterInitialization() + { + var employees = new JsonObject(); + foreach (KeyValuePair employee in EmployeesDatabase.GetTenBestEmployees()) + { + employees.Add(employee); + } + + CheckEmployeesAreDifferent(employees); + } + + [Fact] + public static void TestAddingKeyValuePairsCollection() + { + var employees = new JsonObject(EmployeesDatabase.GetTenBestEmployees()); + + CheckEmployeesAreDifferent(employees); + } + + [Fact] + public static void TestAddingKeyValuePairsCollectionAfterInitialization() + { + var employees = new JsonObject(); + employees.AddRange(EmployeesDatabase.GetTenBestEmployees()); + + CheckEmployeesAreDifferent(employees); + } + + [Fact] + public static void TestContains() + { + var person = new JsonObject + { + { "name", "John" }, + { "ssn", "123456789" }, + }; + + Assert.True(person.ContainsProperty("ssn")); + Assert.Equal("123456789", (JsonString)person["ssn"]); + Assert.False(person.ContainsProperty("surname")); + } + + [Fact] + public static void TestAquiringAllValues() + { + var employees = new JsonObject(EmployeesDatabase.GetTenBestEmployees()); + ICollection employeesWithoutId = employees.PropertyValues; + + Assert.Equal(10, employees.PropertyNames.Count); + Assert.Equal(10, employees.PropertyValues.Count); + + foreach (JsonNode employee in employeesWithoutId) + { + Assert.IsType(employee); + } + } + + [Fact] + public static void TestReplacingsonObjectPrimaryTypes() + { + var person1 = new JsonObject + { + { "name", "John" }, + { "age", 45 }, + { "is_married", true } + }; + + + person1["name"] = new JsonString("Bob"); + Assert.Equal("Bob", (JsonString)person1["name"]); + + person1["age"] = new JsonNumber(55); + Assert.Equal(55, (JsonNumber)person1["age"]); + + person1["is_married"] = new JsonBoolean(false); + Assert.Equal(false, (JsonBoolean)person1["is_married"]); + + var person2 = new JsonObject + { + { "name", "Bob" }, + { "age", 33 }, + { "is_married", true } + }; + + // Copy property from another JsonObject + person1["age"] = person2["age"]; + + Assert.Equal(33, (JsonNumber) person1["age"]); + + // Copy property of different typoe + person1["name"] = person2["name"]; + } + + [Fact] + public static void TestModifyingJsonObjectKeyRemoveAdd() + { + JsonObject manager = EmployeesDatabase.GetManager(); + JsonObject reportingEmployees = manager.GetJsonObjectPropertyValue("reporting employees"); + + static void ModifyProperty(JsonObject jsonObject, string previousName, string newName) + { + JsonNode previousValue = jsonObject[previousName]; + jsonObject.Remove(previousName); + jsonObject.Add(newName, previousValue); + } + + string previousName = "software developers"; + string newName = "software engineers"; + + Assert.True(reportingEmployees.ContainsProperty(previousName)); + JsonNode previousValue = reportingEmployees[previousName]; + + ModifyProperty(reportingEmployees, previousName, newName); + + Assert.False(reportingEmployees.ContainsProperty(previousName)); + Assert.True(reportingEmployees.ContainsProperty(newName)); + Assert.Equal(previousValue, reportingEmployees[newName]); + } + + [Fact] + public static void TestAccessingNestedJsonObjectCastWithAs() + { + JsonObject manager = EmployeesDatabase.GetManager(); + + var reportingEmployees = manager["reporting employees"] as JsonObject; + Assert.NotNull(reportingEmployees); + + var softwareDevelopers = reportingEmployees["software developers"] as JsonObject; + Assert.NotNull(softwareDevelopers); + + var internDevelopers = softwareDevelopers["intern employees"] as JsonObject; + Assert.NotNull(internDevelopers); + + internDevelopers.Add(EmployeesDatabase.GetNextEmployee()); + } + + [Fact] + public static void TestAccessingNestedJsonObjectCastWithIs() + { + JsonObject manager = EmployeesDatabase.GetManager(); + + static bool AddEmployee(JsonObject manager) + { + + if (manager["reporting employees"] is JsonObject reportingEmployees) + { + if (reportingEmployees["software developers"] is JsonObject softwareDevelopers) + { + if (softwareDevelopers["full time employees"] is JsonObject fullTimeEmployees) + { + fullTimeEmployees.Add(EmployeesDatabase.GetNextEmployee()); + return true; + } + } + } + return false; + } + + bool success = AddEmployee(manager); + Assert.True(success); + } + + [Fact] + public static void TestAccessingNestedJsonObjectExplicitCast() + { + JsonObject manager = EmployeesDatabase.GetManager(); + + // Should not throw any exceptions: + ((JsonObject)((JsonObject)manager["reporting employees"])["HR"]).Add(EmployeesDatabase.GetNextEmployee()); + } + + [Fact] + public static void TestAccessingNestedJsonObjectGetPropertyMethod() + { + JsonObject manager = EmployeesDatabase.GetManager(); + + // Should not throw any exceptions: + + JsonObject internDevelopers = manager.GetJsonObjectPropertyValue("reporting employees") + .GetJsonObjectPropertyValue("software developers") + .GetJsonObjectPropertyValue("intern employees"); + internDevelopers.Add(EmployeesDatabase.GetNextEmployee()); + } + + [Fact] + public static void TestAccessingNestedJsonObjectTryGetPropertyMethod() + { + JsonObject manager = EmployeesDatabase.GetManager(); + + static bool AddEmployee(JsonObject manager) + { + if (manager.TryGetJsonObjectPropertyValue("reporting employees", out JsonObject reportingEmployees)) + { + if (reportingEmployees.TryGetJsonObjectPropertyValue("software developers", out JsonObject softwareDevelopers)) + { + if (softwareDevelopers.TryGetJsonObjectPropertyValue("full time employees", out JsonObject fullTimeEmployees)) + { + fullTimeEmployees.Add(EmployeesDatabase.GetNextEmployee()); + return true; + } + } + } + + return false; + } + + bool success = AddEmployee(manager); + Assert.True(success); + } + + [Fact] + public static void TestGetPropertyThrows() + { + Assert.Throws(() => + { + var jsonObject = new JsonObject() + { + { "name", "value" } + }; + + jsonObject.GetPropertyValue("different name"); + }); + } + + [Fact] + public static void TestTryGetProperty () + { + var jsonObject = new JsonObject() + { + { "name", "value" } + }; + + Assert.True(jsonObject.TryGetPropertyValue("name", out JsonNode property)); + Assert.Equal("value", (JsonString)property); + Assert.False(jsonObject.TryGetPropertyValue("other", out property)); + Assert.Null(property); + } + + [Fact] + public static void TestGetJsonObjectPropertyThrows() + { + Assert.Throws(() => + { + var jsonObject = new JsonObject() + { + { "name", "value" } + }; + + jsonObject.GetJsonObjectPropertyValue("name"); + }); + } + + [Fact] + public static void TestTryGetObjectPropertyFails() + { + var jsonObject = new JsonObject() + { + { "name", "value" } + }; + + Assert.False(jsonObject.TryGetJsonObjectPropertyValue("name", out JsonObject property)); + Assert.Null(property); + + Assert.False(jsonObject.TryGetJsonObjectPropertyValue("other", out property)); + Assert.Null(property); + } + + [Fact] + public static void TestArgumentNullValidation() + { + Assert.Throws(() => new JsonObject().Add(null, "")); + Assert.Throws(() => new JsonObject().Add(null, new JsonObject())); + Assert.Throws(() => new JsonObject().Add(null, (byte)17)); + Assert.Throws(() => new JsonObject().Add(null, (short)17)); + Assert.Throws(() => new JsonObject().Add(null, 17)); + Assert.Throws(() => new JsonObject().Add(null, (long)17)); + Assert.Throws(() => new JsonObject().Add(null, 3.14f)); + Assert.Throws(() => new JsonObject().Add(null, 3.14)); + Assert.Throws(() => new JsonObject().Add(null, decimal.One)); + Assert.Throws(() => new JsonObject().Add(null, (sbyte)17)); + Assert.Throws(() => new JsonObject().Add(null, (ushort)17)); + Assert.Throws(() => new JsonObject().Add(null, (uint)17)); + Assert.Throws(() => new JsonObject().Add(null, (ulong)17)); + Assert.Throws(() => new JsonObject().Add(new KeyValuePair(null, new JsonObject()))); + Assert.Throws(() => + { + var property = new KeyValuePair(null, new JsonObject()); + new JsonObject().AddRange(new List>() { property }); + }); + Assert.Throws(() => + { + var property1 = new KeyValuePair("regular property", new JsonObject()); + var property2 = new KeyValuePair("regular property2", new JsonObject()); + var nullProperty = new KeyValuePair(null, new JsonObject()); + var propertyList = new List>() { property1, nullProperty, property2 }; + var jsonObject = new JsonObject(propertyList); + }); + Assert.Throws(() => new JsonObject()[null] = new JsonString()); + Assert.Throws(() => + { + var jsonObject = new JsonObject(); + JsonNode x = jsonObject[null]; + }); + Assert.Throws(() => new JsonObject().Remove(null)); + Assert.Throws(() => new JsonObject().ContainsProperty(null)); + } + + [Fact] + public static void TestDuplicatesEnumOutOfRange() + { + Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)123)); + Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)(-1))); + Assert.Throws(() => new JsonObject((DuplicatePropertyNameHandling)3)); + } + } +} diff --git a/src/System.Text.Json/tests/JsonStringTests.cs b/src/System.Text.Json/tests/JsonStringTests.cs index 94339e131b53..449117877ac3 100644 --- a/src/System.Text.Json/tests/JsonStringTests.cs +++ b/src/System.Text.Json/tests/JsonStringTests.cs @@ -61,12 +61,38 @@ public static void TestNulls() [Fact] public static void TestReadonlySpan() { - var spanValue = new ReadOnlySpan(new char[] { 's', 'p', 'a', 'n'}); + var spanValue = new ReadOnlySpan(new char[] { 's', 'p', 'a', 'n' }); Assert.Equal("span", new JsonString(spanValue).Value); string property = null; spanValue = property.AsSpan(); var jsonString = new JsonString(spanValue); + Assert.Equal("", jsonString.Value); + } + + [Fact] + public static void TestGuid() + { + var guidString = "ca761232-ed42-11ce-bacd-00aa0057b223"; + Guid guid = new Guid(guidString); + var jsonString = new JsonString(guid); + Assert.Equal(guidString, jsonString); + } + + [Fact] + public static void TestDateTime() + { + DateTime dateTime = new DateTime(DateTime.MinValue.Ticks); + var jsonString = new JsonString(dateTime); + Assert.Equal(dateTime.ToString(), jsonString); + } + + [Fact] + public static void TestDateTimeOffset() + { + DateTimeOffset dateTimeOffset = new DateTime(DateTime.MinValue.Ticks); + var jsonString = new JsonString(dateTimeOffset); + Assert.Equal(dateTimeOffset.ToString(), jsonString); } [Fact] diff --git a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj index 5609afaffdfa..3f6fe6d737c3 100644 --- a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -110,14 +110,14 @@ CommonTest\System\Buffers\ArrayBufferWriter.cs - - - + + +