From 305c51c61e4c77ad5703bad9fa67d83e82922177 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 17 Nov 2022 10:06:39 -0800 Subject: [PATCH 01/21] first pass making JsonData internal --- .../src/BinaryDataExtensions.cs | 21 ++ .../Azure.Core.Experimental/src/JsonData.cs | 318 +++++------------- .../tests/JsonDataMutableTests.cs | 30 -- ...zure.Core.Experimental.Tests.Public.csproj | 28 ++ .../public/JsonDataPublicMutableTests.cs | 119 +++++++ .../tests/public/JsonDataPublicTests.cs | 289 ++++++++++++++++ sdk/core/Azure.Core/Azure.Core.sln | 6 + 7 files changed, 556 insertions(+), 255 deletions(-) create mode 100644 sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs delete mode 100644 sdk/core/Azure.Core.Experimental/tests/JsonDataMutableTests.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs diff --git a/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs new file mode 100644 index 000000000000..5b6edb7aca8d --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core; + +namespace Azure +{ + /// + /// Extensions to BinaryData. + /// + public static class BinaryDataExtensions + { + /// + /// + public static dynamic ToDynamic(this BinaryData data) + { + return JsonData.Parse(data); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index fa6f8f1fbb2c..55741a175d94 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -16,8 +16,6 @@ using System.Threading; using System.Threading.Tasks; -#pragma warning disable AZC0014 // Avoid using banned types in public API - namespace Azure.Core { /// @@ -26,7 +24,7 @@ namespace Azure.Core [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(JsonDataDebuggerProxy))] [JsonConverter(typeof(JsonConverter))] - public class JsonData : IDynamicMetaObjectProvider, IEquatable + internal class JsonData : IDynamicMetaObjectProvider, IEquatable { private readonly JsonValueKind _kind; private Dictionary? _objectRepresentation; @@ -35,6 +33,17 @@ public class JsonData : IDynamicMetaObjectProvider, IEquatable private static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new JsonSerializerOptions(); + /// + /// Parses a UTF8 encoded string representing a single JSON value into a . + /// + /// A UTF8 encoded string representing a JSON value. + /// A representation of the value. + internal static JsonData Parse(BinaryData utf8Json) + { + using var doc = JsonDocument.Parse(utf8Json); + return new JsonData(doc); + } + /// /// Creates a new JsonData object which represents an JSON object with no properties. /// @@ -427,150 +436,6 @@ public JsonData SetEmptyArray(string propertyName) return value; } - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(bool value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(double value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(float value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(int value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(long value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// - /// If the property is not this method throws . - /// - public void Add(string? value) => EnsureArray().Add(value); - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// A of the serialized object. - /// - /// If the property is not this method throws . - /// - public JsonData Add(object? serializable) - { - JsonData value = new JsonData(serializable); - EnsureArray().Add(value); - return value; - } - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// Options to control the conversion behavior. - /// A of the serialized object. - /// - /// If the property is not this method throws . - /// - public JsonData Add(object? serializable, JsonSerializerOptions options) - { - JsonData value = new JsonData(serializable, options); - EnsureArray().Add(value); - return value; - } - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// A of the serialized object. - /// - /// If the property is not this method throws . - /// - public JsonData Add(T[] serializable) - { - JsonData value = new JsonData(serializable.Select(x => new JsonData(x))); - EnsureArray().Add(value); - return value; - } - - /// - /// Inserts a new value at the end of an array. - /// - /// The value to insert into the array. - /// Options to control the conversion behavior. - /// A of the serialized object. - /// - /// If the property is not this method throws . - /// - public JsonData Add(T[] serializable, JsonSerializerOptions options) - { - JsonData value = new JsonData(serializable.Select(x => new JsonData(x, options))); - EnsureArray().Add(value); - return value; - } - - /// - /// Inserts a new empty object at the end of an array. - /// - /// A for the newly created empty object. - /// - /// If the property is not this method throws . - /// - public JsonData AddEmptyObject() - { - JsonData value = EmptyObject(); - EnsureArray().Add(value); - return value; - } - - /// - /// Inserts a new empty array at the end of an array. - /// - /// A for the newly created empty array. - /// - /// If the property is not this method throws . - /// - public JsonData AddEmptyArray() - { - JsonData value = EmptyArray(); - EnsureArray().Add(value); - return value; - } - /// /// Gets or sets a value at the given index in an array. /// @@ -582,7 +447,6 @@ public JsonData AddEmptyArray() public JsonData this[int arrayIndex] { get => GetValueAt(arrayIndex); - set => SetValueAt(arrayIndex, value); } /// @@ -596,7 +460,6 @@ public JsonData this[int arrayIndex] public JsonData this[string propertyName] { get => GetPropertyValue(propertyName); - set => SetValue(propertyName, value); } /// @@ -665,72 +528,6 @@ public JsonData this[string propertyName] /// The value to convert. public static explicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(int value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(long value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(double value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(float value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(bool value) =>new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(string? value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(int? value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(long? value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(double? value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(float? value) => new JsonData(value); - - /// - /// Converts an to a . - /// - /// The value to convert. - public static implicit operator JsonData(bool? value) => new JsonData(value); - /// /// Returns true if a has the same value as a given string, /// and false otherwise. @@ -750,7 +547,7 @@ public JsonData this[string propertyName] return false; } - return left.Kind == JsonValueKind.String && ((string?) left._value) == right; + return left.Kind == JsonValueKind.String && ((string?)left._value) == right; } /// @@ -1073,6 +870,19 @@ private object GetDynamicProperty(string propertyName) throw new InvalidOperationException($"Property {propertyName} not found"); } + private JsonData SetViaIndexer(object index, object value) + { + switch (index) + { + case string property: + return SetValue(property, value); + case int i: + return SetValueAt(i, value); + } + + throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); + } + private JsonData SetValue(string propertyName, object value) { if (!(value is JsonData json)) @@ -1209,6 +1019,13 @@ private class MetaObject : DynamicMetaObject private static readonly MethodInfo SetValueMethod = typeof(JsonData).GetMethod(nameof(SetValue), BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo SetIndexerMethod = typeof(JsonData).GetMethod(nameof(SetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly Dictionary Indexers = GetIndexers(); + + // Operators that cast from JsonData to another type + private static readonly Dictionary CastFromOperators = GetCastFromOperators(); + internal MetaObject(Expression parameter, IDynamicMetaObjectProvider value) : base(parameter, BindingRestrictions.Empty, value) { } @@ -1224,17 +1041,37 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) return new DynamicMetaObject(getPropertyCall, restrictions); } + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + var targetObject = Expression.Convert(Expression, LimitType); + var arguments = indexes.Select(i => i.Expression); + var indexProperty = Expression.Property(targetObject, Indexers[indexes[0].LimitType], arguments); + + var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(indexProperty, restrictions); + } + public override DynamicMetaObject BindConvert(ConvertBinder binder) { + Expression targetObject = Expression.Convert(Expression, LimitType); + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + + Expression convertCall; + if (binder.Type == typeof(IEnumerable)) { - var targetObject = Expression.Convert(Expression, LimitType); - var getPropertyCall = Expression.Call(targetObject, GetDynamicEnumerableMethod); + convertCall = Expression.Call(targetObject, GetDynamicEnumerableMethod); + return new DynamicMetaObject(convertCall, restrictions); + } - var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); - return new DynamicMetaObject(getPropertyCall, restrictions); + if (CastFromOperators.TryGetValue(binder.Type, out MethodInfo? castOperator)) + { + convertCall = Expression.Call(castOperator, targetObject); + return new DynamicMetaObject(convertCall, restrictions); } - return base.BindConvert(binder); + + convertCall = Expression.Call(targetObject, nameof(To), new Type[] { binder.Type }); + return new DynamicMetaObject(convertCall, restrictions); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) @@ -1247,6 +1084,35 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM DynamicMetaObject setProperty = new DynamicMetaObject(setPropertyCall, restrictions); return setProperty; } + + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + var targetObject = Expression.Convert(Expression, LimitType); + var arguments = new Expression[2] { + Expression.Convert(indexes[0].Expression, typeof(object)), + Expression.Convert(value.Expression, typeof(object)) + }; + var setCall = Expression.Call(targetObject, SetIndexerMethod, arguments); + + var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(setCall, restrictions); + } + + private static Dictionary GetIndexers() + { + return typeof(JsonData) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Any()) + .ToDictionary(p => p.GetIndexParameters().First().ParameterType); + } + + private static Dictionary GetCastFromOperators() + { + return typeof(JsonData) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(method => method.Name == "op_Explicit" || method.Name == "op_Implicit") + .ToDictionary(method => method.ReturnType); + } } internal class JsonDataDebuggerProxy @@ -1275,7 +1141,8 @@ internal class SingleMember } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public object Members { + public object Members + { get { if (_jsonData.Kind != JsonValueKind.Array && @@ -1283,7 +1150,8 @@ public object Members { return new SingleMember() { Value = _jsonData.ToJsonString() }; return BuildMembers().ToArray(); - }} + } + } private IEnumerable BuildMembers() { @@ -1291,14 +1159,14 @@ private IEnumerable BuildMembers() { foreach (var property in _jsonData.Properties) { - yield return new PropertyMember() {Name = property, Value = _jsonData.Get(property)}; + yield return new PropertyMember() { Name = property, Value = _jsonData.Get(property) }; } } else if (_jsonData.Kind == JsonValueKind.Array) { foreach (var property in _jsonData.Items) { - yield return property; + yield return property; } } } diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataMutableTests.cs deleted file mode 100644 index aa345a3a90e5..000000000000 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataMutableTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using NUnit.Framework; - -namespace Azure.Core.Tests -{ - public class JsonDataMutableTests - { - [Test] - public void ArrayItemsCanBeAssigned() - { - var json = JsonData.FromString("[0, 1, 2, 3]"); - json[1] = 2; - json[2] = null; - json[3] = "string"; - - Assert.AreEqual(json.ToString(), "[0,2,null,\"string\"]"); - } - - [Test] - public void ExistingObjectPropertiesCanBeAssigned() - { - var json = JsonData.FromString("{\"a\":1}"); - json["a"] = "2"; - - Assert.AreEqual(json.ToString(), "{\"a\":\"2\"}"); - } - } -} diff --git a/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj b/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj new file mode 100644 index 000000000000..b51709f9e10b --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj @@ -0,0 +1,28 @@ + + + $(RequiredTargetFrameworks) + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs new file mode 100644 index 000000000000..9accbb31f26f --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Azure.Core.GeoJson; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public +{ + public class JsonDataPublicMutableTests + { + [Test] + public void ArrayItemsCanBeAssigned() + { + dynamic json = new BinaryData("[0, 1, 2, 3]").ToDynamic(); + json[1] = 2; + json[2] = null; + json[3] = "string"; + + Assert.AreEqual(json.ToString(), "[0,2,null,\"string\"]"); + } + + [Test] + public void ExistingObjectPropertiesCanBeAssigned() + { + dynamic json = new BinaryData("{\"a\":1}").ToDynamic(); + json["a"] = "2"; + + Assert.AreEqual(json.ToString(), "{\"a\":\"2\"}"); + } + + [TestCaseSource(nameof(PrimitiveValues))] + public void NewObjectPropertiesCanBeAssignedWithPrimitive(T value, string expected) + { + dynamic json = new BinaryData("{}").ToDynamic(); + json.a = value; + + Assert.AreEqual(json.ToString(), "{\"a\":" + expected + "}"); + } + + [TestCaseSource(nameof(PrimitiveValues))] + public void PrimitiveValuesCanBeParsedDirectly(T value, string expected) + { + dynamic json = new BinaryData(expected).ToDynamic(); + + Assert.AreEqual(value, (T)json); + } + + [Test] + public void NewObjectPropertiesCanBeAssignedWithArrays() + { + dynamic json = new BinaryData("{}").ToDynamic(); + json.a = new object[] { 1, 2, null, "string" }; + + Assert.AreEqual(json.ToString(), "{\"a\":[1,2,null,\"string\"]}"); + } + + [Test] + public void NewObjectPropertiesCanBeAssignedWithObject() + { + var json = new BinaryData("{}").ToDynamic(); + json.a = new { b = 2 }; + + Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); + } + + [Test] + public void NewObjectPropertiesCanBeAssignedWithObjectIndirectly() + { + dynamic json = new BinaryData("{}").ToDynamic(); + dynamic anotherJson = new BinaryData("{}").ToDynamic(); + json.a = anotherJson; + anotherJson.b = 2; + + Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); + } + + [Test] + public void NewObjectPropertiesCanBeAssignedWithSerializedObject() + { + var json = new BinaryData("{}").ToDynamic(); + json.a = new GeoPoint(1, 2); + + Assert.AreEqual("{\"a\":{\"type\":\"Point\",\"coordinates\":[1,2]}}", json.ToString()); + } + + [TestCaseSource(nameof(PrimitiveValues))] + public void CanModifyNestedProperties(T value, string expected) + { + var json = new BinaryData("{\"a\":{\"b\":2}}").ToDynamic(); + json.a.b = value; + + Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":" + expected + "}}"); + Assert.AreEqual(value, (T)json.a.b); + + dynamic reparsedJson = new BinaryData(json.ToString()).ToDynamic(); + + Assert.AreEqual(value, (T)reparsedJson.a.b); + } + + public static IEnumerable PrimitiveValues() + { + yield return new object[] { "string", "\"string\"" }; + yield return new object[] { 1L, "1" }; + yield return new object[] { 1, "1" }; + yield return new object[] { 1.0, "1" }; +#if NETCOREAPP + yield return new object[] {1.1D, "1.1"}; + yield return new object[] {1.1F, "1.100000023841858"}; +#else + yield return new object[] { 1.1D, "1.1000000000000001" }; + yield return new object[] { 1.1F, "1.1000000238418579" }; +#endif + yield return new object[] { true, "true" }; + yield return new object[] { false, "false" }; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs new file mode 100644 index 000000000000..0bba687eb7f1 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public +{ + public class JsonDataPublicTests + { + [Test] + public void CanCreateFromJson() + { + dynamic jsonData = new BinaryData("\"string\"").ToDynamic(); + + Assert.AreEqual("string", (string)jsonData); + } + + [Test] + public void DynamicCanConvertToString() => Assert.AreEqual("string", JsonAsType("\"string\"")); + + [Test] + public void DynamicCanConvertToInt() => Assert.AreEqual(5, JsonAsType("5")); + + [Test] + public void DynamicCanConvertToLong() => Assert.AreEqual(5L, JsonAsType("5")); + + [Test] + public void DynamicCanConvertToBool() => Assert.AreEqual(true, JsonAsType("true")); + + [Test] + public void DynamicCanConvertToNullAsString() => Assert.AreEqual(null, JsonAsType("null")); + + [Test] + public void DynamicCanConvertToNullAsNullableInt() => Assert.AreEqual(null, JsonAsType("null")); + + [Test] + public void DynamicCanConvertToNullAsNullableLong() => Assert.AreEqual(null, JsonAsType("null")); + + [Test] + public void DynamicCanConvertToNullAsNullableBool() => Assert.AreEqual(null, JsonAsType("null")); + + [Test] + public void DynamicCanConvertToIEnumerableDynamic() + { + dynamic jsonData = new BinaryData("[1, null, \"s\"]").ToDynamic(); + int i = 0; + foreach (var dynamicItem in jsonData) + { + switch (i) + { + case 0: + Assert.AreEqual(1, (int)dynamicItem); + break; + case 1: + Assert.AreEqual(null, (string)dynamicItem); + break; + case 2: + Assert.AreEqual("s", (string)dynamicItem); + break; + default: + Assert.Fail(); + break; + } + + i++; + } + Assert.AreEqual(3, i); + } + + [Test] + public void DynamicCanConvertToIEnumerableInt() + { + dynamic jsonData = new BinaryData("[0, 1, 2, 3]").ToDynamic(); + int i = 0; + foreach (int dynamicItem in jsonData) + { + Assert.AreEqual(i, dynamicItem); + + i++; + } + Assert.AreEqual(4, i); + } + + [Test] + public void DynamicArrayHasLength() + { + dynamic jsonData = new BinaryData("[0, 1, 2, 3]").ToDynamic(); + Assert.AreEqual(4, jsonData.Length); + } + + [Test] + public void DynamicArrayFor() + { + dynamic jsonData = new BinaryData("[0, 1, 2, 3]").ToDynamic(); + for (int i = 0; i < jsonData.Length; i++) + { + Assert.AreEqual(i, (int)jsonData[i]); + } + } + + [Test] + public void CanAccessProperties() + { + dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }").ToDynamic(); + + Assert.AreEqual("Hello", (string)jsonData.primitive); + Assert.AreEqual(true, (bool)jsonData.nested.nestedPrimitive); + } + + [Test] + public void CanReadIntsAsFloatingPoints() + { + var json = new BinaryData("5").ToDynamic(); + dynamic jsonData = json; + + Assert.AreEqual(5, (float)jsonData); + Assert.AreEqual(5, (double)jsonData); + Assert.AreEqual(5, (int)jsonData); + Assert.AreEqual(5, (long)jsonData); + Assert.AreEqual(5, (float)json); + Assert.AreEqual(5, (double)json); + Assert.AreEqual(5, (int)json); + Assert.AreEqual(5, (long)json); + } + + [Test] + public void ReadingFloatingPointAsIntThrows() + { + var json = new BinaryData("5.5").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); + Assert.Throws(() => _ = (long)json); + Assert.Throws(() => _ = (long)jsonData); + } + + [Test] + public void FloatOverflowThrows() + { + var json = new BinaryData("34028234663852885981170418348451692544000").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (float)json); + Assert.Throws(() => _ = (float)jsonData); + Assert.AreEqual(34028234663852885981170418348451692544000d, (double)jsonData); + Assert.AreEqual(34028234663852885981170418348451692544000d, (double)json); + } + + [Test] + public void FloatUnderflowThrows() + { + var json = new BinaryData("-34028234663852885981170418348451692544000").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (float)json); + Assert.Throws(() => _ = (float)jsonData); + Assert.AreEqual(-34028234663852885981170418348451692544000d, (double)jsonData); + Assert.AreEqual(-34028234663852885981170418348451692544000d, (double)json); + } + + [Test] + public void IntOverflowThrows() + { + var json = new BinaryData("3402823466385288598").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); + Assert.AreEqual(3402823466385288598L, (long)jsonData); + Assert.AreEqual(3402823466385288598L, (long)json); + Assert.AreEqual(3402823466385288598D, (double)jsonData); + Assert.AreEqual(3402823466385288598D, (double)json); + Assert.AreEqual(3402823466385288598F, (float)jsonData); + Assert.AreEqual(3402823466385288598F, (float)json); + } + + [Test] + public void IntUnderflowThrows() + { + var json = new BinaryData("-3402823466385288598").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); + Assert.AreEqual(-3402823466385288598L, (long)jsonData); + Assert.AreEqual(-3402823466385288598L, (long)json); + Assert.AreEqual(-3402823466385288598D, (double)jsonData); + Assert.AreEqual(-3402823466385288598D, (double)json); + Assert.AreEqual(-3402823466385288598F, (float)jsonData); + Assert.AreEqual(-3402823466385288598F, (float)json); + } + + [Test] + public void ReadingArrayAsValueThrows() + { + var json = new BinaryData("[1,3]").ToDynamic(); + dynamic jsonData = json; + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); + } + + [Test] + public void RoundtripObjects() + { + var model = new SampleModel("Hello World", 5); + var roundtripped = (SampleModel)new BinaryData(model).ToDynamic(); + + Assert.AreEqual(model, roundtripped); + } + + [Test] + public void EqualsHandlesStringsSpecial() + { + dynamic json = new BinaryData("\"test\"").ToDynamic(); + + Assert.IsTrue(json.Equals("test")); + Assert.IsTrue(json.Equals(new BinaryData("\"test\"").ToDynamic())); + } + + [Test] + public void EqualsForObjectsAndArrays() + { + dynamic obj1 = new BinaryData(new { foo = "bar" }).ToDynamic(); + dynamic obj2 = new BinaryData(new { foo = "bar" }).ToDynamic(); + + dynamic arr1 = new BinaryData(new[] { "bar" }).ToDynamic(); + dynamic arr2 = new BinaryData(new[] { "bar" }).ToDynamic(); + + // For objects and arrays, Equals provides reference equality. + Assert.AreEqual(obj1, obj1); + Assert.AreEqual(arr1, arr1); + + Assert.AreNotEqual(obj1, obj2); + Assert.AreNotEqual(arr1, arr2); + } + + [Test] + public void OperatorEqualsForString() + { + dynamic fooJson = new BinaryData("\"foo\"").ToDynamic(); + dynamic barJson = new BinaryData("\"bar\"").ToDynamic(); + + Assert.IsTrue(fooJson == "foo"); + Assert.IsTrue("foo" == fooJson); + Assert.IsFalse(fooJson != "foo"); + Assert.IsFalse("foo" != fooJson); + + Assert.IsFalse(barJson == "foo"); + Assert.IsFalse("foo" == barJson); + Assert.IsTrue(barJson != "foo"); + Assert.IsTrue("foo" != barJson); + } + + private T JsonAsType(string json) + { + dynamic jsonData = new BinaryData(json).ToDynamic(); + return (T)jsonData; + } + +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + internal class SampleModel : IEquatable +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + { + public SampleModel() { } + + public string Message { get; set; } + public int Number { get; set; } + + public SampleModel(string message, int number) + { + Message = message; + Number = number; + } + + public override bool Equals(object obj) + { + SampleModel other = obj as SampleModel; + if (other == null) + { + return false; + } + + return Equals(other); + } + + public bool Equals(SampleModel obj) + { + return Message == obj.Message && Number == obj.Number; + } + } + } +} diff --git a/sdk/core/Azure.Core/Azure.Core.sln b/sdk/core/Azure.Core/Azure.Core.sln index 4f822ceaea99..0c89b7d14274 100644 --- a/sdk/core/Azure.Core/Azure.Core.sln +++ b/sdk/core/Azure.Core/Azure.Core.sln @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Expressions.Data EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Expressions.DataFactory.Tests", "..\Azure.Core.Expressions.DataFactory\tests\Azure.Core.Expressions.DataFactory.Tests.csproj", "{E1E88CD0-609C-4B8C-B723-DB004B90E235}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Tests.Public", "..\Azure.Core.Experimental\tests\public\Azure.Core.Experimental.Tests.Public.csproj", "{3A0B2AFE-21E3-4585-A348-EE0645F98414}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +127,10 @@ Global {E1E88CD0-609C-4B8C-B723-DB004B90E235}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1E88CD0-609C-4B8C-B723-DB004B90E235}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1E88CD0-609C-4B8C-B723-DB004B90E235}.Release|Any CPU.Build.0 = Release|Any CPU + {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9e3f2e2d641ce3c1fd9707a27b9b9365d18fa919 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 17 Nov 2022 10:33:45 -0800 Subject: [PATCH 02/21] make indexer access entirely dynamic; add tests with newly requested features --- .../Azure.Core.Experimental/src/JsonData.cs | 67 +++------ .../tests/JsonDataTests.cs | 2 +- .../tests/public/JsonDataPublicTests.cs | 131 ++++++++++++++++++ 3 files changed, 155 insertions(+), 45 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 55741a175d94..9eb8400d1223 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -436,32 +436,6 @@ public JsonData SetEmptyArray(string propertyName) return value; } - /// - /// Gets or sets a value at the given index in an array. - /// - /// The index in the array of the value to get or set. - /// The value at the given index. - /// - /// If the property is not this method throws . - /// - public JsonData this[int arrayIndex] - { - get => GetValueAt(arrayIndex); - } - - /// - /// Gets or sets a value for a given property in an object. - /// - /// The name of the property in the object to get or set. - /// The value for the given property name. - /// - /// If the property is not this method throws . - /// - public JsonData this[string propertyName] - { - get => GetPropertyValue(propertyName); - } - /// /// Converts the value to a /// @@ -870,14 +844,27 @@ private object GetDynamicProperty(string propertyName) throw new InvalidOperationException($"Property {propertyName} not found"); } + private JsonData GetViaIndexer(object index) + { + switch (index) + { + case string propertyName: + return GetPropertyValue(propertyName); + case int arrayIndex: + return GetValueAt(arrayIndex);; + } + + throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); + } + private JsonData SetViaIndexer(object index, object value) { switch (index) { - case string property: - return SetValue(property, value); - case int i: - return SetValueAt(i, value); + case string propertyName: + return SetValue(propertyName, value); + case int arrayIndex: + return SetValueAt(arrayIndex, value); } throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); @@ -1019,9 +1006,9 @@ private class MetaObject : DynamicMetaObject private static readonly MethodInfo SetValueMethod = typeof(JsonData).GetMethod(nameof(SetValue), BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly MethodInfo SetIndexerMethod = typeof(JsonData).GetMethod(nameof(SetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo GetViaIndexerMethod = typeof(JsonData).GetMethod(nameof(GetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly Dictionary Indexers = GetIndexers(); + private static readonly MethodInfo SetViaIndexerMethod = typeof(JsonData).GetMethod(nameof(SetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance); // Operators that cast from JsonData to another type private static readonly Dictionary CastFromOperators = GetCastFromOperators(); @@ -1044,11 +1031,11 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { var targetObject = Expression.Convert(Expression, LimitType); - var arguments = indexes.Select(i => i.Expression); - var indexProperty = Expression.Property(targetObject, Indexers[indexes[0].LimitType], arguments); + var arguments = new Expression[] { Expression.Convert(indexes[0].Expression, typeof(object)) }; + var getViaIndexerCall = Expression.Call(targetObject, GetViaIndexerMethod, arguments); var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); - return new DynamicMetaObject(indexProperty, restrictions); + return new DynamicMetaObject(getViaIndexerCall, restrictions); } public override DynamicMetaObject BindConvert(ConvertBinder binder) @@ -1092,20 +1079,12 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet Expression.Convert(indexes[0].Expression, typeof(object)), Expression.Convert(value.Expression, typeof(object)) }; - var setCall = Expression.Call(targetObject, SetIndexerMethod, arguments); + var setCall = Expression.Call(targetObject, SetViaIndexerMethod, arguments); var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); return new DynamicMetaObject(setCall, restrictions); } - private static Dictionary GetIndexers() - { - return typeof(JsonData) - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.GetIndexParameters().Any()) - .ToDictionary(p => p.GetIndexParameters().First().ParameterType); - } - private static Dictionary GetCastFromOperators() { return typeof(JsonData) diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs index d3cdb9441882..a23b0748e9f2 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs @@ -291,7 +291,7 @@ void validate(JsonData d) { Assert.AreEqual(JsonValueKind.Object, d.Kind); Assert.AreEqual(d.Properties.Count(), 1); - Assert.AreEqual(d["property"], "hello"); + Assert.AreEqual(d.Get("property"), "hello"); } validate(orig); diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 0bba687eb7f1..69772d63265f 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; +using System.Globalization; using NUnit.Framework; namespace Azure.Core.Tests.Public @@ -108,6 +110,57 @@ public void CanAccessProperties() Assert.AreEqual(true, (bool)jsonData.nested.nestedPrimitive); } + [Test] + public void CanTestPropertyForNull() + { + dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }").ToDynamic(); + + Assert.IsNull(jsonData.OptionalInt); + Assert.IsNull(jsonData.OptionalString); + Assert.AreEqual("Hello", (string)jsonData.Primitive); + } + + [Test] + public void CanAddStringToList() + { + dynamic jsonData = new BinaryData(new { value = "foo" }).ToDynamic(); + + List list = new(); + + // TODO: Should we add an implicit cast to string? + // Can we do it dynamically? + // Alternatively, we could provide a different type for string leaf + // nodes, like we do for numbers. + list.Add(jsonData.value); + + Assert.AreEqual(1, list.Count); + Assert.AreEqual("foo", list[0]); + } + + [Test] + public void CanAddIntToList() + { + dynamic jsonData = new BinaryData(new { value = 5 }).ToDynamic(); + + List list = new(); + + // TODO: Should we add an implicit cast to int? + // Can we do it dynamically? + list.Add(jsonData.value); + + Assert.AreEqual(1, list.Count); + Assert.AreEqual(5, list[0]); + } + + [Test] + public void GetMemberIsCaseInsensitive() + { + dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }").ToDynamic(); + + Assert.AreEqual("Hello", (string)jsonData.Primitive); + Assert.AreEqual(true, (bool)jsonData.Nested.NestedPrimitive); + } + [Test] public void CanReadIntsAsFloatingPoints() { @@ -146,6 +199,24 @@ public void FloatOverflowThrows() Assert.AreEqual(34028234663852885981170418348451692544000d, (double)json); } + [Test] + public void CanAccessArrayValues() + { + dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } , \"array\": [1, 2, 3] }").ToDynamic(); + + Assert.AreEqual(1, (int)jsonData.array[0]); + Assert.AreEqual(2, (int)jsonData.array[1]); + Assert.AreEqual(3, (int)jsonData.array[2]); + } + + [Test] + public void CanAccessJsonPropertiesWithDotnetIllegalCharacters() + { + dynamic jsonData = new BinaryData("{ \"$foo\":\"Hello\" }").ToDynamic(); + + Assert.AreEqual("Hello", (string)jsonData["$foo"]); + } + [Test] public void FloatUnderflowThrows() { @@ -205,6 +276,52 @@ public void RoundtripObjects() Assert.AreEqual(model, roundtripped); } + [Test] + public void CanCastToTypesYouDontOwn() + { + var now = DateTimeOffset.Now; + + // "O" is the only format supported by default JsonSerializer: + // https://learn.microsoft.com/dotnet/standard/datetime/system-text-json-support + dynamic nowJson = new BinaryData($"{{ \"value\": \"{now.ToString("O", CultureInfo.InvariantCulture)}\" }}").ToDynamic().value; + + var cast = (DateTimeOffset)nowJson; + + Assert.AreEqual(now, cast); + } + + [Test] + public void CanCastToIEnumerableOfT() + { + dynamic data = new BinaryData("{ \"array\": [ 1, 2, 3] }").ToDynamic(); + + var enumerable = (IEnumerable)data.array; + + int i = 0; + foreach (var item in enumerable) + { + Assert.AreEqual(++i, item); + } + } + + [Test] + public void CanGetDynamicFromBinaryData() + { + var data = new BinaryData(new + { + array = new[] { 1, 2, 3 } + }); + + dynamic json = data.ToDynamic(); + dynamic array = json.array; + + int i = 0; + foreach (int item in array) + { + Assert.AreEqual(++i, item); + } + } + [Test] public void EqualsHandlesStringsSpecial() { @@ -248,6 +365,20 @@ public void OperatorEqualsForString() Assert.IsTrue("foo" != barJson); } + [Test] + public void EqualsForStringNUnit() + { + dynamic foo = new BinaryData("{ \"value\": \"foo\" }").ToDynamic(); + var value = foo.Value; + + // TODO: Outstanding question regarding whether we want these to succeed without a cast. + Assert.AreEqual(value, "foo"); + Assert.AreEqual("foo", value); + + Assert.That(value, Is.EqualTo("foo")); + Assert.That("foo", Is.EqualTo(value)); + } + private T JsonAsType(string json) { dynamic jsonData = new BinaryData(json).ToDynamic(); From 32d19700d873fe1977b83985bf3389ef06ee89e2 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 17 Nov 2022 10:55:58 -0800 Subject: [PATCH 03/21] removed unused public APIs --- .../Azure.Core.Experimental/src/JsonData.cs | 340 ++---------------- .../tests/JsonDataDynamicMutableTests.cs | 4 +- .../tests/JsonDataTests.cs | 8 - 3 files changed, 25 insertions(+), 327 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 9eb8400d1223..24eea4a41c6a 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -142,23 +142,6 @@ private JsonData(IEnumerable> properties) } } - private JsonData(IEnumerable array) - { - _kind = JsonValueKind.Array; - _arrayRepresentation = new List(); - foreach (var item in array) - { - if (item == null) - { - _arrayRepresentation.Add(new JsonData((object?)null)); - } - else - { - _arrayRepresentation.Add(item); - } - } - } - private void InitFromElement(JsonElement element) { switch (element.ValueKind) @@ -195,38 +178,13 @@ private void InitFromElement(JsonElement element) } } - /// - /// Returns the value for a given property. - /// - /// The type of the object to return. - /// The name of the property to get. - /// The value for a given property - /// If the property is not this method throws . - public T Get(string propertyName) - { - return GetPropertyValue(propertyName).To(); - } - - /// - /// Gets the value of a property from an object. - /// - /// The type of the object to return. - /// The name of the property to get. - /// Options to control the conversion behavior. - /// The value for a given property. - /// If the property is not this method throws . - public T Get(string propertyName, JsonSerializerOptions options) - { - return GetPropertyValue(propertyName).To(options); - } - /// /// Gets the value of a property from an object, or null if no such property exists. /// /// The name of the property to get /// The value for a given property, or null if no such property exists. /// If the property is not this method throws . - public JsonData? Get(string propertyName) + internal JsonData? Get(string propertyName) { if (EnsureObject().TryGetValue(propertyName, out JsonData value)) { @@ -241,7 +199,7 @@ public T Get(string propertyName, JsonSerializerOptions options) /// /// The type to convert the value into. /// A new instance of constructed from the underlying JSON value. - public T To() => To(DefaultJsonSerializerOptions); + internal T To() => To(DefaultJsonSerializerOptions); /// /// Deserializes the given JSON value into an instance of a given type. @@ -249,7 +207,7 @@ public T Get(string propertyName, JsonSerializerOptions options) /// The type to deserialize the value into /// Options to control the conversion behavior. /// A new instance of constructed from the underlying JSON value. - public T To(JsonSerializerOptions options) + internal T To(JsonSerializerOptions options) { return JsonSerializer.Deserialize(ToJsonString(), options); } @@ -258,7 +216,7 @@ public T To(JsonSerializerOptions options) /// Returns a stringified version of the JSON for this value. /// /// Returns a stringified version of the JSON for this value. - public string ToJsonString() + internal string ToJsonString() { using var memoryStream = new MemoryStream(); using (var writer = new Utf8JsonWriter(memoryStream)) @@ -268,174 +226,6 @@ public string ToJsonString() return Encoding.UTF8.GetString(memoryStream.ToArray()); } - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, bool value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, double value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, float value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, int value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, long value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public void Set(string propertyName, string? value) => EnsureObject()[propertyName] = new JsonData(value); - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// A of the serialized object. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData Set(string propertyName, object? serializable) - { - JsonData value = new JsonData(serializable); - EnsureObject()[propertyName] = value; - return value; - } - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// Options to control the conversion behavior. - /// A of the serialized object. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData Set(string propertyName, object? serializable, JsonSerializerOptions options) - { - JsonData value = new JsonData(serializable, options); - EnsureObject()[propertyName] = value; - return value; - } - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// A of the serialized object. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData Set(string propertyName, T[] serializable) - { - JsonData value = new JsonData(serializable.Select(x => new JsonData(x))); - EnsureObject()[propertyName] = value; - return value; - } - - /// - /// Sets the property of an object to a given value. - /// - /// The property to set. - /// The value to set the property to. - /// Options to control the conversion behavior. - /// A of the serialized object. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData Set(string propertyName, T[] serializable, JsonSerializerOptions options) - { - JsonData value = new JsonData(serializable.Select(x => new JsonData(x, options))); - EnsureObject()[propertyName] = value; - return value; - } - - /// - /// Sets the property of an object to a new empty object. - /// - /// The property to set. - /// A for the newly created empty object. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData SetEmptyObject(string propertyName) - { - JsonData value = EmptyObject(); - EnsureObject()[propertyName] = value; - return value; - } - - /// - /// Sets the property of an object to a new empty array. - /// - /// The property to set. - /// A for the newly created empty array. - /// - /// If the property already exists, the value is overwritten. - /// If the property is not this method throws . - /// - public JsonData SetEmptyArray(string propertyName) - { - JsonData value = EmptyArray(); - EnsureObject()[propertyName] = value; - return value; - } - /// /// Converts the value to a /// @@ -564,61 +354,8 @@ public JsonData SetEmptyArray(string propertyName) /// False if the given JsonData represents the given string, and false otherwise public static bool operator !=(string? left, JsonData? right) => !(left == right); - /// - /// Parses text representing a single JSON value into a . - /// - /// A string representing a JSON value. - /// A representation of the value. - public static JsonData FromString(string json) => new JsonData(JsonDocument.Parse(json)); - - /// - /// Parses text representing a single JSON value into a . - /// - /// A UTF8 encoded string representing a JSON value. - /// A representation of the value. - public static JsonData FromBytes(byte[] utf8Json) => new JsonData(JsonDocument.Parse(utf8Json)); - - /// - /// Parses text representing a single JSON value into a . - /// - /// A UTF8 encoded string representing a JSON value. - /// A representation of the value. - public static JsonData FromBytes(ReadOnlyMemory utf8Json) => new JsonData(JsonDocument.Parse(utf8Json)); - - /// - /// Parses text representing a single JSON value into a . - /// - /// A UTF8 encoded string representing a JSON value. - /// A representation of the value - public static JsonData FromStream(Stream utf8Json) => new JsonData(JsonDocument.Parse(utf8Json)); - - /// - /// Parses text representing a single JSON value into a . - /// - /// A UTF8 encoded string representing a JSON value. - /// A token to monitor for cancelation requests. - /// A Task which will construct the representation of the value - public static async Task FromStreamAsync(Stream utf8JsonStream, CancellationToken cancellationToken = default) => new JsonData((await JsonDocument.ParseAsync(utf8JsonStream, cancellationToken: cancellationToken).ConfigureAwait(false)).RootElement); - - /// - /// Constructs a from an object. The value corresponds to the serialized representation of the object. - /// - /// The object to construct the from. - /// Options to control the conversion behavior. - /// A representation of the value. - public static JsonData FromObject(T value, JsonSerializerOptions? options = null) => new JsonData(value, options ?? DefaultJsonSerializerOptions); - - /// - /// Returns a new that represents an empty object. - /// - /// A that represents an empty object. - public static JsonData EmptyObject() => new JsonData(); - - /// - /// Returns a new that represents an empty array. - /// - /// A that represents an empty array. - public static JsonData EmptyArray() => new JsonData(System.Array.Empty()); + // Used in internal tests. + internal static JsonData FromString(string json) => new JsonData(JsonDocument.Parse(json)); /// /// The of the value of this instance. @@ -641,7 +378,7 @@ public int Length /// Returns the names of all the properties of this object. /// /// If is not this methods throws . - public IEnumerable Properties + internal IEnumerable Properties { get => EnsureObject().Keys; } @@ -650,38 +387,11 @@ public IEnumerable Properties /// Returns all the elements in this array. /// /// If is not this methods throws . - public IEnumerable Items + internal IEnumerable Items { get => EnsureArray(); } - /// - /// Writes the UTF-8 encoded string representation of this instance. - /// - /// The stream to write to. - /// The number of bytes written into the stream. - public long WriteTo(Stream stream) - { - using Utf8JsonWriter writer = new Utf8JsonWriter(stream); - WriteTo(writer); - writer.Flush(); - return writer.BytesCommitted; - } - - /// - /// Writes the UTF-8 encoded string representation of this instance. - /// - /// The stream to write to. - /// A token to monitor for cancelation requests. - /// The number of bytes written into the stream. - public async Task WriteToAsync(Stream stream, CancellationToken cancellationToken) - { - using Utf8JsonWriter writer = new Utf8JsonWriter(stream); - WriteTo(writer); - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - return writer.BytesCommitted; - } - /// public override string ToString() { @@ -836,12 +546,7 @@ private object GetDynamicProperty(string propertyName) return Length; } - if (EnsureObject().TryGetValue(propertyName, out JsonData element)) - { - return element; - } - - throw new InvalidOperationException($"Property {propertyName} not found"); + return GetPropertyValue(propertyName); } private JsonData GetViaIndexer(object index) @@ -857,19 +562,6 @@ private JsonData GetViaIndexer(object index) throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); } - private JsonData SetViaIndexer(object index, object value) - { - switch (index) - { - case string propertyName: - return SetValue(propertyName, value); - case int arrayIndex: - return SetValueAt(arrayIndex, value); - } - - throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); - } - private JsonData SetValue(string propertyName, object value) { if (!(value is JsonData json)) @@ -892,6 +584,19 @@ private List EnsureArray() return _arrayRepresentation!; } + private JsonData SetViaIndexer(object index, object value) + { + switch (index) + { + case string propertyName: + return SetValue(propertyName, value); + case int arrayIndex: + return SetValueAt(arrayIndex, value); + } + + throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); + } + private JsonData GetValueAt(int index) { return EnsureArray()[index]; @@ -917,6 +622,7 @@ private JsonData SetValueAt(int index, object value) return _value; } + private Number EnsureNumberValue() { if (_kind != JsonValueKind.Number) diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs index 9f7ef897834f..c7741809a5dd 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs @@ -65,7 +65,7 @@ public void NewObjectPropertiesCanBeAssignedWithObject() { var json = JsonData.FromString("{}"); dynamic jsonData = json; - jsonData.a = JsonData.EmptyObject(); + jsonData.a = JsonData.FromString("{}"); jsonData.a.b = 2; Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); @@ -76,7 +76,7 @@ public void NewObjectPropertiesCanBeAssignedWithObjectIndirectly() { var json = JsonData.FromString("{}"); dynamic jsonData = json; - dynamic anotherJson = JsonData.EmptyObject(); + dynamic anotherJson = JsonData.FromString("{}"); jsonData.a = anotherJson; anotherJson.b = 2; diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs index a23b0748e9f2..ac9f4a6d7426 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs @@ -301,14 +301,6 @@ void validate(JsonData d) validate(roundTrip); } - [Test] - public void GetOfTWithStringWorks() - { - JsonData d = new JsonData(); - d.Set("property", "Hello"); - Assert.AreEqual("Hello", d.Get("property")); - } - private T JsonAsType(string json) { dynamic jsonData = JsonData.FromString(json); From 0eff1da8b03c2a865a139f056859b95b32d4f694 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 17 Nov 2022 11:00:22 -0800 Subject: [PATCH 04/21] return null for optional properties --- sdk/core/Azure.Core.Experimental/src/JsonData.cs | 8 ++++---- .../tests/public/JsonDataPublicTests.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 24eea4a41c6a..96680a3a08e0 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -522,14 +522,14 @@ private Dictionary EnsureObject() return _objectRepresentation!; } - private JsonData GetPropertyValue(string propertyName) + private JsonData? GetPropertyValue(string propertyName) { if (EnsureObject().TryGetValue(propertyName, out JsonData element)) { return element; } - throw new InvalidOperationException($"Property {propertyName} not found"); + return null; } /// @@ -539,7 +539,7 @@ private JsonData GetPropertyValue(string propertyName) /// /// The name of the property to get the value of. /// - private object GetDynamicProperty(string propertyName) + private object? GetDynamicProperty(string propertyName) { if (_kind == JsonValueKind.Array && propertyName == nameof(Length)) { @@ -549,7 +549,7 @@ private object GetDynamicProperty(string propertyName) return GetPropertyValue(propertyName); } - private JsonData GetViaIndexer(object index) + private JsonData? GetViaIndexer(object index) { switch (index) { diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 69772d63265f..66dff2359b1e 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -117,7 +117,7 @@ public void CanTestPropertyForNull() Assert.IsNull(jsonData.OptionalInt); Assert.IsNull(jsonData.OptionalString); - Assert.AreEqual("Hello", (string)jsonData.Primitive); + Assert.AreEqual("Hello", (string)jsonData.primitive); } [Test] From 43cd339b34fbe59ac85d66829cb26f3f1665b1e0 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 17 Nov 2022 13:05:20 -0800 Subject: [PATCH 05/21] dispose JsonDocument --- .../Azure.Core.Experimental/src/JsonData.cs | 31 +++++----------- .../tests/JsonDataDynamicMutableTests.cs | 24 ++++++------- .../tests/JsonDataTests.cs | 36 ++++++++----------- 3 files changed, 34 insertions(+), 57 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 96680a3a08e0..cc06be3e55e6 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -45,10 +45,14 @@ internal static JsonData Parse(BinaryData utf8Json) } /// - /// Creates a new JsonData object which represents an JSON object with no properties. + /// Parses test representing a single JSON value into a . /// - public JsonData() : this(Array.Empty>()) + /// The JSON string. + /// A representation of the value. + internal static JsonData Parse(string json) { + using var doc = JsonDocument.Parse(json); + return new JsonData(doc); } /// @@ -125,23 +129,6 @@ private JsonData(JsonElement element) InitFromElement(element); } - private JsonData(IEnumerable> properties) - { - _kind = JsonValueKind.Object; - _objectRepresentation = new Dictionary(); - foreach (var property in properties) - { - if (property.Value == null) - { - _objectRepresentation[property.Key] = new JsonData((object?)null); - } - else - { - _objectRepresentation[property.Key] = property.Value; - } - } - } - private void InitFromElement(JsonElement element) { switch (element.ValueKind) @@ -354,9 +341,6 @@ internal string ToJsonString() /// False if the given JsonData represents the given string, and false otherwise public static bool operator !=(string? left, JsonData? right) => !(left == right); - // Used in internal tests. - internal static JsonData FromString(string json) => new JsonData(JsonDocument.Parse(json)); - /// /// The of the value of this instance. /// @@ -866,7 +850,8 @@ private class JsonConverter : JsonConverter { public override JsonData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new JsonData(JsonDocument.ParseValue(ref reader)); + using var document = JsonDocument.ParseValue(ref reader); + return new JsonData(document); } public override void Write(Utf8JsonWriter writer, JsonData value, JsonSerializerOptions options) diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs index c7741809a5dd..503210d4566f 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs @@ -13,7 +13,7 @@ public class JsonDataDynamicMutableTests [Test] public void ArrayItemsCanBeAssigned() { - var json = JsonData.FromString("[0, 1, 2, 3]"); + var json = JsonData.Parse("[0, 1, 2, 3]"); dynamic jsonData = json; jsonData[1] = 2; jsonData[2] = null; @@ -25,7 +25,7 @@ public void ArrayItemsCanBeAssigned() [Test] public void ExistingObjectPropertiesCanBeAssigned() { - var json = JsonData.FromString("{\"a\":1}"); + var json = JsonData.Parse("{\"a\":1}"); dynamic jsonData = json; jsonData.a = "2"; @@ -35,7 +35,7 @@ public void ExistingObjectPropertiesCanBeAssigned() [TestCaseSource(nameof(PrimitiveValues))] public void NewObjectPropertiesCanBeAssignedWithPrimitive(T value, string expected) { - var json = JsonData.FromString("{}"); + var json = JsonData.Parse("{}"); dynamic jsonData = json; jsonData.a = value; @@ -45,7 +45,7 @@ public void NewObjectPropertiesCanBeAssignedWithPrimitive(T value, string exp [TestCaseSource(nameof(PrimitiveValues))] public void PrimitiveValuesCanBeParsedDirectly(T value, string expected) { - dynamic json = JsonData.FromString(expected); + dynamic json = JsonData.Parse(expected); Assert.AreEqual(value, (T)json); } @@ -53,7 +53,7 @@ public void PrimitiveValuesCanBeParsedDirectly(T value, string expected) [Test] public void NewObjectPropertiesCanBeAssignedWithArrays() { - var json = JsonData.FromString("{}"); + var json = JsonData.Parse("{}"); dynamic jsonData = json; jsonData.a = new JsonData(new object[] { 1, 2, null, "string" }); @@ -63,9 +63,9 @@ public void NewObjectPropertiesCanBeAssignedWithArrays() [Test] public void NewObjectPropertiesCanBeAssignedWithObject() { - var json = JsonData.FromString("{}"); + var json = JsonData.Parse("{}"); dynamic jsonData = json; - jsonData.a = JsonData.FromString("{}"); + jsonData.a = JsonData.Parse("{}"); jsonData.a.b = 2; Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); @@ -74,9 +74,9 @@ public void NewObjectPropertiesCanBeAssignedWithObject() [Test] public void NewObjectPropertiesCanBeAssignedWithObjectIndirectly() { - var json = JsonData.FromString("{}"); + var json = JsonData.Parse("{}"); dynamic jsonData = json; - dynamic anotherJson = JsonData.FromString("{}"); + dynamic anotherJson = JsonData.Parse("{}"); jsonData.a = anotherJson; anotherJson.b = 2; @@ -86,7 +86,7 @@ public void NewObjectPropertiesCanBeAssignedWithObjectIndirectly() [Test] public void NewObjectPropertiesCanBeAssignedWithSerializedObject() { - var json = JsonData.FromString("{}"); + var json = JsonData.Parse("{}"); dynamic jsonData = json; jsonData.a = new JsonData(new GeoPoint(1, 2)); @@ -96,14 +96,14 @@ public void NewObjectPropertiesCanBeAssignedWithSerializedObject() [TestCaseSource(nameof(PrimitiveValues))] public void CanModifyNestedProperties(T value, string expected) { - var json = JsonData.FromString("{\"a\":{\"b\":2}}"); + var json = JsonData.Parse("{\"a\":{\"b\":2}}"); dynamic jsonData = json; jsonData.a.b = value; Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":" + expected + "}}"); Assert.AreEqual(value, (T)jsonData.a.b); - dynamic reparsedJson = JsonData.FromString(json.ToString()); + dynamic reparsedJson = JsonData.Parse(json.ToString()); Assert.AreEqual(value, (T)reparsedJson.a.b); } diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs index ac9f4a6d7426..3cf55b28bf41 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs @@ -12,18 +12,10 @@ namespace Azure.Core.Tests { public class JsonDataTests { - [Test] - public void DefaultConstructorMakesEmptyObject() - { - var jsonData = new JsonData(); - - Assert.AreEqual(0, jsonData.Properties.Count()); - } - [Test] public void CanCreateFromJson() { - var jsonData = JsonData.FromString("\"string\""); + var jsonData = JsonData.Parse("\"string\""); Assert.AreEqual("\"string\"", jsonData.ToJsonString()); } @@ -62,7 +54,7 @@ public void CanCreateFromNull() [Test] public void DynamicCanConvertToIEnumerableDynamic() { - dynamic jsonData = JsonData.FromString("[1, null, \"s\"]"); + dynamic jsonData = JsonData.Parse("[1, null, \"s\"]"); int i = 0; foreach (var dynamicItem in jsonData) { @@ -90,7 +82,7 @@ public void DynamicCanConvertToIEnumerableDynamic() [Test] public void DynamicCanConvertToIEnumerableInt() { - dynamic jsonData = JsonData.FromString("[0, 1, 2, 3]"); + dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); int i = 0; foreach (int dynamicItem in jsonData) { @@ -104,14 +96,14 @@ public void DynamicCanConvertToIEnumerableInt() [Test] public void DynamicArrayHasLength() { - dynamic jsonData = JsonData.FromString("[0, 1, 2, 3]"); + dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); Assert.AreEqual(4, jsonData.Length); } [Test] public void DynamicArrayFor() { - dynamic jsonData = JsonData.FromString("[0, 1, 2, 3]"); + dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); for (int i = 0; i < jsonData.Length; i++) { Assert.AreEqual(i, (int)jsonData[i]); @@ -121,7 +113,7 @@ public void DynamicArrayFor() [Test] public void CanAccessProperties() { - dynamic jsonData = JsonData.FromString("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }"); + dynamic jsonData = JsonData.Parse("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }"); Assert.AreEqual("Hello", (string)jsonData.primitive); Assert.AreEqual(true, (bool)jsonData.nested.nestedPrimitive); @@ -130,7 +122,7 @@ public void CanAccessProperties() [Test] public void CanReadIntsAsFloatingPoints() { - var json = JsonData.FromString("5"); + var json = JsonData.Parse("5"); dynamic jsonData = json; Assert.AreEqual(5, (float)jsonData); @@ -146,7 +138,7 @@ public void CanReadIntsAsFloatingPoints() [Test] public void ReadingFloatingPointAsIntThrows() { - var json = JsonData.FromString("5.5"); + var json = JsonData.Parse("5.5"); dynamic jsonData = json; Assert.Throws(() => _ = (int)json); Assert.Throws(() => _ = (int)jsonData); @@ -157,7 +149,7 @@ public void ReadingFloatingPointAsIntThrows() [Test] public void FloatOverflowThrows() { - var json = JsonData.FromString("34028234663852885981170418348451692544000"); + var json = JsonData.Parse("34028234663852885981170418348451692544000"); dynamic jsonData = json; Assert.Throws(() => _ = (float)json); Assert.Throws(() => _ = (float)jsonData); @@ -168,7 +160,7 @@ public void FloatOverflowThrows() [Test] public void FloatUnderflowThrows() { - var json = JsonData.FromString("-34028234663852885981170418348451692544000"); + var json = JsonData.Parse("-34028234663852885981170418348451692544000"); dynamic jsonData = json; Assert.Throws(() => _ = (float)json); Assert.Throws(() => _ = (float)jsonData); @@ -179,7 +171,7 @@ public void FloatUnderflowThrows() [Test] public void IntOverflowThrows() { - var json = JsonData.FromString("3402823466385288598"); + var json = JsonData.Parse("3402823466385288598"); dynamic jsonData = json; Assert.Throws(() => _ = (int)json); Assert.Throws(() => _ = (int)jsonData); @@ -194,7 +186,7 @@ public void IntOverflowThrows() [Test] public void IntUnderflowThrows() { - var json = JsonData.FromString("-3402823466385288598"); + var json = JsonData.Parse("-3402823466385288598"); dynamic jsonData = json; Assert.Throws(() => _ = (int)json); Assert.Throws(() => _ = (int)jsonData); @@ -209,7 +201,7 @@ public void IntUnderflowThrows() [Test] public void ReadingArrayAsValueThrows() { - var json = JsonData.FromString("[1,3]"); + var json = JsonData.Parse("[1,3]"); dynamic jsonData = json; Assert.Throws(() => _ = (int)json); Assert.Throws(() => _ = (int)jsonData); @@ -303,7 +295,7 @@ void validate(JsonData d) private T JsonAsType(string json) { - dynamic jsonData = JsonData.FromString(json); + dynamic jsonData = JsonData.Parse(json); return (T) jsonData; } From d3947640588751d0e428f2b30e683e18282e69c3 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 18 Nov 2022 14:09:03 -0800 Subject: [PATCH 06/21] nit rewrite for clarity --- sdk/core/Azure.Core.Experimental/src/JsonData.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index cc06be3e55e6..9398ce142fd8 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -116,7 +116,9 @@ public JsonData(object? value, JsonSerializerOptions options, Type? type = null) InitFromElement(doc.RootElement); break; default: - JsonElement e = JsonDocument.Parse(JsonSerializer.Serialize(value, type ?? (value == null ? typeof(object) : value.GetType()), options)).RootElement; + Type inputType = type ?? (value == null ? typeof(object) : value.GetType()); + string json = JsonSerializer.Serialize(value, inputType, options); + JsonElement e = JsonDocument.Parse(json).RootElement; _kind = e.ValueKind; InitFromElement(e); break; From d196588efd7ad5e2a435beaacbe687f97e4420c8 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 18 Nov 2022 15:58:51 -0800 Subject: [PATCH 07/21] add tests of requirements for 'leaf node' dynamic interface --- .../Azure.Core.Experimental/src/JsonData.cs | 7 +- .../tests/public/JsonDataArrayTests.cs | 15 + .../tests/public/JsonDataLeafTests.cs | 278 ++++++++++++++++++ .../tests/public/JsonDataPublicTests.cs | 54 +--- .../tests/public/JsonDataTestHelpers.cs | 59 ++++ 5 files changed, 366 insertions(+), 47 deletions(-) create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 9398ce142fd8..46fa5a38dd46 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -532,7 +532,12 @@ private Dictionary EnsureObject() return Length; } - return GetPropertyValue(propertyName); + if (_kind == JsonValueKind.Object) + { + return GetPropertyValue(propertyName); + } + + throw new InvalidOperationException($"Cannot get property on JSON element with kind {_kind}."); } private JsonData? GetViaIndexer(object index) diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs new file mode 100644 index 000000000000..58d2c203cda9 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Azure.Core.Experimental.Tests.Public +{ + internal class JsonDataArrayTests + { + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs new file mode 100644 index 000000000000..153b0941ddc3 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public +{ + public class JsonDataLeafTests + { + [Test] + public void CanConvertIntLeafToInt() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.AreEqual(5, (int)data); + } + + [Test] + public void CanConvertIntLeafToDouble() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.AreEqual(5d, (double)data); + } + + [Test] + public void CanConvertIntLeafToLong() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.AreEqual((long)5, (long)data); + } + + [Test] + public void CannotConvertIntLeafToString() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var s = (string)data; } + ); + } + + [Test] + public void CannotConvertIntLeafToBoolean() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var b = (bool)data; } + ); + } + + [Test] + public void CannotConvertIntLeafToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + + // TODO: Throws JsonException - is that preferred over + // InvalidOperationException? + + Assert.Throws( + () => { var model = (SampleModel)data; } + ); + } + + [Test] + public void CannotGetMemberOnLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var x = data.Property; } + ); + } + + [Test] + public void CannotSetMemberOnLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { data.Property = "invalid"; } + ); + } + + [Test] + public void CannotGetIndexPropertyOnLeaf() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var x = data["Property"]; } + ); + } + + [Test] + public void CannotSetIndexPropertyOnLeaf() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { data["Property"] = "invalid"; } + ); + } + + [Test] + public void CannotGetArrayIndexOnLeaf() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var x = data[0]; } + ); + } + + [Test] + public void CannotSetArrayIndexOnLeaf() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { data[0] = "invalid"; } + ); + } + + [Test] + public void CannotEnumerateLeaf() + { + // TODO: This exception says "exepcted kind to be object" - improve this. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { foreach (var item in data) { } } + ); + } + + [Test] + public void CanConvertIntLeafPropertyToInt() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5, (int)data.value); + } + + [Test] + public void CanConvertIntLeafPropertyToDouble() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5d, (double)data.value); + } + + [Test] + public void CanConvertIntLeafPropertyToLong() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual((long)5, (long)data.value); + } + + [Test] + public void CannotConvertIntLeafPropertyToString() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var s = (string)data.value; } + ); + } + + [Test] + public void CannotConvertIntLeafPropertyToBoolean() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var b = (bool)data.value; } + ); + } + + [Test] + public void CannotConvertIntLeafPropertyToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + + // TODO: Throws JsonException - is that preferred over + // InvalidOperationException? + + Assert.Throws( + () => { var model = (SampleModel)data.value; } + ); + } + + [Test] + public void CannotGetMemberOnLeafProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var x = data.value.Property; } + ); + } + + [Test] + public void CannotSetMemberOnLeafProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { data.value.Property = "invalid"; } + ); + } + + [Test] + public void CannotGetIndexPropertyOnLeafProperty() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var x = data.value["Property"]; } + ); + } + + [Test] + public void CannotSetIndexPropertyOnLeafProperty() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { data.value["Property"] = "invalid"; } + ); + } + + [Test] + public void CannotGetArrayIndexOnLeafProperty() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var x = data.value[0]; } + ); + } + + [Test] + public void CannotSetArrayIndexOnLeafProperty() + { + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { data.value[0] = "invalid"; } + ); + } + + [Test] + public void CannotEnumerateLeafProperty() + { + // TODO: This exception says "exepcted kind to be object" - improve this. + + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { foreach (var item in data.value) { } } + ); + } + + [Test] + public void CanSetLeafProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + data.value = 6; + Assert.AreEqual(6, (int)data.value); + } + + [Test] + public void CanSetLeafPropertyToDifferentType() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + data.value = "valid"; + Assert.AreEqual("valid", (string)data.value); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 66dff2359b1e..44a19fd53eea 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -19,28 +19,28 @@ public void CanCreateFromJson() } [Test] - public void DynamicCanConvertToString() => Assert.AreEqual("string", JsonAsType("\"string\"")); + public void DynamicCanConvertToString() => Assert.AreEqual("string", JsonDataTestHelpers.JsonAsType("\"string\"")); [Test] - public void DynamicCanConvertToInt() => Assert.AreEqual(5, JsonAsType("5")); + public void DynamicCanConvertToInt() => Assert.AreEqual(5, JsonDataTestHelpers.JsonAsType("5")); [Test] - public void DynamicCanConvertToLong() => Assert.AreEqual(5L, JsonAsType("5")); + public void DynamicCanConvertToLong() => Assert.AreEqual(5L, JsonDataTestHelpers.JsonAsType("5")); [Test] - public void DynamicCanConvertToBool() => Assert.AreEqual(true, JsonAsType("true")); + public void DynamicCanConvertToBool() => Assert.AreEqual(true, JsonDataTestHelpers.JsonAsType("true")); [Test] - public void DynamicCanConvertToNullAsString() => Assert.AreEqual(null, JsonAsType("null")); + public void DynamicCanConvertToNullAsString() => Assert.AreEqual(null, JsonDataTestHelpers.JsonAsType("null")); [Test] - public void DynamicCanConvertToNullAsNullableInt() => Assert.AreEqual(null, JsonAsType("null")); + public void DynamicCanConvertToNullAsNullableInt() => Assert.AreEqual(null, JsonDataTestHelpers.JsonAsType("null")); [Test] - public void DynamicCanConvertToNullAsNullableLong() => Assert.AreEqual(null, JsonAsType("null")); + public void DynamicCanConvertToNullAsNullableLong() => Assert.AreEqual(null, JsonDataTestHelpers.JsonAsType("null")); [Test] - public void DynamicCanConvertToNullAsNullableBool() => Assert.AreEqual(null, JsonAsType("null")); + public void DynamicCanConvertToNullAsNullableBool() => Assert.AreEqual(null, JsonDataTestHelpers.JsonAsType("null")); [Test] public void DynamicCanConvertToIEnumerableDynamic() @@ -378,43 +378,5 @@ public void EqualsForStringNUnit() Assert.That(value, Is.EqualTo("foo")); Assert.That("foo", Is.EqualTo(value)); } - - private T JsonAsType(string json) - { - dynamic jsonData = new BinaryData(json).ToDynamic(); - return (T)jsonData; - } - -#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() - internal class SampleModel : IEquatable -#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() - { - public SampleModel() { } - - public string Message { get; set; } - public int Number { get; set; } - - public SampleModel(string message, int number) - { - Message = message; - Number = number; - } - - public override bool Equals(object obj) - { - SampleModel other = obj as SampleModel; - if (other == null) - { - return false; - } - - return Equals(other); - } - - public bool Equals(SampleModel obj) - { - return Message == obj.Message && Number == obj.Number; - } - } } } diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs new file mode 100644 index 000000000000..052e60784b7f --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Azure.Core.Tests.Public +{ + public class JsonDataTestHelpers + { + public static dynamic CreateFromJson(string json) + { + return BinaryData.FromString(json).ToDynamic(); + } + + public static T JsonAsType(string json) + { + dynamic jsonData = new BinaryData(json).ToDynamic(); + return (T)jsonData; + } + } + +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() +#pragma warning disable SA1402 // File may only contain a single type + public class SampleModel : IEquatable +#pragma warning restore SA1402 // File may only contain a single type +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + { + public SampleModel() { } + + public string Message { get; set; } + public int Number { get; set; } + + public SampleModel(string message, int number) + { + Message = message; + Number = number; + } + + public override bool Equals(object obj) + { + SampleModel other = obj as SampleModel; + if (other == null) + { + return false; + } + + return Equals(other); + } + + public bool Equals(SampleModel obj) + { + return Message == obj.Message && Number == obj.Number; + } + } +} From be4cc72d0463b9e69fed50cab868df5d1fd29985 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 18 Nov 2022 16:45:15 -0800 Subject: [PATCH 08/21] add tests of requirements for array dyanmic interface --- .../tests/public/JsonDataArrayTests.cs | 252 +++++++++++++++++- 1 file changed, 250 insertions(+), 2 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs index 58d2c203cda9..8423e9aa7aa8 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs @@ -2,14 +2,262 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; +using NUnit.Framework; -namespace Azure.Core.Experimental.Tests.Public +namespace Azure.Core.Tests.Public { - internal class JsonDataArrayTests + public class JsonDataArrayTests { + [Test] + public void CanConvertArrayToIEnumerable() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + // TODO: This is interesting - what is commented out below fails, + // because apparently the type system has decided the IEnumerable + // is holding JsonData, not dynamic, so the DMO callback is never called + // and the explicit cast to int is not visible b/c JsonData is internal. + //IEnumerable value = (IEnumerable)data; + + //int i = 1; + //foreach (int item in value) + //{ + // Assert.AreEqual(i++, (int)item); + //} + + int i = 1; + foreach (int item in data) + { + Assert.AreEqual(i++, item); + } + } + + [Test] + public void CannotConvertArrayToLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + // TODO: Standardize Exception types. + Assert.Throws(() => { var model = (int)data; }); + Assert.Throws(() => { var model = (bool)data; }); + Assert.Throws(() => { var model = (string)data; }); + Assert.Throws(() => { var model = (DateTime)data; }); + } + + [Test] + public void CannotConvertArrayToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { var model = (SampleModel)data; }); + } + + [Test] + public void CannotGetMemberOnArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { var x = data.Property; }); + } + + [Test] + public void CannotSetMemberOnArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { data.Property = "invalid"; }); + } + + [Test] + public void CannotGetIndexPropertyOnArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { var x = data["Property"]; }); + } + + [Test] + public void CannotSetIndexPropertyOnArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { data["Property"] = "invalid"; }); + } + + [Test] + public void CanGetArrayIndex() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + Assert.AreEqual(1, (int)data[0]); + Assert.AreEqual(2, (int)data[1]); + Assert.AreEqual(3, (int)data[2]); + } + + [Test] + public void CanGetArrayLength() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.AreEqual(3, data.Length); + } + + [Test] + public void CannotSetArrayLength() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { data.Length = 5; }); + } + + [Test] + public void CanSetArrayIndex() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + data[0] = 5; + data[1] = "valid"; + data[2] = null; + + Assert.AreEqual(5, (int)data[0]); + Assert.AreEqual("valid", (string)data[1]); + + // TODO: to check for null, we have to cast to string. Is that + // what we want? + Assert.AreEqual(null, (string)data[2]); + } + + [Test] + public void CanEnumerateArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + int i = 1; + foreach (int item in data) + { + Assert.AreEqual(i++, item); + } + } + + [Test] + public void CannotConvertArrayPropertyToLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + // TODO: Standardize Exception types. + Assert.Throws(() => { var model = (int)data.value; }); + Assert.Throws(() => { var model = (bool)data.value; }); + Assert.Throws(() => { var model = (string)data.value; }); + Assert.Throws(() => { var model = (DateTime)data.value; }); + } + + [Test] + public void CannotConvertArrayPropertyToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var model = (SampleModel)data.value; }); + } + + [Test] + public void CannotGetMemberOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var x = data.value.Property; }); + } + + [Test] + public void CannotSetMemberOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { data.value.Property = "invalid"; }); + } + + [Test] + public void CannotGetIndexPropertyOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var x = data.value["Property"]; }); + } + + [Test] + public void CannotSetIndexPropertyOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { data.value["Property"] = "invalid"; }); + } + + [Test] + public void CanGetArrayPropertyIndex() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + Assert.AreEqual(1, (int)data.value[0]); + Assert.AreEqual(2, (int)data.value[1]); + Assert.AreEqual(3, (int)data.value[2]); + } + + [Test] + public void CanGetArrayPropertyLength() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.AreEqual(3, data.value.Length); + } + + [Test] + public void CannotSetArrayPropertyLength() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { data.value.Length = 5; }); + } + + [Test] + public void CanSetArrayPropertyIndex() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + data.value[0] = 5; + data.value[1] = "valid"; + data.value[2] = null; + + Assert.AreEqual(5, (int)data.value[0]); + Assert.AreEqual("valid", (string)data.value[1]); + + // TODO: to check for null, we have to cast to string. Is that + // what we want? + Assert.AreEqual(null, (string)data.value[2]); + } + + [Test] + public void CanEnumerateArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + int i = 1; + foreach (int item in data.value) + { + Assert.AreEqual(i++, item); + } + } + + [Test] + public void CanGetObjectMemberNestedInArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" + ); + + Assert.AreEqual("tagValue", (string)data.value[0].tag); + } + + [Test] + public void CanSetObjectMemberNestedInArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" + ); + + data.value[0].tag = "newValue"; + + Assert.AreEqual("newValue", (string)data.value[0].tag); + } } } From 4fe4d95df421c020ee6daa820bf8de702b40aa99 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 18 Nov 2022 17:33:17 -0800 Subject: [PATCH 09/21] adding tests on object dynamic interface --- .../tests/public/JsonDataObjectTests.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs new file mode 100644 index 000000000000..e34cc0a86728 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public +{ + public class JsonDataObjectTests + { + [Test] + public void CannotConvertObjectToLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + + // TODO: Standardize Exception types. + Assert.Throws(() => { var i = (int)data; }); + Assert.Throws(() => { var b = (bool)data; }); + Assert.Throws(() => { var s = (string)data; }); + Assert.Throws(() => { var time = (DateTime)data; }); + } + + [Test] + public void CanConvertObjectToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""Message"": ""Hi"", + ""Number"" : 5 }"); + + Assert.AreEqual(new SampleModel("Hi", 5), (SampleModel)data); + } + + [Test] + public void CanConvertObjectToModelWithExtraProperties() + { + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""Message"": ""Hi"", + ""Number"" : 5, + ""Invalid"" : ""Not on SampleModel"" }"); + + SampleModel model = data; + + Assert.AreEqual("Hi", model.Message); + Assert.AreEqual(5, model.Number); + } + + [Test] + public void GoofingOffWithSerializerOptions() + { + //string json = @"{ ""Message"": ""Hi"", + // ""Number"" : 5, + // ""Invalid"" : ""Not on SampleModel"" }"; + + // "invalid"" is silently ignored + // https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-7-0#missingmemberhandling + // called "missing on the target type" + string json = @"{ ""message"": ""Hi"", + ""invalid"" : ""Not on SampleModel"" }"; + + //dynamic data = JsonDataTestHelpers.CreateFromJson(json); + + var model = JsonSerializer.Deserialize(json, + new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); + + var d = JsonSerializer.Deserialize>(json); + //var i = JsonSerializer.Deserialize(json); + + //SampleModel model = data; + + Assert.AreEqual("Hi", model.Message); + Assert.AreEqual(5, model.Number); + } + } +} From 7ac745be6795adcde535466cae62252f40595404 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Tue, 22 Nov 2022 11:05:00 -0800 Subject: [PATCH 10/21] some test re-org for clarity; not complete. --- .../tests/public/JsonDataArrayTests.cs | 221 ++++++++++-------- .../tests/public/JsonDataLeafTests.cs | 206 ++++++++-------- .../tests/public/JsonDataObjectTests.cs | 86 +++++++ 3 files changed, 326 insertions(+), 187 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs index 8423e9aa7aa8..e789fbdc8f57 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs @@ -2,18 +2,15 @@ // Licensed under the MIT License. using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; using NUnit.Framework; namespace Azure.Core.Tests.Public { public class JsonDataArrayTests { + #region Convert tests + [Test] public void CanConvertArrayToIEnumerable() { @@ -38,6 +35,18 @@ public void CanConvertArrayToIEnumerable() } } + [Test] + public void CanConvertArrayToArray() + { + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + + int[] array = data; + + Assert.AreEqual(1, array[0]); + Assert.AreEqual(2, array[1]); + Assert.AreEqual(3, array[2]); + } + [Test] public void CannotConvertArrayToLeaf() { @@ -57,6 +66,29 @@ public void CannotConvertArrayToModel() Assert.Throws(() => { var model = (SampleModel)data; }); } + [Test] + public void CannotConvertArrayPropertyToLeaf() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + // TODO: Standardize Exception types. + Assert.Throws(() => { var model = (int)data.value; }); + Assert.Throws(() => { var model = (bool)data.value; }); + Assert.Throws(() => { var model = (string)data.value; }); + Assert.Throws(() => { var model = (DateTime)data.value; }); + } + + [Test] + public void CannotConvertArrayPropertyToModel() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var model = (SampleModel)data.value; }); + } + + #endregion + + #region GetMember tests + [Test] public void CannotGetMemberOnArray() { @@ -64,6 +96,17 @@ public void CannotGetMemberOnArray() Assert.Throws(() => { var x = data.Property; }); } + [Test] + public void CannotGetMemberOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var x = data.value.Property; }); + } + + #endregion + + #region SetMember tests + [Test] public void CannotSetMemberOnArray() { @@ -71,6 +114,17 @@ public void CannotSetMemberOnArray() Assert.Throws(() => { data.Property = "invalid"; }); } + [Test] + public void CannotSetMemberOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { data.value.Property = "invalid"; }); + } + + #endregion + + #region GetIndex tests + [Test] public void CannotGetIndexPropertyOnArray() { @@ -79,10 +133,10 @@ public void CannotGetIndexPropertyOnArray() } [Test] - public void CannotSetIndexPropertyOnArray() + public void CannotGetIndexPropertyOnArrayProperty() { - dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - Assert.Throws(() => { data["Property"] = "invalid"; }); + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { var x = data.value["Property"]; }); } [Test] @@ -96,17 +150,41 @@ public void CanGetArrayIndex() } [Test] - public void CanGetArrayLength() + public void CanGetArrayPropertyIndex() { - dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - Assert.AreEqual(3, data.Length); + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + + Assert.AreEqual(1, (int)data.value[0]); + Assert.AreEqual(2, (int)data.value[1]); + Assert.AreEqual(3, (int)data.value[2]); } [Test] - public void CannotSetArrayLength() + public void CanGetObjectMemberViaArrayIndex() + { + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" + ); + + Assert.AreEqual("tagValue", (string)data.value[0].tag); + } + + #endregion + + #region SetIndex tests + + [Test] + public void CannotSetIndexPropertyOnArray() { dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - Assert.Throws(() => { data.Length = 5; }); + Assert.Throws(() => { data["Property"] = "invalid"; }); + } + + [Test] + public void CannotSetIndexPropertyOnArrayProperty() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.Throws(() => { data.value["Property"] = "invalid"; }); } [Test] @@ -127,79 +205,57 @@ public void CanSetArrayIndex() } [Test] - public void CanEnumerateArray() + public void CanSetArrayPropertyIndex() { - dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - int i = 1; - foreach (int item in data) - { - Assert.AreEqual(i++, item); - } - } + data.value[0] = 5; + data.value[1] = "valid"; + data.value[2] = null; - [Test] - public void CannotConvertArrayPropertyToLeaf() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); + Assert.AreEqual(5, (int)data.value[0]); + Assert.AreEqual("valid", (string)data.value[1]); - // TODO: Standardize Exception types. - Assert.Throws(() => { var model = (int)data.value; }); - Assert.Throws(() => { var model = (bool)data.value; }); - Assert.Throws(() => { var model = (string)data.value; }); - Assert.Throws(() => { var model = (DateTime)data.value; }); + // TODO: to check for null, we have to cast to string. Is that + // what we want? + Assert.AreEqual(null, (string)data.value[2]); } [Test] - public void CannotConvertArrayPropertyToModel() + public void CanSetObjectMemberViaArrayIndex() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { var model = (SampleModel)data.value; }); - } + dynamic data = JsonDataTestHelpers.CreateFromJson( + @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" + ); - [Test] - public void CannotGetMemberOnArrayProperty() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { var x = data.value.Property; }); - } + data.value[0].tag = "newValue"; - [Test] - public void CannotSetMemberOnArrayProperty() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { data.value.Property = "invalid"; }); + Assert.AreEqual("newValue", (string)data.value[0].tag); } - [Test] - public void CannotGetIndexPropertyOnArrayProperty() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { var x = data.value["Property"]; }); - } + #endregion + + #region Array behaviors tests [Test] - public void CannotSetIndexPropertyOnArrayProperty() + public void CanGetArrayLength() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { data.value["Property"] = "invalid"; }); + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.AreEqual(3, data.Length); } [Test] - public void CanGetArrayPropertyIndex() + public void CanGetArrayPropertyLength() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - - Assert.AreEqual(1, (int)data.value[0]); - Assert.AreEqual(2, (int)data.value[1]); - Assert.AreEqual(3, (int)data.value[2]); + Assert.AreEqual(3, data.value.Length); } [Test] - public void CanGetArrayPropertyLength() + public void CannotSetArrayLength() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.AreEqual(3, data.value.Length); + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); + Assert.Throws(() => { data.Length = 5; }); } [Test] @@ -210,20 +266,15 @@ public void CannotSetArrayPropertyLength() } [Test] - public void CanSetArrayPropertyIndex() + public void CanEnumerateArray() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - - data.value[0] = 5; - data.value[1] = "valid"; - data.value[2] = null; - - Assert.AreEqual(5, (int)data.value[0]); - Assert.AreEqual("valid", (string)data.value[1]); + dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - // TODO: to check for null, we have to cast to string. Is that - // what we want? - Assert.AreEqual(null, (string)data.value[2]); + int i = 1; + foreach (int item in data) + { + Assert.AreEqual(i++, item); + } } [Test] @@ -238,26 +289,6 @@ public void CanEnumerateArrayProperty() } } - [Test] - public void CanGetObjectMemberNestedInArray() - { - dynamic data = JsonDataTestHelpers.CreateFromJson( - @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" - ); - - Assert.AreEqual("tagValue", (string)data.value[0].tag); - } - - [Test] - public void CanSetObjectMemberNestedInArray() - { - dynamic data = JsonDataTestHelpers.CreateFromJson( - @"{ ""value"": [ { ""tag"": ""tagValue"" }, 2, 3] }" - ); - - data.value[0].tag = "newValue"; - - Assert.AreEqual("newValue", (string)data.value[0].tag); - } + #endregion } } diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs index 153b0941ddc3..5be4a0bf5b3e 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs @@ -9,6 +9,8 @@ namespace Azure.Core.Tests.Public { public class JsonDataLeafTests { + #region Convert tests + [Test] public void CanConvertIntLeafToInt() { @@ -16,6 +18,13 @@ public void CanConvertIntLeafToInt() Assert.AreEqual(5, (int)data); } + [Test] + public void CanConvertIntLeafPropertyToInt() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5, (int)data.value); + } + [Test] public void CanConvertIntLeafToDouble() { @@ -23,6 +32,13 @@ public void CanConvertIntLeafToDouble() Assert.AreEqual(5d, (double)data); } + [Test] + public void CanConvertIntLeafPropertyToDouble() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5d, (double)data.value); + } + [Test] public void CanConvertIntLeafToLong() { @@ -30,6 +46,13 @@ public void CanConvertIntLeafToLong() Assert.AreEqual((long)5, (long)data); } + [Test] + public void CanConvertIntLeafPropertyToLong() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual((long)5, (long)data.value); + } + [Test] public void CannotConvertIntLeafToString() { @@ -39,6 +62,15 @@ public void CannotConvertIntLeafToString() ); } + [Test] + public void CannotConvertIntLeafPropertyToString() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var s = (string)data.value; } + ); + } + [Test] public void CannotConvertIntLeafToBoolean() { @@ -48,6 +80,15 @@ public void CannotConvertIntLeafToBoolean() ); } + [Test] + public void CannotConvertIntLeafPropertyToBoolean() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var b = (bool)data.value; } + ); + } + [Test] public void CannotConvertIntLeafToModel() { @@ -62,156 +103,137 @@ public void CannotConvertIntLeafToModel() } [Test] - public void CannotGetMemberOnLeaf() + public void CannotConvertIntLeafPropertyToModel() { - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); - Assert.Throws( - () => { var x = data.Property; } - ); - } + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - [Test] - public void CannotSetMemberOnLeaf() - { - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); - Assert.Throws( - () => { data.Property = "invalid"; } + // TODO: Throws JsonException - is that preferred over + // InvalidOperationException? + + Assert.Throws( + () => { var model = (SampleModel)data.value; } ); } - [Test] - public void CannotGetIndexPropertyOnLeaf() - { - // TODO: Make exception messages consistent. + #endregion - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); - Assert.Throws( - () => { var x = data["Property"]; } - ); - } + #region GetMember tests [Test] - public void CannotSetIndexPropertyOnLeaf() + public void CannotGetMemberOnLeaf() { - // TODO: Make exception messages consistent. - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { data["Property"] = "invalid"; } + () => { var x = data.Property; } ); } [Test] - public void CannotGetArrayIndexOnLeaf() + public void CannotGetMemberOnLeafProperty() { - // TODO: Make exception messages consistent. - - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); Assert.Throws( - () => { var x = data[0]; } + () => { var x = data.value.Property; } ); } + #endregion + + #region SetMember tests [Test] - public void CannotSetArrayIndexOnLeaf() + public void CannotSetMemberOnLeaf() { - // TODO: Make exception messages consistent. - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { data[0] = "invalid"; } + () => { data.Property = "invalid"; } ); } [Test] - public void CannotEnumerateLeaf() + public void CannotSetMemberOnLeafProperty() { - // TODO: This exception says "exepcted kind to be object" - improve this. - - dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); Assert.Throws( - () => { foreach (var item in data) { } } + () => { data.value.Property = "invalid"; } ); } [Test] - public void CanConvertIntLeafPropertyToInt() + public void CanSetLeafProperty() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.AreEqual(5, (int)data.value); + data.value = 6; + Assert.AreEqual(6, (int)data.value); } [Test] - public void CanConvertIntLeafPropertyToDouble() + public void CanSetLeafPropertyToDifferentType() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.AreEqual(5d, (double)data.value); + data.value = "valid"; + Assert.AreEqual("valid", (string)data.value); } - [Test] - public void CanConvertIntLeafPropertyToLong() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.AreEqual((long)5, (long)data.value); - } + #endregion - [Test] - public void CannotConvertIntLeafPropertyToString() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.Throws( - () => { var s = (string)data.value; } - ); - } + #region GetIndex tests [Test] - public void CannotConvertIntLeafPropertyToBoolean() + public void CannotGetIndexPropertyOnLeaf() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.Throws( - () => { var b = (bool)data.value; } + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); + Assert.Throws( + () => { var x = data["Property"]; } ); } [Test] - public void CannotConvertIntLeafPropertyToModel() + public void CannotGetIndexPropertyOnLeafProperty() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - - // TODO: Throws JsonException - is that preferred over - // InvalidOperationException? + // TODO: Make exception messages consistent. - Assert.Throws( - () => { var model = (SampleModel)data.value; } + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.Throws( + () => { var x = data.value["Property"]; } ); } [Test] - public void CannotGetMemberOnLeafProperty() + public void CannotGetArrayIndexOnLeaf() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + // TODO: Make exception messages consistent. + + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { var x = data.value.Property; } + () => { var x = data[0]; } ); } [Test] - public void CannotSetMemberOnLeafProperty() + public void CannotGetArrayIndexOnLeafProperty() { + // TODO: Make exception messages consistent. + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); Assert.Throws( - () => { data.value.Property = "invalid"; } + () => { var x = data.value[0]; } ); } + #endregion + + #region SetIndex tests + [Test] - public void CannotGetIndexPropertyOnLeafProperty() + public void CannotSetIndexPropertyOnLeaf() { // TODO: Make exception messages consistent. - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { var x = data.value["Property"]; } + () => { data["Property"] = "invalid"; } ); } @@ -227,13 +249,13 @@ public void CannotSetIndexPropertyOnLeafProperty() } [Test] - public void CannotGetArrayIndexOnLeafProperty() + public void CannotSetArrayIndexOnLeaf() { // TODO: Make exception messages consistent. - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { var x = data.value[0]; } + () => { data[0] = "invalid"; } ); } @@ -247,32 +269,32 @@ public void CannotSetArrayIndexOnLeafProperty() () => { data.value[0] = "invalid"; } ); } + #endregion + + #region Array behaviors tests [Test] - public void CannotEnumerateLeafProperty() + public void CannotEnumerateLeaf() { // TODO: This exception says "exepcted kind to be object" - improve this. - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + dynamic data = JsonDataTestHelpers.CreateFromJson("5"); Assert.Throws( - () => { foreach (var item in data.value) { } } + () => { foreach (var item in data) { } } ); } [Test] - public void CanSetLeafProperty() + public void CannotEnumerateLeafProperty() { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - data.value = 6; - Assert.AreEqual(6, (int)data.value); - } + // TODO: This exception says "exepcted kind to be object" - improve this. - [Test] - public void CanSetLeafPropertyToDifferentType() - { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - data.value = "valid"; - Assert.AreEqual("valid", (string)data.value); + Assert.Throws( + () => { foreach (var item in data.value) { } } + ); } + + #endregion } } diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs index e34cc0a86728..aefea745eda7 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs @@ -13,6 +13,8 @@ namespace Azure.Core.Tests.Public { public class JsonDataObjectTests { + #region Convert tests + [Test] public void CannotConvertObjectToLeaf() { @@ -38,6 +40,8 @@ public void CanConvertObjectToModel() [Test] public void CanConvertObjectToModelWithExtraProperties() { + // TODO: this is just how JsonSerializer works - change this + // test to do something useful. dynamic data = JsonDataTestHelpers.CreateFromJson( @"{ ""Message"": ""Hi"", ""Number"" : 5, @@ -49,6 +53,7 @@ public void CanConvertObjectToModelWithExtraProperties() Assert.AreEqual(5, model.Number); } + // TODO: remove [Test] public void GoofingOffWithSerializerOptions() { @@ -75,5 +80,86 @@ public void GoofingOffWithSerializerOptions() Assert.AreEqual("Hi", model.Message); Assert.AreEqual(5, model.Number); } + + #endregion + + #region GetMember tests + + public void CanGetMemberOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5, (int)data.value); + } + + #endregion + + #region SetMember tests + + public void CanSetMemberOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); + Assert.AreEqual(5, (int)data.value); + + data.value = 6; + Assert.AreEqual(6, (int)data.value); + } + + #endregion + + #region GetIndex tests + + public void CanGetIndexPropertyOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": ""hi"" }"); + string prop = data["value"]; + Assert.AreEqual("hi", prop); + } + + [Test] + public void CannotGetArrayIndexOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": ""hi"" }"); + Assert.Throws( + () => { var x = data[0]; } + ); + } + #endregion + + #region SetIndex tests + + public void CanSetIndexPropertyOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": ""hi"" }"); + data["value"] = "hello"; + Assert.AreEqual("hello", (string)data["value"]); + } + + [Test] + public void CannotSetArrayIndexOnObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": ""hi"" }"); + Assert.Throws( + () => { data[0] = "invalid"; } + ); + } + + #endregion + + [Test] + public void CanEnumerateObject() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""first"": 1, ""second"": 2 }"); + + var expectedKeys = new[] { "first", "second" }; + var expectedValues = new[] { 1, 2 }; + + int i = 0; + foreach (var pair in data) + { + Assert.AreEqual(expectedKeys[i], pair.Key); + Assert.AreEqual(expectedValues[i], (int)pair.Value); + i++; + } + } } } From cc1de188837c85dccf282260ed5becafffce407a Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 2 Dec 2022 15:29:06 -0800 Subject: [PATCH 11/21] Make JsonData public and change namespace --- .../Azure.Core.Experimental.netstandard2.0.cs | 102 ++++-------------- .../src/BinaryDataExtensions.cs | 3 +- .../Azure.Core.Experimental/src/JsonData.cs | 16 ++- .../tests/JsonDataDynamicMutableTests.cs | 2 +- .../tests/JsonDataTests.cs | 3 +- .../tests/public/JsonDataObjectTests.cs | 32 ------ .../public/JsonDataPublicMutableTests.cs | 1 + .../tests/public/JsonDataPublicTests.cs | 1 + .../tests/public/JsonDataTestHelpers.cs | 5 +- 9 files changed, 36 insertions(+), 129 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index 5ec260852c6e..edf87c9fc723 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -106,91 +106,35 @@ public readonly partial struct Value [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public bool TryGetValue(out T value) { throw null; } } } -namespace Azure.Core +namespace Azure.Core.Dynamic { + public static partial class BinaryDataExtensions + { + public static dynamic ToDynamic(this System.BinaryData data) { throw null; } + } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] - public partial class JsonData : System.Dynamic.IDynamicMetaObjectProvider, System.IEquatable + public partial class JsonData : System.Dynamic.IDynamicMetaObjectProvider, System.IEquatable { - public JsonData() { } - public JsonData(object? value) { } - public JsonData(object? value, System.Text.Json.JsonSerializerOptions options, System.Type? type = null) { } - public JsonData(System.Text.Json.JsonDocument jsonDocument) { } - public Azure.Core.JsonData this[int arrayIndex] { get { throw null; } set { } } - public Azure.Core.JsonData this[string propertyName] { get { throw null; } set { } } - public System.Collections.Generic.IEnumerable Items { get { throw null; } } - public System.Text.Json.JsonValueKind Kind { get { throw null; } } - public int Length { get { throw null; } } - public System.Collections.Generic.IEnumerable Properties { get { throw null; } } - public void Add(bool value) { } - public void Add(double value) { } - public void Add(int value) { } - public void Add(long value) { } - public Azure.Core.JsonData Add(object? serializable) { throw null; } - public Azure.Core.JsonData Add(object? serializable, System.Text.Json.JsonSerializerOptions options) { throw null; } - public void Add(float value) { } - public void Add(string? value) { } - public Azure.Core.JsonData AddEmptyArray() { throw null; } - public Azure.Core.JsonData AddEmptyObject() { throw null; } - public Azure.Core.JsonData Add(T[] serializable) { throw null; } - public Azure.Core.JsonData Add(T[] serializable, System.Text.Json.JsonSerializerOptions options) { throw null; } - public static Azure.Core.JsonData EmptyArray() { throw null; } - public static Azure.Core.JsonData EmptyObject() { throw null; } - public bool Equals(Azure.Core.JsonData other) { throw null; } + internal JsonData() { } + public bool Equals(Azure.Core.Dynamic.JsonData other) { throw null; } public override bool Equals(object? obj) { throw null; } - public static Azure.Core.JsonData FromBytes(byte[] utf8Json) { throw null; } - public static Azure.Core.JsonData FromBytes(System.ReadOnlyMemory utf8Json) { throw null; } - public static Azure.Core.JsonData FromObject(T value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } - public static Azure.Core.JsonData FromStream(System.IO.Stream utf8Json) { throw null; } - public static System.Threading.Tasks.Task FromStreamAsync(System.IO.Stream utf8JsonStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static Azure.Core.JsonData FromString(string json) { throw null; } - public Azure.Core.JsonData? Get(string propertyName) { throw null; } public override int GetHashCode() { throw null; } - public T Get(string propertyName) { throw null; } - public T Get(string propertyName, System.Text.Json.JsonSerializerOptions options) { throw null; } - public static bool operator ==(Azure.Core.JsonData? left, string? right) { throw null; } - public static bool operator ==(string? left, Azure.Core.JsonData? right) { throw null; } - public static explicit operator bool (Azure.Core.JsonData json) { throw null; } - public static explicit operator double (Azure.Core.JsonData json) { throw null; } - public static explicit operator int (Azure.Core.JsonData json) { throw null; } - public static explicit operator long (Azure.Core.JsonData json) { throw null; } - public static explicit operator bool? (Azure.Core.JsonData json) { throw null; } - public static explicit operator double? (Azure.Core.JsonData json) { throw null; } - public static explicit operator int? (Azure.Core.JsonData json) { throw null; } - public static explicit operator long? (Azure.Core.JsonData json) { throw null; } - public static explicit operator float? (Azure.Core.JsonData json) { throw null; } - public static explicit operator float (Azure.Core.JsonData json) { throw null; } - public static explicit operator string (Azure.Core.JsonData json) { throw null; } - public static implicit operator Azure.Core.JsonData (bool value) { throw null; } - public static implicit operator Azure.Core.JsonData (double value) { throw null; } - public static implicit operator Azure.Core.JsonData (int value) { throw null; } - public static implicit operator Azure.Core.JsonData (long value) { throw null; } - public static implicit operator Azure.Core.JsonData (bool? value) { throw null; } - public static implicit operator Azure.Core.JsonData (double? value) { throw null; } - public static implicit operator Azure.Core.JsonData (int? value) { throw null; } - public static implicit operator Azure.Core.JsonData (long? value) { throw null; } - public static implicit operator Azure.Core.JsonData (float? value) { throw null; } - public static implicit operator Azure.Core.JsonData (float value) { throw null; } - public static implicit operator Azure.Core.JsonData (string? value) { throw null; } - public static bool operator !=(Azure.Core.JsonData? left, string? right) { throw null; } - public static bool operator !=(string? left, Azure.Core.JsonData? right) { throw null; } - public void Set(string propertyName, bool value) { } - public void Set(string propertyName, double value) { } - public void Set(string propertyName, int value) { } - public void Set(string propertyName, long value) { } - public Azure.Core.JsonData Set(string propertyName, object? serializable) { throw null; } - public Azure.Core.JsonData Set(string propertyName, object? serializable, System.Text.Json.JsonSerializerOptions options) { throw null; } - public void Set(string propertyName, float value) { } - public void Set(string propertyName, string? value) { } - public Azure.Core.JsonData SetEmptyArray(string propertyName) { throw null; } - public Azure.Core.JsonData SetEmptyObject(string propertyName) { throw null; } - public Azure.Core.JsonData Set(string propertyName, T[] serializable) { throw null; } - public Azure.Core.JsonData Set(string propertyName, T[] serializable, System.Text.Json.JsonSerializerOptions options) { throw null; } + public static bool operator ==(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } + public static bool operator ==(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } + public static explicit operator bool (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator double (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator int (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator long (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator bool? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator double? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator int? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator long? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator float? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator float (Azure.Core.Dynamic.JsonData json) { throw null; } + public static explicit operator string (Azure.Core.Dynamic.JsonData json) { throw null; } + public static bool operator !=(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } + public static bool operator !=(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } - public string ToJsonString() { throw null; } public override string ToString() { throw null; } - public T To() { throw null; } - public T To(System.Text.Json.JsonSerializerOptions options) { throw null; } - public long WriteTo(System.IO.Stream stream) { throw null; } - public System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken) { throw null; } } } diff --git a/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs index 5b6edb7aca8d..a4aa920c03f6 100644 --- a/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs +++ b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs @@ -2,9 +2,8 @@ // Licensed under the MIT License. using System; -using Azure.Core; -namespace Azure +namespace Azure.Core.Dynamic { /// /// Extensions to BinaryData. diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 46fa5a38dd46..a29c32c51295 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -13,10 +13,8 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -namespace Azure.Core +namespace Azure.Core.Dynamic { /// /// A mutable representation of a JSON value. @@ -24,7 +22,7 @@ namespace Azure.Core [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(JsonDataDebuggerProxy))] [JsonConverter(typeof(JsonConverter))] - internal class JsonData : IDynamicMetaObjectProvider, IEquatable + public class JsonData : IDynamicMetaObjectProvider, IEquatable { private readonly JsonValueKind _kind; private Dictionary? _objectRepresentation; @@ -60,7 +58,7 @@ internal static JsonData Parse(string json) /// /// The JsonDocument to convert. /// A JsonDocument can be constructed from a JSON string using . - public JsonData(JsonDocument jsonDocument) : this((object?)jsonDocument) + internal JsonData(JsonDocument jsonDocument) : this((object?)jsonDocument) { } @@ -68,7 +66,7 @@ public JsonData(JsonDocument jsonDocument) : this((object?)jsonDocument) /// Creates a new JsonData object which represents the given object. /// /// The value to convert. - public JsonData(object? value) : this(value, DefaultJsonSerializerOptions) + internal JsonData(object? value) : this(value, DefaultJsonSerializerOptions) { } @@ -78,7 +76,7 @@ public JsonData(object? value) : this(value, DefaultJsonSerializerOptions) /// The value to convert. /// Options to control the conversion behavior. /// The type of the value to convert. - public JsonData(object? value, JsonSerializerOptions options, Type? type = null) + internal JsonData(object? value, JsonSerializerOptions options, Type? type = null) { _value = value; switch (value) @@ -346,7 +344,7 @@ internal string ToJsonString() /// /// The of the value of this instance. /// - public JsonValueKind Kind + internal JsonValueKind Kind { get => _kind; } @@ -355,7 +353,7 @@ public JsonValueKind Kind /// Returns the number of elements in this array. /// /// If is not this methods throws . - public int Length + internal int Length { get => EnsureArray().Count; } diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs index 503210d4566f..eb977062bc87 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Text.Json; +using Azure.Core.Dynamic; using Azure.Core.GeoJson; using NUnit.Framework; diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs index 3cf55b28bf41..de0dd47650c3 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs @@ -2,10 +2,9 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; +using Azure.Core.Dynamic; using NUnit.Framework; namespace Azure.Core.Tests diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs index aefea745eda7..a09ae888ac6c 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs @@ -2,11 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; using NUnit.Framework; namespace Azure.Core.Tests.Public @@ -53,34 +49,6 @@ public void CanConvertObjectToModelWithExtraProperties() Assert.AreEqual(5, model.Number); } - // TODO: remove - [Test] - public void GoofingOffWithSerializerOptions() - { - //string json = @"{ ""Message"": ""Hi"", - // ""Number"" : 5, - // ""Invalid"" : ""Not on SampleModel"" }"; - - // "invalid"" is silently ignored - // https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-7-0#missingmemberhandling - // called "missing on the target type" - string json = @"{ ""message"": ""Hi"", - ""invalid"" : ""Not on SampleModel"" }"; - - //dynamic data = JsonDataTestHelpers.CreateFromJson(json); - - var model = JsonSerializer.Deserialize(json, - new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); - - var d = JsonSerializer.Deserialize>(json); - //var i = JsonSerializer.Deserialize(json); - - //SampleModel model = data; - - Assert.AreEqual("Hi", model.Message); - Assert.AreEqual(5, model.Number); - } - #endregion #region GetMember tests diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs index 9accbb31f26f..a97ce0f1e39a 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Azure.Core.Dynamic; using Azure.Core.GeoJson; using NUnit.Framework; diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 44a19fd53eea..885841745c12 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Azure.Core.Dynamic; using NUnit.Framework; namespace Azure.Core.Tests.Public diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs index 052e60784b7f..4ac0da4aecca 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataTestHelpers.cs @@ -2,10 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Azure.Core.Dynamic; namespace Azure.Core.Tests.Public { From a2b2fc57a6a771fa5060a27328fd0048619f2d85 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 2 Dec 2022 15:46:49 -0800 Subject: [PATCH 12/21] add int equality operator --- .../Azure.Core.Experimental/src/JsonData.cs | 55 +++++++++++++++++++ .../tests/public/JsonDataPublicTests.cs | 17 ++++++ 2 files changed, 72 insertions(+) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index a29c32c51295..8f91a5f4b77a 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -341,6 +341,58 @@ internal string ToJsonString() /// False if the given JsonData represents the given string, and false otherwise public static bool operator !=(string? left, JsonData? right) => !(left == right); + /// + /// Returns true if a has the same value as a given int, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given int, and false otherwise. + public static bool operator ==(JsonData? left, int right) + { + if (left is null) + { + return false; + } + + return left.Kind == JsonValueKind.Number && ((int)left) == right; + } + + /// + /// Returns false if a has the same value as a given int, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given string, and false otherwise + public static bool operator !=(JsonData? left, int right) => !(left == right); + + /// + /// Returns true if a has the same value as a given int, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given int, and false otherwise. + public static bool operator ==(int left, JsonData? right) + { + if (right is null) + { + return false; + } + + return right.Kind == JsonValueKind.Number && ((int)right) == left; + } + + /// + /// Returns false if a has the same value as a given int, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given int, and false otherwise + public static bool operator !=(int left, JsonData? right) => !(left == right); + /// /// The of the value of this instance. /// @@ -437,6 +489,7 @@ public override int GetHashCode() } private string? GetString() => (string?)EnsureValue(); + private int GetIn32() { var value = EnsureNumberValue().AsLong(); @@ -448,6 +501,7 @@ private int GetIn32() } private long GetLong() => EnsureNumberValue().AsLong(); + private float GetFloat() { var value = EnsureNumberValue().AsDouble(); @@ -458,6 +512,7 @@ private float GetFloat() return (float)value; } private double GetDouble() => EnsureNumberValue().AsDouble(); + private bool GetBoolean() => (bool)EnsureValue()!; private void WriteTo(Utf8JsonWriter writer) diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 885841745c12..2f0a4e686d5e 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -349,6 +349,23 @@ public void EqualsForObjectsAndArrays() Assert.AreNotEqual(arr1, arr2); } + [Test] + public void OperatorEqualsForInt() + { + dynamic fiveJson = new BinaryData("{ \"value\": 5 }").ToDynamic().value; + dynamic sixJson = new BinaryData("{ \"value\": 6 }").ToDynamic().value; + + Assert.IsTrue(fiveJson == 5); + Assert.IsTrue(5 == fiveJson); + Assert.IsFalse(fiveJson != 5); + Assert.IsFalse(5 != fiveJson); + + Assert.IsFalse(sixJson == 5); + Assert.IsFalse(5 == sixJson); + Assert.IsTrue(sixJson != 5); + Assert.IsTrue(5 != sixJson); + } + [Test] public void OperatorEqualsForString() { From eb74ae2f776c72ef2aaed27a6f425a0f2838fe28 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 2 Dec 2022 16:16:25 -0800 Subject: [PATCH 13/21] update API --- .../api/Azure.Core.Experimental.netstandard2.0.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index edf87c9fc723..cee13ac475f8 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -119,7 +119,9 @@ internal JsonData() { } public bool Equals(Azure.Core.Dynamic.JsonData other) { throw null; } public override bool Equals(object? obj) { throw null; } public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Core.Dynamic.JsonData? left, int right) { throw null; } public static bool operator ==(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } + public static bool operator ==(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } public static bool operator ==(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } public static explicit operator bool (Azure.Core.Dynamic.JsonData json) { throw null; } public static explicit operator double (Azure.Core.Dynamic.JsonData json) { throw null; } @@ -132,7 +134,9 @@ internal JsonData() { } public static explicit operator float? (Azure.Core.Dynamic.JsonData json) { throw null; } public static explicit operator float (Azure.Core.Dynamic.JsonData json) { throw null; } public static explicit operator string (Azure.Core.Dynamic.JsonData json) { throw null; } + public static bool operator !=(Azure.Core.Dynamic.JsonData? left, int right) { throw null; } public static bool operator !=(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } + public static bool operator !=(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } public static bool operator !=(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } From 3f1adb9bc296e1612af15833d801d4cb24e9ac72 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Tue, 6 Dec 2022 17:12:40 -0800 Subject: [PATCH 14/21] add test that only works when access is public --- .../tests/public/JsonDataObjectTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs index a09ae888ac6c..1150ee631805 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs @@ -3,6 +3,7 @@ using System; using System.Text.Json; +using Microsoft.CSharp.RuntimeBinder; using NUnit.Framework; namespace Azure.Core.Tests.Public @@ -129,5 +130,13 @@ public void CanEnumerateObject() i++; } } + + [Test] + public void CannotCallGetDirectly() + { + dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""first"": 1, ""second"": 2 }"); + + Assert.Throws(() => data.Get("first")); + } } } From c4ebd66890ce27aa81dc24e5d9406a420876b36e Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Wed, 7 Dec 2022 13:52:18 -0800 Subject: [PATCH 15/21] Address build break in Experimental perf project --- ...oj => Azure.Core.Experimental.Perf.csproj} | 0 .../tests/perf/DynamicReadingBenchmark.cs | 6 ++-- .../tests/perf/FirstReadBenchmark.cs | 6 ++-- .../perf/StronglyTypedReadingBenchmark.cs | 30 ------------------- sdk/core/Azure.Core/Azure.Core.sln | 6 ++++ 5 files changed, 14 insertions(+), 34 deletions(-) rename sdk/core/Azure.Core.Experimental/tests/perf/{Azure.Core.Experimental.Performance.csproj => Azure.Core.Experimental.Perf.csproj} (100%) delete mode 100644 sdk/core/Azure.Core.Experimental/tests/perf/StronglyTypedReadingBenchmark.cs diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Performance.csproj b/sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.csproj similarity index 100% rename from sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Performance.csproj rename to sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.csproj diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs index 20db7c0e5634..9f2518282b40 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Dynamic; -using Azure.Core; +using Azure.Core.Dynamic; using BenchmarkDotNet.Attributes; using Newtonsoft.Json.Linq; @@ -13,9 +14,10 @@ namespace Azure.Data.AppConfiguration.Performance public class DynamicReadingBenchmark { private static string _json = "{\"a\":{\"b\":5}}"; + private static BinaryData _binaryData = new BinaryData(_json); private static dynamic _expandoObject = new ExpandoObject(); - private static dynamic _jsonData = JsonData.FromString(_json); + private static dynamic _jsonData = _binaryData.ToDynamic(); private static dynamic _dynamicNewtonsoftJson = JObject.Parse(_json); static DynamicReadingBenchmark() diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs index c8886776c477..586af969e450 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Text.Json; -using Azure.Core; +using Azure.Core.Dynamic; using BenchmarkDotNet.Attributes; namespace Azure.Data.AppConfiguration.Performance @@ -11,6 +12,7 @@ namespace Azure.Data.AppConfiguration.Performance public class FirstReadBenchmark { private static string _json = "{\"a\":{\"b\":5}}"; + private static BinaryData _binaryData = new BinaryData(_json); [Benchmark(Baseline = true)] public int ReadJsonElement() @@ -21,7 +23,7 @@ public int ReadJsonElement() [Benchmark] public int ReadJsonData() { - return (int)JsonData.FromString(_json)["a"]["b"]; + return (int)_binaryData.ToDynamic()["a"]["b"]; } } } diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/StronglyTypedReadingBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/StronglyTypedReadingBenchmark.cs deleted file mode 100644 index 9fae706e104c..000000000000 --- a/sdk/core/Azure.Core.Experimental/tests/perf/StronglyTypedReadingBenchmark.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Text.Json; -using Azure.Core; -using BenchmarkDotNet.Attributes; - -namespace Azure.Data.AppConfiguration.Performance -{ - [MemoryDiagnoser] - public class StronglyTypedReadingBenchmark - { - private static string _json = "{\"a\":{\"b\":5}}"; - - private static JsonElement _element = JsonDocument.Parse(_json).RootElement; - private static JsonData _jsonData = JsonData.FromString(_json); - - [Benchmark(Baseline = true)] - public int ReadJsonElement() - { - return _element.GetProperty("a").GetProperty("b").GetInt32(); - } - - [Benchmark] - public int ReadJsonData() - { - return (int)_jsonData["a"]["b"]; - } - } -} diff --git a/sdk/core/Azure.Core/Azure.Core.sln b/sdk/core/Azure.Core/Azure.Core.sln index 0c89b7d14274..44cd66cf3ed6 100644 --- a/sdk/core/Azure.Core/Azure.Core.sln +++ b/sdk/core/Azure.Core/Azure.Core.sln @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Expressions.Data EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Tests.Public", "..\Azure.Core.Experimental\tests\public\Azure.Core.Experimental.Tests.Public.csproj", "{3A0B2AFE-21E3-4585-A348-EE0645F98414}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Perf", "..\Azure.Core.Experimental\tests\perf\Azure.Core.Experimental.Perf.csproj", "{8812429A-CDA8-4951-B6FD-2CC84741FFA9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +133,10 @@ Global {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A0B2AFE-21E3-4585-A348-EE0645F98414}.Release|Any CPU.Build.0 = Release|Any CPU + {8812429A-CDA8-4951-B6FD-2CC84741FFA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8812429A-CDA8-4951-B6FD-2CC84741FFA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8812429A-CDA8-4951-B6FD-2CC84741FFA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8812429A-CDA8-4951-B6FD-2CC84741FFA9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 17f842bff2c9a51a0096c168eddd5eb3d8c4b83f Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Wed, 7 Dec 2022 14:44:00 -0800 Subject: [PATCH 16/21] changes to benchmark project --- .../Azure.Core.Experimental/src/JsonData.cs | 18 ++++++++++++++---- ...e.Core.Experimental.Perf.Benchmarks.csproj} | 0 .../tests/perf/DynamicReadingBenchmark.cs | 1 + sdk/core/Azure.Core/Azure.Core.sln | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) rename sdk/core/Azure.Core.Experimental/tests/perf/{Azure.Core.Experimental.Perf.csproj => Azure.Core.Experimental.Perf.Benchmarks.csproj} (100%) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index 8f91a5f4b77a..e23bffc08d63 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -115,10 +116,19 @@ internal JsonData(object? value, JsonSerializerOptions options, Type? type = nul break; default: Type inputType = type ?? (value == null ? typeof(object) : value.GetType()); - string json = JsonSerializer.Serialize(value, inputType, options); - JsonElement e = JsonDocument.Parse(json).RootElement; - _kind = e.ValueKind; - InitFromElement(e); + + // TODO: Profile to determine if this is the best approach to serialize/parse + using (var stream = new MemoryStream()) + { + using (var writer = new Utf8JsonWriter(stream)) + { + JsonSerializer.Serialize(writer, value, inputType, options); + stream.Position = 0; + JsonElement e = JsonDocument.Parse(stream).RootElement; + _kind = e.ValueKind; + InitFromElement(e); + } + } break; } } diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.csproj b/sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.Benchmarks.csproj similarity index 100% rename from sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.csproj rename to sdk/core/Azure.Core.Experimental/tests/perf/Azure.Core.Experimental.Perf.Benchmarks.csproj diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs index 9f2518282b40..9576d4513ad5 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs @@ -37,6 +37,7 @@ public int ReadJsonData() { return _jsonData.a.b; } + [Benchmark] public int ReadNewtonsoftJson() { diff --git a/sdk/core/Azure.Core/Azure.Core.sln b/sdk/core/Azure.Core/Azure.Core.sln index 44cd66cf3ed6..08d4bf1538be 100644 --- a/sdk/core/Azure.Core/Azure.Core.sln +++ b/sdk/core/Azure.Core/Azure.Core.sln @@ -53,7 +53,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Expressions.Data EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Tests.Public", "..\Azure.Core.Experimental\tests\public\Azure.Core.Experimental.Tests.Public.csproj", "{3A0B2AFE-21E3-4585-A348-EE0645F98414}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Perf", "..\Azure.Core.Experimental\tests\perf\Azure.Core.Experimental.Perf.csproj", "{8812429A-CDA8-4951-B6FD-2CC84741FFA9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Perf.Benchmarks", "..\Azure.Core.Experimental\tests\perf\Azure.Core.Experimental.Perf.Benchmarks.csproj", "{8812429A-CDA8-4951-B6FD-2CC84741FFA9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 1766a04504a18830f97b49ac088359add6e9bfba Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 9 Dec 2022 11:22:28 -0800 Subject: [PATCH 17/21] sm refactor: move operators to a separate file before adding many --- .../src/JsonData.Operators.cs | 205 ++++++++++++++++++ .../Azure.Core.Experimental/src/JsonData.cs | 182 +--------------- 2 files changed, 206 insertions(+), 181 deletions(-) create mode 100644 sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs new file mode 100644 index 000000000000..fad0e2815f21 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Azure.Core.Dynamic +{ + /// + /// A mutable representation of a JSON value. + /// + public partial class JsonData : IDynamicMetaObjectProvider, IEquatable + { + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator bool(JsonData json) => json.GetBoolean(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator int(JsonData json) => json.GetIn32(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator long(JsonData json) => json.GetLong(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator string?(JsonData json) => json.GetString(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator float(JsonData json) => json.GetFloat(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator double(JsonData json) => json.GetDouble(); + + /// + /// Converts the value to a + /// + /// The value to convert. + public static explicit operator bool?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetBoolean(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static explicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetIn32(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static explicit operator long?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetLong(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static explicit operator float?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetFloat(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static explicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); + + /// + /// Returns true if a has the same value as a given string, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given string, and false otherwise. + public static bool operator ==(JsonData? left, string? right) + { + if (left is null && right is null) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.Kind == JsonValueKind.String && ((string?)left._value) == right; + } + + /// + /// Returns false if a has the same value as a given string, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given string, and false otherwise + public static bool operator !=(JsonData? left, string? right) => !(left == right); + + /// + /// Returns true if a has the same value as a given string, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given string, and false otherwise. + public static bool operator ==(string? left, JsonData? right) + { + if (left is null && right is null) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return right.Kind == JsonValueKind.String && ((string?)right._value) == left; + } + + /// + /// Returns false if a has the same value as a given string, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given string, and false otherwise + public static bool operator !=(string? left, JsonData? right) => !(left == right); + + /// + /// Returns true if a has the same value as a given int, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given int, and false otherwise. + public static bool operator ==(JsonData? left, int right) + { + if (left is null) + { + return false; + } + + return left.Kind == JsonValueKind.Number && ((int)left) == right; + } + + /// + /// Returns false if a has the same value as a given int, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given string, and false otherwise + public static bool operator !=(JsonData? left, int right) => !(left == right); + + /// + /// Returns true if a has the same value as a given int, + /// and false otherwise. + /// + /// The to compare. + /// The to compare. + /// True if the given JsonData represents the given int, and false otherwise. + public static bool operator ==(int left, JsonData? right) + { + if (right is null) + { + return false; + } + + return right.Kind == JsonValueKind.Number && ((int)right) == left; + } + + /// + /// Returns false if a has the same value as a given int, + /// and true otherwise. + /// + /// The to compare. + /// The to compare. + /// False if the given JsonData represents the given int, and false otherwise + public static bool operator !=(int left, JsonData? right) => !(left == right); + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index e23bffc08d63..f76892daf58a 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -23,7 +23,7 @@ namespace Azure.Core.Dynamic [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(JsonDataDebuggerProxy))] [JsonConverter(typeof(JsonConverter))] - public class JsonData : IDynamicMetaObjectProvider, IEquatable + public partial class JsonData : IDynamicMetaObjectProvider, IEquatable { private readonly JsonValueKind _kind; private Dictionary? _objectRepresentation; @@ -223,186 +223,6 @@ internal string ToJsonString() return Encoding.UTF8.GetString(memoryStream.ToArray()); } - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator bool(JsonData json) => json.GetBoolean(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator int(JsonData json) => json.GetIn32(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator long(JsonData json) => json.GetLong(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator string?(JsonData json) => json.GetString(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator float(JsonData json) => json.GetFloat(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator double(JsonData json) => json.GetDouble(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static explicit operator bool?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetBoolean(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - public static explicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetIn32(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - public static explicit operator long?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetLong(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - public static explicit operator float?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetFloat(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - public static explicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); - - /// - /// Returns true if a has the same value as a given string, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given string, and false otherwise. - public static bool operator ==(JsonData? left, string? right) - { - if (left is null && right is null) - { - return true; - } - - if (left is null || right is null) - { - return false; - } - - return left.Kind == JsonValueKind.String && ((string?)left._value) == right; - } - - /// - /// Returns false if a has the same value as a given string, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given string, and false otherwise - public static bool operator !=(JsonData? left, string? right) => !(left == right); - - /// - /// Returns true if a has the same value as a given string, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given string, and false otherwise. - public static bool operator ==(string? left, JsonData? right) - { - if (left is null && right is null) - { - return true; - } - - if (left is null || right is null) - { - return false; - } - - return right.Kind == JsonValueKind.String && ((string?)right._value) == left; - } - - /// - /// Returns false if a has the same value as a given string, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given string, and false otherwise - public static bool operator !=(string? left, JsonData? right) => !(left == right); - - /// - /// Returns true if a has the same value as a given int, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given int, and false otherwise. - public static bool operator ==(JsonData? left, int right) - { - if (left is null) - { - return false; - } - - return left.Kind == JsonValueKind.Number && ((int)left) == right; - } - - /// - /// Returns false if a has the same value as a given int, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given string, and false otherwise - public static bool operator !=(JsonData? left, int right) => !(left == right); - - /// - /// Returns true if a has the same value as a given int, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given int, and false otherwise. - public static bool operator ==(int left, JsonData? right) - { - if (right is null) - { - return false; - } - - return right.Kind == JsonValueKind.Number && ((int)right) == left; - } - - /// - /// Returns false if a has the same value as a given int, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given int, and false otherwise - public static bool operator !=(int left, JsonData? right) => !(left == right); - /// /// The of the value of this instance. /// From a0ed82361b8689ba55eb08cab6180d461c9bcff9 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 9 Dec 2022 11:56:58 -0800 Subject: [PATCH 18/21] exclude public tests from InternalsVisibleTo test project, and ignore tests for unimplemented features. --- .../tests/Azure.Core.Experimental.Tests.csproj | 1 + .../tests/public/JsonDataPublicTests.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj b/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj index 7d5cbf62640f..7eb4406f4e7a 100644 --- a/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj +++ b/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 2f0a4e686d5e..2ec4499145ed 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -122,6 +122,7 @@ public void CanTestPropertyForNull() } [Test] + [Ignore(reason: "TODO: Decide whether to require cast for this case or not.")] public void CanAddStringToList() { dynamic jsonData = new BinaryData(new { value = "foo" }).ToDynamic(); @@ -139,6 +140,7 @@ public void CanAddStringToList() } [Test] + [Ignore(reason:"TODO: Decide whether to require cast for this case or not.")] public void CanAddIntToList() { dynamic jsonData = new BinaryData(new { value = 5 }).ToDynamic(); @@ -154,6 +156,7 @@ public void CanAddIntToList() } [Test] + [Ignore(reason: "TODO: Feature to be added in later version.")] public void GetMemberIsCaseInsensitive() { dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }").ToDynamic(); @@ -384,12 +387,12 @@ public void OperatorEqualsForString() } [Test] + [Ignore(reason: "TODO: Decide whether to require cast for this case or not.")] public void EqualsForStringNUnit() { dynamic foo = new BinaryData("{ \"value\": \"foo\" }").ToDynamic(); var value = foo.Value; - // TODO: Outstanding question regarding whether we want these to succeed without a cast. Assert.AreEqual(value, "foo"); Assert.AreEqual("foo", value); From f2864fc510429803002dd66109f2aad092a0e973 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 9 Dec 2022 16:11:55 -0800 Subject: [PATCH 19/21] make casts to primitive types implicit --- .../src/JsonData.Operators.cs | 32 +++++++------------ .../tests/public/JsonDataPublicTests.cs | 10 ------ 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs index fad0e2815f21..a32dd12ac287 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs @@ -2,18 +2,8 @@ // Licensed under the MIT License. using System; -using System.Buffers; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; using System.Dynamic; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; namespace Azure.Core.Dynamic { @@ -26,67 +16,67 @@ public partial class JsonData : IDynamicMetaObjectProvider, IEquatable /// Converts the value to a /// /// The value to convert. - public static explicit operator bool(JsonData json) => json.GetBoolean(); + public static implicit operator bool(JsonData json) => json.GetBoolean(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator int(JsonData json) => json.GetIn32(); + public static implicit operator int(JsonData json) => json.GetIn32(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator long(JsonData json) => json.GetLong(); + public static implicit operator long(JsonData json) => json.GetLong(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator string?(JsonData json) => json.GetString(); + public static implicit operator string?(JsonData json) => json.GetString(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator float(JsonData json) => json.GetFloat(); + public static implicit operator float(JsonData json) => json.GetFloat(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator double(JsonData json) => json.GetDouble(); + public static implicit operator double(JsonData json) => json.GetDouble(); /// /// Converts the value to a /// /// The value to convert. - public static explicit operator bool?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetBoolean(); + public static implicit operator bool?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetBoolean(); /// /// Converts the value to a or null. /// /// The value to convert. - public static explicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetIn32(); + public static implicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetIn32(); /// /// Converts the value to a or null. /// /// The value to convert. - public static explicit operator long?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetLong(); + public static implicit operator long?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetLong(); /// /// Converts the value to a or null. /// /// The value to convert. - public static explicit operator float?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetFloat(); + public static implicit operator float?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetFloat(); /// /// Converts the value to a or null. /// /// The value to convert. - public static explicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); + public static implicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); /// /// Returns true if a has the same value as a given string, diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs index 2ec4499145ed..9b8b0f9de9a7 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -122,17 +122,11 @@ public void CanTestPropertyForNull() } [Test] - [Ignore(reason: "TODO: Decide whether to require cast for this case or not.")] public void CanAddStringToList() { dynamic jsonData = new BinaryData(new { value = "foo" }).ToDynamic(); List list = new(); - - // TODO: Should we add an implicit cast to string? - // Can we do it dynamically? - // Alternatively, we could provide a different type for string leaf - // nodes, like we do for numbers. list.Add(jsonData.value); Assert.AreEqual(1, list.Count); @@ -140,15 +134,11 @@ public void CanAddStringToList() } [Test] - [Ignore(reason:"TODO: Decide whether to require cast for this case or not.")] public void CanAddIntToList() { dynamic jsonData = new BinaryData(new { value = 5 }).ToDynamic(); List list = new(); - - // TODO: Should we add an implicit cast to int? - // Can we do it dynamically? list.Add(jsonData.value); Assert.AreEqual(1, list.Count); From 4f714bbdf419ead55e5645792f8dce54130ee291 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Fri, 9 Dec 2022 17:16:06 -0800 Subject: [PATCH 20/21] export API --- .../Azure.Core.Experimental.netstandard2.0.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index cee13ac475f8..b9c08529bb56 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -123,17 +123,17 @@ internal JsonData() { } public static bool operator ==(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } public static bool operator ==(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } public static bool operator ==(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static explicit operator bool (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator double (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator int (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator long (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator bool? (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator double? (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator int? (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator long? (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator float? (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator float (Azure.Core.Dynamic.JsonData json) { throw null; } - public static explicit operator string (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator bool (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator double (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator int (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator long (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator bool? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator double? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator int? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator long? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator float? (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator float (Azure.Core.Dynamic.JsonData json) { throw null; } + public static implicit operator string (Azure.Core.Dynamic.JsonData json) { throw null; } public static bool operator !=(Azure.Core.Dynamic.JsonData? left, int right) { throw null; } public static bool operator !=(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } public static bool operator !=(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } From 1b5df72dd1e8741ca2b18d8c8cda860615e56b83 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Mon, 12 Dec 2022 10:21:10 -0800 Subject: [PATCH 21/21] pr fb --- .../Azure.Core.Experimental/src/JsonData.Operators.cs | 4 ++-- sdk/core/Azure.Core.Experimental/src/JsonData.cs | 10 +++++----- .../public/Azure.Core.Experimental.Tests.Public.csproj | 4 ---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs index a32dd12ac287..8b2cbfbac0de 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs @@ -22,7 +22,7 @@ public partial class JsonData : IDynamicMetaObjectProvider, IEquatable /// Converts the value to a /// /// The value to convert. - public static implicit operator int(JsonData json) => json.GetIn32(); + public static implicit operator int(JsonData json) => json.GetInt32(); /// /// Converts the value to a @@ -58,7 +58,7 @@ public partial class JsonData : IDynamicMetaObjectProvider, IEquatable /// Converts the value to a or null. /// /// The value to convert. - public static implicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetIn32(); + public static implicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetInt32(); /// /// Converts the value to a or null. diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.cs index f76892daf58a..3c4e168d5f66 100644 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ b/sdk/core/Azure.Core.Experimental/src/JsonData.cs @@ -320,7 +320,7 @@ public override int GetHashCode() private string? GetString() => (string?)EnsureValue(); - private int GetIn32() + private int GetInt32() { var value = EnsureNumberValue().AsLong(); if (value > int.MaxValue || value < int.MinValue) @@ -408,7 +408,7 @@ private Dictionary EnsureObject() /// /// The name of the property to get the value of. /// - private object? GetDynamicProperty(string propertyName) + private object? GetDynamicPropertyValue(string propertyName) { if (_kind == JsonValueKind.Array && propertyName == nameof(Length)) { @@ -433,7 +433,7 @@ private Dictionary EnsureObject() return GetValueAt(arrayIndex);; } - throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); + throw new InvalidOperationException($"Tried to access indexer with an unsupported index type: {index}"); } private JsonData SetValue(string propertyName, object value) @@ -468,7 +468,7 @@ private JsonData SetViaIndexer(object index, object value) return SetValueAt(arrayIndex, value); } - throw new InvalidOperationException($"Tried to access indexer with unsupport index type: {index}"); + throw new InvalidOperationException($"Tried to access indexer with an unsupported index type: {index}"); } private JsonData GetValueAt(int index) @@ -580,7 +580,7 @@ public double AsDouble() private class MetaObject : DynamicMetaObject { - private static readonly MethodInfo GetDynamicValueMethod = typeof(JsonData).GetMethod(nameof(GetDynamicProperty), BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo GetDynamicValueMethod = typeof(JsonData).GetMethod(nameof(GetDynamicPropertyValue), BindingFlags.NonPublic | BindingFlags.Instance); private static readonly MethodInfo GetDynamicEnumerableMethod = typeof(JsonData).GetMethod(nameof(GetDynamicEnumerable), BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj b/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj index b51709f9e10b..79e3e1ad9634 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj +++ b/sdk/core/Azure.Core.Experimental/tests/public/Azure.Core.Experimental.Tests.Public.csproj @@ -15,10 +15,6 @@ - - - -