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 c47485f4125268..0554df8190e778 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -850,6 +850,8 @@ public sealed partial class JsonObject : System.Text.Json.Nodes.JsonNode, System System.Collections.Generic.KeyValuePair System.Collections.Generic.IList>.this[int index] { get { throw null; } set { } } public void Add(System.Collections.Generic.KeyValuePair property) { } public void Add(string propertyName, System.Text.Json.Nodes.JsonNode? value) { } + public bool TryAdd(string propertyName, JsonNode? value) { throw null; } + public bool TryAdd(string propertyName, JsonNode? value, out int index) { throw null; } public void Clear() { } public bool ContainsKey(string propertyName) { throw null; } public static System.Text.Json.Nodes.JsonObject? Create(System.Text.Json.JsonElement element, System.Text.Json.Nodes.JsonNodeOptions? options = default(System.Text.Json.Nodes.JsonNodeOptions?)) { throw null; } @@ -870,6 +872,7 @@ public void SetAt(int index, System.Text.Json.Nodes.JsonNode? value) { } void System.Collections.Generic.IList>.RemoveAt(int index) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryGetPropertyValue(string propertyName, out System.Text.Json.Nodes.JsonNode? jsonNode) { throw null; } + public bool TryGetPropertyValue(string propertyName, out System.Text.Json.Nodes.JsonNode? jsonNode, out int index) { throw null; } public override void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null) { } } public abstract partial class JsonValue : System.Text.Json.Nodes.JsonNode diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs index 857cb57f2c7cf5..f86362c9eea2ca 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs @@ -31,6 +31,46 @@ public void Add(string propertyName, JsonNode? value) value?.AssignParent(this); } + /// + /// Adds an element with the provided name and value to the , if a property named doesn't already exist. + /// + /// The property name of the element to add. + /// The value of the element to add. + /// is null. + /// + /// if the property didn't exist and the element was added; otherwise, . + /// + public bool TryAdd(string propertyName, JsonNode? value) => TryAdd(propertyName, value, out _); + + /// + /// Adds an element with the provided name and value to the , if a property named doesn't already exist. + /// + /// The property name of the element to add. + /// The value of the element to add. + /// The index of the added or existing . This is always a valid index into the . + /// is null. + /// + /// if the property didn't exist and the element was added; otherwise, . + /// + public bool TryAdd(string propertyName, JsonNode? value, out int index) + { + if (propertyName is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(propertyName)); + } +#if NET9_0 + bool success = Dictionary.TryAdd(propertyName, value); + index = success ? Dictionary.Count - 1 : Dictionary.IndexOf(propertyName); +#else + bool success = Dictionary.TryAdd(propertyName, value, out index); +#endif + if (success) + { + value?.AssignParent(this); + } + return success; + } + /// /// Adds the specified property to the . /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs index f903a005d76dfd..ebab8d554de25e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs @@ -115,14 +115,46 @@ internal string GetPropertyName(JsonNode? node) /// /// The name of the property to return. /// The JSON value of the property with the specified name. + /// + /// is . + /// /// /// if a property with the specified name was found; otherwise, . /// - public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode) + public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode) => TryGetPropertyValue(propertyName, out jsonNode, out _); + + /// + /// Gets the value associated with the specified property name. + /// + /// The property name of the value to get. + /// + /// When this method returns, it contains the value associated with the specified property name, if the property name is found; + /// otherwise . + /// + /// The index of if found; otherwise, -1. + /// + /// is . + /// + /// + /// if the contains an element with the specified property name; otherwise, . + /// + public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode, out int index) { ArgumentNullException.ThrowIfNull(propertyName); - return Dictionary.TryGetValue(propertyName, out jsonNode); +#if NET9_0 + index = Dictionary.IndexOf(propertyName); + if (index < 0) + { + jsonNode = null; + return false; + } + + jsonNode = Dictionary.GetAt(index).Value; + return true; +#else + return Dictionary.TryGetValue(propertyName, out jsonNode, out index); +#endif } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs index c824dd97952198..fb97edcf75f54d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs @@ -35,15 +35,9 @@ internal override void ReadElementAndSetProperty( { jObject[propertyName] = jNodeValue; } - else + else if (!jObject.TryAdd(propertyName, jNodeValue)) { - // TODO: Use TryAdd once https://github.com/dotnet/runtime/issues/110244 is resolved. - if (jObject.ContainsKey(propertyName)) - { - ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(propertyName); - } - - jObject.Add(propertyName, jNodeValue); + ThrowHelper.ThrowJsonException_DuplicatePropertyNotAllowed(propertyName); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs index f1e4ee077b76c3..d24280b86c5fb0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs @@ -1604,6 +1604,69 @@ public static void JsonObject_IsIList() Assert.Equal(-1, jObject.IndexOf("Two")); } + [Fact] + public static void TryAdd_NewKey_EmptyJsonObject() + { + JsonObject jObject = new(); + + Assert.True(jObject.TryAdd("First", "value", out var index)); + Assert.Equal(0, index); + } + + [Fact] + public static void TryAdd_NewKey() + { + JsonObject jObject = new() + { + ["One"] = 1, + ["Two"] = "str", + ["Three"] = null, + }; + + Assert.True(jObject.TryAdd("Four", 33, out var index)); + Assert.Equal(3, index); + } + + [Fact] + public static void TryAdd_ExistingKey() + { + JsonObject jObject = new() + { + ["One"] = 1, + ["Two"] = "str", + ["Three"] = null, + }; + + Assert.False(jObject.TryAdd("Two", 33, out var index)); + Assert.Equal(1, index); + } + + [Fact] + public static void TryAdd_ThrowsArgumentNullException() + { + JsonObject jObject = new() + { + ["One"] = 1, + ["Two"] = "str", + ["Three"] = null, + }; + + Assert.Throws(() => { jObject.TryAdd(null, 33); }); + } + + [Fact] + public static void TryGetPropertyValue_ThrowsArgumentNullException() + { + JsonObject jObject = new() + { + ["One"] = 1, + ["Two"] = "str", + ["Three"] = null, + }; + + Assert.Throws(() => { jObject.TryGetPropertyValue(null, out var jsonNode, out var index); }); + } + [Theory] [InlineData(10_000)] [InlineData(50_000)] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs index 7c6e82db5d30bd..81619d89665c9b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/ParseTests.cs @@ -84,6 +84,60 @@ public static void Parse_TryGetPropertyValue() Assert.IsType(node); } + [Fact] + public static void Parse_TryGetPropertyValueWithIndex() + { + JsonObject jObject = JsonNode.Parse(JsonNodeTests.ExpectedDomJson).AsObject(); + + JsonNode? node; + int index; + + Assert.True(jObject.TryGetPropertyValue("MyString", out node, out index)); + Assert.Equal("Hello!", node.GetValue()); + Assert.Equal(0, index); + + Assert.True(jObject.TryGetPropertyValue("MyNull", out node, out index)); + Assert.Null(node); + Assert.Equal(1, index); + + Assert.True(jObject.TryGetPropertyValue("MyBoolean", out node, out index)); + Assert.False(node.GetValue()); + Assert.Equal(2, index); + + Assert.True(jObject.TryGetPropertyValue("MyArray", out node, out index)); + Assert.IsType(node); + Assert.Equal(3, index); + + Assert.True(jObject.TryGetPropertyValue("MyInt", out node, out index)); + Assert.Equal(43, node.GetValue()); + Assert.Equal(4, index); + + Assert.True(jObject.TryGetPropertyValue("MyDateTime", out node, out index)); + Assert.Equal("2020-07-08T00:00:00", node.GetValue()); + Assert.Equal(5, index); + + Assert.True(jObject.TryGetPropertyValue("MyGuid", out node, out index)); + Assert.Equal("ed957609-cdfe-412f-88c1-02daca1b4f51", node.AsValue().GetValue().ToString()); + Assert.Equal(6, index); + + Assert.True(jObject.TryGetPropertyValue("MyObject", out node, out index)); + Assert.IsType(node); + Assert.Equal(7, index); + } + + [Fact] + public static void Parse_TryGetPropertyValueFail() + { + JsonObject jObject = JsonNode.Parse(JsonNodeTests.ExpectedDomJson).AsObject(); + + JsonNode? node; + int index; + + Assert.False(jObject.TryGetPropertyValue("NonExistentKey", out node, out index)); + Assert.Null(node); + Assert.Equal(-1, index); + } + [Fact] public static void Parse_TryGetValue() {