diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net461.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net461.cs new file mode 100644 index 0000000000000..7335516eb5e0c --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net461.cs @@ -0,0 +1,203 @@ +namespace Azure +{ + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct Value + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public Value(System.ArraySegment segment) { throw null; } + public Value(System.ArraySegment segment) { throw null; } + public Value(bool value) { throw null; } + public Value(byte value) { throw null; } + public Value(char value) { throw null; } + public Value(System.DateTime value) { throw null; } + public Value(System.DateTimeOffset value) { throw null; } + public Value(double value) { throw null; } + public Value(short value) { throw null; } + public Value(int value) { throw null; } + public Value(long value) { throw null; } + public Value(bool? value) { throw null; } + public Value(byte? value) { throw null; } + public Value(char? value) { throw null; } + public Value(System.DateTimeOffset? value) { throw null; } + public Value(System.DateTime? value) { throw null; } + public Value(double? value) { throw null; } + public Value(short? value) { throw null; } + public Value(int? value) { throw null; } + public Value(long? value) { throw null; } + public Value(sbyte? value) { throw null; } + public Value(float? value) { throw null; } + public Value(ushort? value) { throw null; } + public Value(uint? value) { throw null; } + public Value(ulong? value) { throw null; } + public Value(object? value) { throw null; } + public Value(sbyte value) { throw null; } + public Value(float value) { throw null; } + public Value(ushort value) { throw null; } + public Value(uint value) { throw null; } + public Value(ulong value) { throw null; } + public System.Type? Type { get { throw null; } } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public T As() { throw null; } + public static Azure.Value Create(T value) { throw null; } + public static explicit operator System.ArraySegment (in Azure.Value value) { throw null; } + public static explicit operator System.ArraySegment (in Azure.Value value) { throw null; } + public static explicit operator bool (in Azure.Value value) { throw null; } + public static explicit operator byte (in Azure.Value value) { throw null; } + public static explicit operator char (in Azure.Value value) { throw null; } + public static explicit operator System.DateTime (in Azure.Value value) { throw null; } + public static explicit operator System.DateTimeOffset (in Azure.Value value) { throw null; } + public static explicit operator decimal (in Azure.Value value) { throw null; } + public static explicit operator double (in Azure.Value value) { throw null; } + public static explicit operator short (in Azure.Value value) { throw null; } + public static explicit operator int (in Azure.Value value) { throw null; } + public static explicit operator long (in Azure.Value value) { throw null; } + public static explicit operator bool? (in Azure.Value value) { throw null; } + public static explicit operator byte? (in Azure.Value value) { throw null; } + public static explicit operator char? (in Azure.Value value) { throw null; } + public static explicit operator System.DateTimeOffset? (in Azure.Value value) { throw null; } + public static explicit operator System.DateTime? (in Azure.Value value) { throw null; } + public static explicit operator decimal? (in Azure.Value value) { throw null; } + public static explicit operator double? (in Azure.Value value) { throw null; } + public static explicit operator short? (in Azure.Value value) { throw null; } + public static explicit operator int? (in Azure.Value value) { throw null; } + public static explicit operator long? (in Azure.Value value) { throw null; } + public static explicit operator sbyte? (in Azure.Value value) { throw null; } + public static explicit operator float? (in Azure.Value value) { throw null; } + public static explicit operator ushort? (in Azure.Value value) { throw null; } + public static explicit operator uint? (in Azure.Value value) { throw null; } + public static explicit operator ulong? (in Azure.Value value) { throw null; } + public static explicit operator sbyte (in Azure.Value value) { throw null; } + public static explicit operator float (in Azure.Value value) { throw null; } + public static explicit operator ushort (in Azure.Value value) { throw null; } + public static explicit operator uint (in Azure.Value value) { throw null; } + public static explicit operator ulong (in Azure.Value value) { throw null; } + public static implicit operator Azure.Value (System.ArraySegment value) { throw null; } + public static implicit operator Azure.Value (System.ArraySegment value) { throw null; } + public static implicit operator Azure.Value (bool value) { throw null; } + public static implicit operator Azure.Value (byte value) { throw null; } + public static implicit operator Azure.Value (char value) { throw null; } + public static implicit operator Azure.Value (System.DateTime value) { throw null; } + public static implicit operator Azure.Value (System.DateTimeOffset value) { throw null; } + public static implicit operator Azure.Value (decimal value) { throw null; } + public static implicit operator Azure.Value (double value) { throw null; } + public static implicit operator Azure.Value (short value) { throw null; } + public static implicit operator Azure.Value (int value) { throw null; } + public static implicit operator Azure.Value (long value) { throw null; } + public static implicit operator Azure.Value (bool? value) { throw null; } + public static implicit operator Azure.Value (byte? value) { throw null; } + public static implicit operator Azure.Value (char? value) { throw null; } + public static implicit operator Azure.Value (System.DateTimeOffset? value) { throw null; } + public static implicit operator Azure.Value (System.DateTime? value) { throw null; } + public static implicit operator Azure.Value (decimal? value) { throw null; } + public static implicit operator Azure.Value (double? value) { throw null; } + public static implicit operator Azure.Value (short? value) { throw null; } + public static implicit operator Azure.Value (int? value) { throw null; } + public static implicit operator Azure.Value (long? value) { throw null; } + public static implicit operator Azure.Value (sbyte? value) { throw null; } + public static implicit operator Azure.Value (float? value) { throw null; } + public static implicit operator Azure.Value (ushort? value) { throw null; } + public static implicit operator Azure.Value (uint? value) { throw null; } + public static implicit operator Azure.Value (ulong? value) { throw null; } + public static implicit operator Azure.Value (sbyte value) { throw null; } + public static implicit operator Azure.Value (float value) { throw null; } + public static implicit operator Azure.Value (ushort value) { throw null; } + public static implicit operator Azure.Value (uint value) { throw null; } + public static implicit operator Azure.Value (ulong value) { throw null; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public bool TryGetValue(out T value) { throw null; } + } +} +namespace Azure.Core.Dynamic +{ + public static partial class BinaryDataExtensions + { + public static dynamic ToDynamic(this System.BinaryData data) { throw null; } + } + public abstract partial class DynamicData + { + protected DynamicData() { } + internal abstract void WriteTo(System.IO.Stream stream); + public static void WriteTo(System.IO.Stream stream, Azure.Core.Dynamic.DynamicData data) { } + } + public partial class DynamicJson : Azure.Core.Dynamic.DynamicData, System.Dynamic.IDynamicMetaObjectProvider + { + internal DynamicJson() { } + public static implicit operator bool (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator bool? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator string (Azure.Core.Dynamic.DynamicJson value) { throw null; } + System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } + public override string ToString() { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.DynamicJson Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.DynamicJson.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } + public partial class MutableJsonDocument + { + internal MutableJsonDocument() { } + public Azure.Core.Dynamic.MutableJsonElement RootElement { get { throw null; } } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(System.BinaryData utf8Json) { throw null; } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(string json) { throw null; } + public void WriteTo(System.IO.Stream stream, System.Buffers.StandardFormat format = default(System.Buffers.StandardFormat)) { } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct MutableJsonElement + { + private object _dummy; + private int _dummyPrimitive; + public System.Text.Json.JsonValueKind ValueKind { get { throw null; } } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator EnumerateArray() { throw null; } + public bool GetBoolean() { throw null; } + public double GetDouble() { throw null; } + public int GetInt32() { throw null; } + public long GetInt64() { throw null; } + public Azure.Core.Dynamic.MutableJsonElement GetProperty(string name) { throw null; } + public float GetSingle() { throw null; } + public string? GetString() { throw null; } + public void RemoveProperty(string name) { } + public void Set(Azure.Core.Dynamic.MutableJsonElement value) { } + public void Set(bool value) { } + public void Set(double value) { } + public void Set(int value) { } + public void Set(long value) { } + public void Set(object value) { } + public void Set(float value) { } + public void Set(string value) { } + public Azure.Core.Dynamic.MutableJsonElement SetProperty(string name, object value) { throw null; } + public override string ToString() { throw null; } + public bool TryGetProperty(string name, out Azure.Core.Dynamic.MutableJsonElement value) { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.MutableJsonElement Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net6.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net6.0.cs new file mode 100644 index 0000000000000..7335516eb5e0c --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net6.0.cs @@ -0,0 +1,203 @@ +namespace Azure +{ + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct Value + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public Value(System.ArraySegment segment) { throw null; } + public Value(System.ArraySegment segment) { throw null; } + public Value(bool value) { throw null; } + public Value(byte value) { throw null; } + public Value(char value) { throw null; } + public Value(System.DateTime value) { throw null; } + public Value(System.DateTimeOffset value) { throw null; } + public Value(double value) { throw null; } + public Value(short value) { throw null; } + public Value(int value) { throw null; } + public Value(long value) { throw null; } + public Value(bool? value) { throw null; } + public Value(byte? value) { throw null; } + public Value(char? value) { throw null; } + public Value(System.DateTimeOffset? value) { throw null; } + public Value(System.DateTime? value) { throw null; } + public Value(double? value) { throw null; } + public Value(short? value) { throw null; } + public Value(int? value) { throw null; } + public Value(long? value) { throw null; } + public Value(sbyte? value) { throw null; } + public Value(float? value) { throw null; } + public Value(ushort? value) { throw null; } + public Value(uint? value) { throw null; } + public Value(ulong? value) { throw null; } + public Value(object? value) { throw null; } + public Value(sbyte value) { throw null; } + public Value(float value) { throw null; } + public Value(ushort value) { throw null; } + public Value(uint value) { throw null; } + public Value(ulong value) { throw null; } + public System.Type? Type { get { throw null; } } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public T As() { throw null; } + public static Azure.Value Create(T value) { throw null; } + public static explicit operator System.ArraySegment (in Azure.Value value) { throw null; } + public static explicit operator System.ArraySegment (in Azure.Value value) { throw null; } + public static explicit operator bool (in Azure.Value value) { throw null; } + public static explicit operator byte (in Azure.Value value) { throw null; } + public static explicit operator char (in Azure.Value value) { throw null; } + public static explicit operator System.DateTime (in Azure.Value value) { throw null; } + public static explicit operator System.DateTimeOffset (in Azure.Value value) { throw null; } + public static explicit operator decimal (in Azure.Value value) { throw null; } + public static explicit operator double (in Azure.Value value) { throw null; } + public static explicit operator short (in Azure.Value value) { throw null; } + public static explicit operator int (in Azure.Value value) { throw null; } + public static explicit operator long (in Azure.Value value) { throw null; } + public static explicit operator bool? (in Azure.Value value) { throw null; } + public static explicit operator byte? (in Azure.Value value) { throw null; } + public static explicit operator char? (in Azure.Value value) { throw null; } + public static explicit operator System.DateTimeOffset? (in Azure.Value value) { throw null; } + public static explicit operator System.DateTime? (in Azure.Value value) { throw null; } + public static explicit operator decimal? (in Azure.Value value) { throw null; } + public static explicit operator double? (in Azure.Value value) { throw null; } + public static explicit operator short? (in Azure.Value value) { throw null; } + public static explicit operator int? (in Azure.Value value) { throw null; } + public static explicit operator long? (in Azure.Value value) { throw null; } + public static explicit operator sbyte? (in Azure.Value value) { throw null; } + public static explicit operator float? (in Azure.Value value) { throw null; } + public static explicit operator ushort? (in Azure.Value value) { throw null; } + public static explicit operator uint? (in Azure.Value value) { throw null; } + public static explicit operator ulong? (in Azure.Value value) { throw null; } + public static explicit operator sbyte (in Azure.Value value) { throw null; } + public static explicit operator float (in Azure.Value value) { throw null; } + public static explicit operator ushort (in Azure.Value value) { throw null; } + public static explicit operator uint (in Azure.Value value) { throw null; } + public static explicit operator ulong (in Azure.Value value) { throw null; } + public static implicit operator Azure.Value (System.ArraySegment value) { throw null; } + public static implicit operator Azure.Value (System.ArraySegment value) { throw null; } + public static implicit operator Azure.Value (bool value) { throw null; } + public static implicit operator Azure.Value (byte value) { throw null; } + public static implicit operator Azure.Value (char value) { throw null; } + public static implicit operator Azure.Value (System.DateTime value) { throw null; } + public static implicit operator Azure.Value (System.DateTimeOffset value) { throw null; } + public static implicit operator Azure.Value (decimal value) { throw null; } + public static implicit operator Azure.Value (double value) { throw null; } + public static implicit operator Azure.Value (short value) { throw null; } + public static implicit operator Azure.Value (int value) { throw null; } + public static implicit operator Azure.Value (long value) { throw null; } + public static implicit operator Azure.Value (bool? value) { throw null; } + public static implicit operator Azure.Value (byte? value) { throw null; } + public static implicit operator Azure.Value (char? value) { throw null; } + public static implicit operator Azure.Value (System.DateTimeOffset? value) { throw null; } + public static implicit operator Azure.Value (System.DateTime? value) { throw null; } + public static implicit operator Azure.Value (decimal? value) { throw null; } + public static implicit operator Azure.Value (double? value) { throw null; } + public static implicit operator Azure.Value (short? value) { throw null; } + public static implicit operator Azure.Value (int? value) { throw null; } + public static implicit operator Azure.Value (long? value) { throw null; } + public static implicit operator Azure.Value (sbyte? value) { throw null; } + public static implicit operator Azure.Value (float? value) { throw null; } + public static implicit operator Azure.Value (ushort? value) { throw null; } + public static implicit operator Azure.Value (uint? value) { throw null; } + public static implicit operator Azure.Value (ulong? value) { throw null; } + public static implicit operator Azure.Value (sbyte value) { throw null; } + public static implicit operator Azure.Value (float value) { throw null; } + public static implicit operator Azure.Value (ushort value) { throw null; } + public static implicit operator Azure.Value (uint value) { throw null; } + public static implicit operator Azure.Value (ulong value) { throw null; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public bool TryGetValue(out T value) { throw null; } + } +} +namespace Azure.Core.Dynamic +{ + public static partial class BinaryDataExtensions + { + public static dynamic ToDynamic(this System.BinaryData data) { throw null; } + } + public abstract partial class DynamicData + { + protected DynamicData() { } + internal abstract void WriteTo(System.IO.Stream stream); + public static void WriteTo(System.IO.Stream stream, Azure.Core.Dynamic.DynamicData data) { } + } + public partial class DynamicJson : Azure.Core.Dynamic.DynamicData, System.Dynamic.IDynamicMetaObjectProvider + { + internal DynamicJson() { } + public static implicit operator bool (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator bool? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator string (Azure.Core.Dynamic.DynamicJson value) { throw null; } + System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } + public override string ToString() { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.DynamicJson Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.DynamicJson.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } + public partial class MutableJsonDocument + { + internal MutableJsonDocument() { } + public Azure.Core.Dynamic.MutableJsonElement RootElement { get { throw null; } } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(System.BinaryData utf8Json) { throw null; } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(string json) { throw null; } + public void WriteTo(System.IO.Stream stream, System.Buffers.StandardFormat format = default(System.Buffers.StandardFormat)) { } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct MutableJsonElement + { + private object _dummy; + private int _dummyPrimitive; + public System.Text.Json.JsonValueKind ValueKind { get { throw null; } } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator EnumerateArray() { throw null; } + public bool GetBoolean() { throw null; } + public double GetDouble() { throw null; } + public int GetInt32() { throw null; } + public long GetInt64() { throw null; } + public Azure.Core.Dynamic.MutableJsonElement GetProperty(string name) { throw null; } + public float GetSingle() { throw null; } + public string? GetString() { throw null; } + public void RemoveProperty(string name) { } + public void Set(Azure.Core.Dynamic.MutableJsonElement value) { } + public void Set(bool value) { } + public void Set(double value) { } + public void Set(int value) { } + public void Set(long value) { } + public void Set(object value) { } + public void Set(float value) { } + public void Set(string value) { } + public Azure.Core.Dynamic.MutableJsonElement SetProperty(string name, object value) { throw null; } + public override string ToString() { throw null; } + public bool TryGetProperty(string name, out Azure.Core.Dynamic.MutableJsonElement value) { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.MutableJsonElement Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } +} 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 9540ae753f989..7335516eb5e0c 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 @@ -115,52 +115,89 @@ public static partial class BinaryDataExtensions public abstract partial class DynamicData { protected DynamicData() { } - internal abstract void WriteTo(System.Text.Json.Utf8JsonWriter writer); - public static void WriteTo(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Dynamic.DynamicData data) { } + internal abstract void WriteTo(System.IO.Stream stream); + public static void WriteTo(System.IO.Stream stream, Azure.Core.Dynamic.DynamicData data) { } } - [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] - public partial class JsonData : Azure.Core.Dynamic.DynamicData, System.Dynamic.IDynamicMetaObjectProvider, System.IEquatable + public partial class DynamicJson : Azure.Core.Dynamic.DynamicData, System.Dynamic.IDynamicMetaObjectProvider { - 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, bool right) { throw null; } - public static bool operator ==(Azure.Core.Dynamic.JsonData? left, double right) { throw null; } - public static bool operator ==(Azure.Core.Dynamic.JsonData? left, int right) { throw null; } - public static bool operator ==(Azure.Core.Dynamic.JsonData? left, long right) { throw null; } - public static bool operator ==(Azure.Core.Dynamic.JsonData? left, float right) { throw null; } - public static bool operator ==(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } - public static bool operator ==(bool left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator ==(double left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator ==(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator ==(long left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator ==(float left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator ==(string? left, Azure.Core.Dynamic.JsonData? right) { 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, bool right) { throw null; } - public static bool operator !=(Azure.Core.Dynamic.JsonData? left, double right) { throw null; } - public static bool operator !=(Azure.Core.Dynamic.JsonData? left, int right) { throw null; } - public static bool operator !=(Azure.Core.Dynamic.JsonData? left, long right) { throw null; } - public static bool operator !=(Azure.Core.Dynamic.JsonData? left, float right) { throw null; } - public static bool operator !=(Azure.Core.Dynamic.JsonData? left, string? right) { throw null; } - public static bool operator !=(bool left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator !=(double left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator !=(int left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator !=(long left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator !=(float left, Azure.Core.Dynamic.JsonData? right) { throw null; } - public static bool operator !=(string? left, Azure.Core.Dynamic.JsonData? right) { throw null; } + internal DynamicJson() { } + public static implicit operator bool (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator bool? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator double? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator int? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator long? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float? (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator float (Azure.Core.Dynamic.DynamicJson value) { throw null; } + public static implicit operator string (Azure.Core.Dynamic.DynamicJson value) { throw null; } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.DynamicJson Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.DynamicJson.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + } + public partial class MutableJsonDocument + { + internal MutableJsonDocument() { } + public Azure.Core.Dynamic.MutableJsonElement RootElement { get { throw null; } } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(System.BinaryData utf8Json) { throw null; } + public static Azure.Core.Dynamic.MutableJsonDocument Parse(string json) { throw null; } + public void WriteTo(System.IO.Stream stream, System.Buffers.StandardFormat format = default(System.Buffers.StandardFormat)) { } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct MutableJsonElement + { + private object _dummy; + private int _dummyPrimitive; + public System.Text.Json.JsonValueKind ValueKind { get { throw null; } } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator EnumerateArray() { throw null; } + public bool GetBoolean() { throw null; } + public double GetDouble() { throw null; } + public int GetInt32() { throw null; } + public long GetInt64() { throw null; } + public Azure.Core.Dynamic.MutableJsonElement GetProperty(string name) { throw null; } + public float GetSingle() { throw null; } + public string? GetString() { throw null; } + public void RemoveProperty(string name) { } + public void Set(Azure.Core.Dynamic.MutableJsonElement value) { } + public void Set(bool value) { } + public void Set(double value) { } + public void Set(int value) { } + public void Set(long value) { } + public void Set(object value) { } + public void Set(float value) { } + public void Set(string value) { } + public Azure.Core.Dynamic.MutableJsonElement SetProperty(string name, object value) { throw null; } + public override string ToString() { throw null; } + public bool TryGetProperty(string name, out Azure.Core.Dynamic.MutableJsonElement value) { throw null; } + [System.Diagnostics.DebuggerDisplayAttribute("{Current,nq}")] + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable + { + private object _dummy; + private int _dummyPrimitive; + public Azure.Core.Dynamic.MutableJsonElement Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + public void Dispose() { } + public Azure.Core.Dynamic.MutableJsonElement.ArrayEnumerator GetEnumerator() { throw null; } + public bool MoveNext() { throw null; } + public void Reset() { } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } } } diff --git a/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj b/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj index f1059fb1bbd90..b45eb4a93d693 100644 --- a/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj +++ b/sdk/core/Azure.Core.Experimental/src/Azure.Core.Experimental.csproj @@ -1,11 +1,11 @@ - + Experimental types that might eventually move to Azure.Core Microsoft Azure Client Pipeline Experimental Extensions 0.1.0-preview.24 Microsoft Azure Client Pipeline enable - $(RequiredTargetFrameworks) + $(RequiredTargetFrameworks);net461;net6.0 $(NoWarn);AZC0001;AZC0012 true diff --git a/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs index 355d8deac7a75..f34689fa4365c 100644 --- a/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs +++ b/sdk/core/Azure.Core.Experimental/src/BinaryDataExtensions.cs @@ -15,7 +15,7 @@ public static class BinaryDataExtensions /// public static dynamic ToDynamic(this BinaryData data) { - return JsonData.Parse(data); + return new DynamicJson(MutableJsonDocument.Parse(data).RootElement); } } } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicData.cs b/sdk/core/Azure.Core.Experimental/src/DynamicData.cs index 9b3c64b6561f8..47d6f573bca10 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicData.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicData.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Text.Json; +using System.IO; namespace Azure.Core.Dynamic { @@ -14,15 +14,15 @@ namespace Azure.Core.Dynamic public abstract class DynamicData { /// - /// Writes the data to the provided writer as a JSON value. + /// Writes the data to the provided stream. /// - /// The writer to which to write the document. + /// The stream to which to write the document. /// The dynamic data value to write. - public static void WriteTo(Utf8JsonWriter writer, DynamicData data) + public static void WriteTo(Stream stream, DynamicData data) { - data.WriteTo(writer); + data.WriteTo(stream); } - internal abstract void WriteTo(Utf8JsonWriter writer); + internal abstract void WriteTo(Stream stream); } } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicJson.ArrayEnumerator.cs b/sdk/core/Azure.Core.Experimental/src/DynamicJson.ArrayEnumerator.cs new file mode 100644 index 0000000000000..33d451093f2ae --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicJson.ArrayEnumerator.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Azure.Core.Dynamic +{ + public partial class DynamicJson + { + /// + /// An enumerable and enumerator for the contents of a mutable JSON array. + /// + [DebuggerDisplay("{Current,nq}")] + public struct ArrayEnumerator : IEnumerable, IEnumerator + { + private MutableJsonElement.ArrayEnumerator _enumerator; + + internal ArrayEnumerator(MutableJsonElement.ArrayEnumerator enumerator) + { + _enumerator = enumerator; + } + + /// Returns an enumerator that iterates through a collection. + /// An value that can be used to iterate through the array. + public ArrayEnumerator GetEnumerator() => new(_enumerator.GetEnumerator()); + + /// + public DynamicJson Current => new(_enumerator.Current); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public void Reset() => _enumerator.Reset(); + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() => _enumerator.MoveNext(); + + /// + public void Dispose() => _enumerator.Dispose(); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicJson.DynamicMetaObject.cs b/sdk/core/Azure.Core.Experimental/src/DynamicJson.DynamicMetaObject.cs new file mode 100644 index 0000000000000..98f44e78299ed --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicJson.DynamicMetaObject.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Dynamic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Azure.Core.Dynamic +{ + public partial class DynamicJson : IDynamicMetaObjectProvider + { + /// + DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) => new MetaObject(parameter, this); + + private class MetaObject : DynamicMetaObject + { + internal MetaObject(Expression parameter, IDynamicMetaObjectProvider value) : base(parameter, BindingRestrictions.Empty, value) + { + } + + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + { + UnaryExpression this_ = Expression.Convert(Expression, LimitType); + + Expression[] getPropertyArgs = new Expression[] { Expression.Constant(binder.Name) }; + MethodCallExpression getPropertyCall = Expression.Call(this_, GetPropertyMethod, getPropertyArgs); + + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(getPropertyCall, restrictions); + } + + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + UnaryExpression this_ = Expression.Convert(Expression, LimitType); + + Expression[] arguments = new Expression[] { Expression.Convert(indexes[0].Expression, typeof(object)) }; + MethodCallExpression getViaIndexerCall = Expression.Call(this_, GetViaIndexerMethod, arguments); + + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(getViaIndexerCall, restrictions); + } + + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + Expression this_ = Expression.Convert(Expression, LimitType); + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + + MethodCallExpression convertCall; + + if (binder.Type == typeof(IEnumerable)) + { + convertCall = Expression.Call(this_, GetEnumerableMethod); + return new DynamicMetaObject(convertCall, restrictions); + } + + if (CastFromOperators.TryGetValue(binder.Type, out MethodInfo? castOperator)) + { + convertCall = Expression.Call(castOperator, this_); + return new DynamicMetaObject(convertCall, restrictions); + } + + convertCall = Expression.Call(this_, nameof(ConvertTo), new Type[] { binder.Type }); + return new DynamicMetaObject(convertCall, restrictions); + } + + public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + { + UnaryExpression this_ = Expression.Convert(Expression, LimitType); + + Expression[] setArgs = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) }; + MethodCallExpression setCall = Expression.Call(this_, SetPropertyMethod, setArgs); + + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(setCall, restrictions); + } + + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + UnaryExpression this_ = Expression.Convert(Expression, LimitType); + + Expression[] setArgs = new Expression[2] { + Expression.Convert(indexes[0].Expression, typeof(object)), + Expression.Convert(value.Expression, typeof(object)) + }; + MethodCallExpression setCall = Expression.Call(this_, SetViaIndexerMethod, setArgs); + + BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); + return new DynamicMetaObject(setCall, restrictions); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicJson.Operators.cs b/sdk/core/Azure.Core.Experimental/src/DynamicJson.Operators.cs new file mode 100644 index 0000000000000..514deafdb61ad --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicJson.Operators.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; + +namespace Azure.Core.Dynamic +{ + public partial class DynamicJson + { + // Operators that cast from DynamicJson to another type + private static readonly Dictionary CastFromOperators = typeof(DynamicJson) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(method => method.Name == "op_Explicit" || method.Name == "op_Implicit") + .ToDictionary(method => method.ReturnType); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator bool(DynamicJson value) => value._element.GetBoolean(); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator int(DynamicJson value) => value._element.GetInt32(); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator long(DynamicJson value) => value._element.GetInt64(); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator string?(DynamicJson value) => value._element.GetString(); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator float(DynamicJson value) => value._element.GetSingle(); + + /// + /// Converts the value to a . + /// + /// The value to convert. + public static implicit operator double(DynamicJson value) => value._element.GetDouble(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static implicit operator bool?(DynamicJson value) => value._element.ValueKind == JsonValueKind.Null ? null : value._element.GetBoolean(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static implicit operator int?(DynamicJson value) => value._element.ValueKind == JsonValueKind.Null ? null : value._element.GetInt32(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static implicit operator long?(DynamicJson value) => value._element.ValueKind == JsonValueKind.Null ? null : value._element.GetInt64(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static implicit operator float?(DynamicJson value) => value._element.ValueKind == JsonValueKind.Null ? null : value._element.GetSingle(); + + /// + /// Converts the value to a or null. + /// + /// The value to convert. + public static implicit operator double?(DynamicJson value) => value._element.ValueKind == JsonValueKind.Null ? null : value._element.GetDouble(); + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicJson.cs b/sdk/core/Azure.Core.Experimental/src/DynamicJson.cs new file mode 100644 index 0000000000000..7eac236c00c01 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicJson.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Azure.Core.Dynamic +{ + /// + /// Dynamic layer over MutableJsonDocument. + /// + [JsonConverter(typeof(JsonConverter))] + public partial class DynamicJson : DynamicData + { + private static readonly MethodInfo GetPropertyMethod = typeof(DynamicJson).GetMethod(nameof(GetProperty), BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly MethodInfo SetPropertyMethod = typeof(DynamicJson).GetMethod(nameof(SetProperty), BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly MethodInfo GetEnumerableMethod = typeof(DynamicJson).GetMethod(nameof(GetEnumerable), BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly MethodInfo GetViaIndexerMethod = typeof(DynamicJson).GetMethod(nameof(GetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly MethodInfo SetViaIndexerMethod = typeof(DynamicJson).GetMethod(nameof(SetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance)!; + + private MutableJsonElement _element; + + internal DynamicJson(MutableJsonElement element) + { + _element = element; + } + + internal override void WriteTo(Stream stream) + { + Utf8JsonWriter writer = new(stream); + _element.WriteTo(writer); + writer.Flush(); + } + + private object GetProperty(string name) + { + return new DynamicJson(_element.GetProperty(name)); + } + + private object GetViaIndexer(object index) + { + switch (index) + { + case string propertyName: + return GetProperty(propertyName); + case int arrayIndex: + return new DynamicJson(_element.GetIndexElement(arrayIndex)); + } + + throw new InvalidOperationException($"Tried to access indexer with an unsupported index type: {index}"); + } + + private IEnumerable GetEnumerable() + { + return new ArrayEnumerator(_element.EnumerateArray()); + } + + private object? SetProperty(string name, object value) + { + _element = _element.SetProperty(name, value); + + // Binding machinery expects the call site signature to return an object + return null; + } + + private object? SetViaIndexer(object index, object value) + { + switch (index) + { + case string propertyName: + return SetProperty(propertyName, value); + case int arrayIndex: + MutableJsonElement element = _element.GetIndexElement(arrayIndex); + element.Set(value); + return new DynamicJson(element); + } + + throw new InvalidOperationException($"Tried to access indexer with an unsupported index type: {index}"); + } + + private T ConvertTo() + { +#if NET6_0_OR_GREATER + return JsonSerializer.Deserialize(_element.GetJsonElement(), MutableJsonDocument.DefaultJsonSerializerOptions)!; +#else + Utf8JsonReader reader = MutableJsonElement.GetReaderForElement(_element.GetJsonElement()); + return JsonSerializer.Deserialize(ref reader, MutableJsonDocument.DefaultJsonSerializerOptions); +#endif + } + + /// + public override string ToString() + { + return _element.ToString(); + } + + private class JsonConverter : JsonConverter + { + public override DynamicJson Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return new DynamicJson(new MutableJsonDocument(document).RootElement); + } + + public override void Write(Utf8JsonWriter writer, DynamicJson value, JsonSerializerOptions options) + { + value._element.WriteTo(writer); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs b/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs deleted file mode 100644 index 817a7c544bb3c..0000000000000 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.Operators.cs +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Dynamic; -using System.Text.Json; - -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 implicit operator bool(JsonData json) => json.GetBoolean(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static implicit operator int(JsonData json) => json.GetInt32(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static implicit operator long(JsonData json) => json.GetLong(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static implicit operator string?(JsonData json) => json.GetString(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static implicit operator float(JsonData json) => json.GetFloat(); - - /// - /// Converts the value to a - /// - /// The value to convert. - public static implicit operator double(JsonData json) => json.GetDouble(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - 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 implicit operator int?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetInt32(); - - /// - /// Converts the value to a or null. - /// - /// The value to convert. - 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 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 implicit operator double?(JsonData json) => json.Kind == JsonValueKind.Null ? null : json.GetDouble(); - - /// - /// Returns true if a has the same value as a given bool, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given bool, and false otherwise. - public static bool operator ==(JsonData? left, bool right) - { - if (left is null) - { - return false; - } - - return (left.Kind == JsonValueKind.False || left.Kind == JsonValueKind.True) && - ((bool)left) == right; - } - - /// - /// Returns false if a has the same value as a given bool, - /// 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, bool right) => !(left == right); - - /// - /// Returns true if a has the same value as a given bool, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given bool, and false otherwise. - public static bool operator ==(bool left, JsonData? right) - { - if (right is null) - { - return false; - } - - return (right.Kind == JsonValueKind.False || right.Kind == JsonValueKind.True) && - ((bool)right) == left; - } - - /// - /// Returns false if a has the same value as a given bool, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given bool, and false otherwise - public static bool operator !=(bool 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); - - /// - /// Returns true if a has the same value as a given long, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given long, and false otherwise. - public static bool operator ==(JsonData? left, long right) - { - if (left is null) - { - return false; - } - - return left.Kind == JsonValueKind.Number && ((long)left) == right; - } - - /// - /// Returns false if a has the same value as a given long, - /// 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, long right) => !(left == right); - - /// - /// Returns true if a has the same value as a given long, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given long, and false otherwise. - public static bool operator ==(long left, JsonData? right) - { - if (right is null) - { - return false; - } - - return right.Kind == JsonValueKind.Number && ((long)right) == left; - } - - /// - /// Returns false if a has the same value as a given long, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given long, and false otherwise - public static bool operator !=(long left, JsonData? 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 ==(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 float, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given float, and false otherwise. - public static bool operator ==(JsonData? left, float right) - { - if (left is null) - { - return false; - } - - return left.Kind == JsonValueKind.Number && ((float)left) == right; - } - - /// - /// Returns false if a has the same value as a given float, - /// 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, float right) => !(left == right); - - /// - /// Returns true if a has the same value as a given float, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given float, and false otherwise. - public static bool operator ==(float left, JsonData? right) - { - if (right is null) - { - return false; - } - - return right.Kind == JsonValueKind.Number && ((float)right) == left; - } - - /// - /// Returns false if a has the same value as a given float, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given float, and false otherwise - public static bool operator !=(float left, JsonData? right) => !(left == right); - - /// - /// Returns true if a has the same value as a given double, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given double, and false otherwise. - public static bool operator ==(JsonData? left, double right) - { - if (left is null) - { - return false; - } - - return left.Kind == JsonValueKind.Number && ((double)left) == right; - } - - /// - /// Returns false if a has the same value as a given double, - /// 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, double right) => !(left == right); - - /// - /// Returns true if a has the same value as a given double, - /// and false otherwise. - /// - /// The to compare. - /// The to compare. - /// True if the given JsonData represents the given double, and false otherwise. - public static bool operator ==(double left, JsonData? right) - { - if (right is null) - { - return false; - } - - return right.Kind == JsonValueKind.Number && ((double)right) == left; - } - - /// - /// Returns false if a has the same value as a given double, - /// and true otherwise. - /// - /// The to compare. - /// The to compare. - /// False if the given JsonData represents the given double, and false otherwise - public static bool operator !=(double 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 deleted file mode 100644 index 58b283f9ef0cb..0000000000000 --- a/sdk/core/Azure.Core.Experimental/src/JsonData.cs +++ /dev/null @@ -1,753 +0,0 @@ -// 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. - /// - [DebuggerDisplay("{DebuggerDisplay,nq}")] - [DebuggerTypeProxy(typeof(JsonDataDebuggerProxy))] - [JsonConverter(typeof(JsonConverter))] - public partial class JsonData : DynamicData, IDynamicMetaObjectProvider, IEquatable - { - private readonly JsonValueKind _kind; - private Dictionary? _objectRepresentation; - private List? _arrayRepresentation; - private object? _value; - - private static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new JsonSerializerOptions(); - - /// - /// Parses a UTF-8 encoded string representing a single JSON value into a . - /// - /// A UTF-8 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); - } - - /// - /// Parses test representing a single JSON value into a . - /// - /// The JSON string. - /// A representation of the value. - internal static JsonData Parse(string json) - { - using var doc = JsonDocument.Parse(json); - return new JsonData(doc); - } - - /// - /// Creates a new JsonData object which represents the value of the given JsonDocument. - /// - /// The JsonDocument to convert. - /// A JsonDocument can be constructed from a JSON string using . - internal JsonData(JsonDocument jsonDocument) : this((object?)jsonDocument) - { - } - - /// - /// Creates a new JsonData object which represents the given object. - /// - /// The value to convert. - internal JsonData(object? value) : this(value, DefaultJsonSerializerOptions) - { - } - - /// - /// Creates a new JsonData object which represents the given object. - /// - /// The value to convert. - /// Options to control the conversion behavior. - /// The type of the value to convert. - internal JsonData(object? value, JsonSerializerOptions options, Type? type = null) - { - _value = value; - switch (value) - { - case long l: - _kind = JsonValueKind.Number; - _value = new Number(l); - break; - case int i: - _kind = JsonValueKind.Number; - _value = new Number(i); - break; - case double d: - _kind = JsonValueKind.Number; - _value = new Number(d); - break; - case float d: - _kind = JsonValueKind.Number; - _value = new Number(d); - break; - case bool b when b: - _kind = JsonValueKind.True; - break; - case bool b when !b: - _kind = JsonValueKind.False; - break; - case string: - _kind = JsonValueKind.String; - break; - case null: - _kind = JsonValueKind.Null; - break; - case JsonDocument doc: - _kind = doc.RootElement.ValueKind; - InitFromElement(doc.RootElement); - break; - default: - Type inputType = type ?? (value == null ? typeof(object) : value.GetType()); - - // 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; - } - } - - private JsonData(JsonElement element) - { - _kind = element.ValueKind; - InitFromElement(element); - } - - private void InitFromElement(JsonElement element) - { - switch (element.ValueKind) - { - case JsonValueKind.Object: - _objectRepresentation = new Dictionary(); - foreach (var item in element.EnumerateObject()) - { - _objectRepresentation[item.Name] = new JsonData(item.Value); - } - break; - case JsonValueKind.Array: - _arrayRepresentation = new List(); - foreach (var item in element.EnumerateArray()) - { - _arrayRepresentation.Add(new JsonData(item)); - } - break; - case JsonValueKind.String: - _value = element.GetString(); - break; - case JsonValueKind.Number: - _value = new Number(element); - break; - case JsonValueKind.True: - case JsonValueKind.False: - _value = element.GetBoolean(); - break; - case JsonValueKind.Null: - _value = null; - break; - default: - throw new ArgumentOutOfRangeException(nameof(element), "Unsupported element kind"); - } - } - - /// - /// 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 . - internal JsonData? Get(string propertyName) - { - if (EnsureObject().TryGetValue(propertyName, out JsonData value)) - { - return value; - } - - return null; - } - - /// - /// Converts the given JSON value into an instance of a given type. - /// - /// The type to convert the value into. - /// A new instance of constructed from the underlying JSON value. - internal T To() => To(DefaultJsonSerializerOptions); - - /// - /// Deserializes the given JSON value into an instance of a given type. - /// - /// The type to deserialize the value into - /// Options to control the conversion behavior. - /// A new instance of constructed from the underlying JSON value. - internal T To(JsonSerializerOptions options) - { - return JsonSerializer.Deserialize(ToJsonString(), options); - } - - /// - /// Returns a stringified version of the JSON for this value. - /// - /// Returns a stringified version of the JSON for this value. - internal string ToJsonString() - { - using var memoryStream = new MemoryStream(); - using (var writer = new Utf8JsonWriter(memoryStream)) - { - WriteTo(writer); - } - return Encoding.UTF8.GetString(memoryStream.ToArray()); - } - - /// - /// The of the value of this instance. - /// - internal JsonValueKind Kind - { - get => _kind; - } - - /// - /// Returns the number of elements in this array. - /// - /// If is not this methods throws . - internal int Length - { - get => EnsureArray().Count; - } - - /// - /// Returns the names of all the properties of this object. - /// - /// If is not this methods throws . - internal IEnumerable Properties - { - get => EnsureObject().Keys; - } - - /// - /// Returns all the elements in this array. - /// - /// If is not this methods throws . - internal IEnumerable Items - { - get => EnsureArray(); - } - - /// - public override string ToString() - { - if (_kind == JsonValueKind.Object || _kind == JsonValueKind.Array) - { - return ToJsonString(); - } - - return (_value ?? "").ToString(); - } - - /// - public override bool Equals(object? obj) - { - if (obj is string) - { - return this == ((string?)obj); - } - - if (obj is JsonData) - { - return Equals((JsonData)obj); - } - - return base.Equals(obj); - } - - /// - public bool Equals(JsonData other) - { - if (_kind != other._kind) - { - return false; - } - - switch (_kind) - { - case JsonValueKind.Null: - case JsonValueKind.Undefined: - return true; - case JsonValueKind.Object: - return _objectRepresentation!.Equals(other._objectRepresentation); - case JsonValueKind.Array: - return _arrayRepresentation!.Equals(other._arrayRepresentation); - default: - return _value!.Equals(other._value); - } - } - - /// - public override int GetHashCode() - { - if (_kind == JsonValueKind.String) - { - return ((string?)_value)!.GetHashCode(); - } - - return base.GetHashCode(); - } - - private string? GetString() => (string?)EnsureValue(); - - private int GetInt32() - { - var value = EnsureNumberValue().AsLong(); - if (value > int.MaxValue || value < int.MinValue) - { - throw new OverflowException(); - } - return (int)value; - } - - private long GetLong() => EnsureNumberValue().AsLong(); - - private float GetFloat() - { - var value = EnsureNumberValue().AsDouble(); - if (value > float.MaxValue || value < float.MinValue) - { - throw new OverflowException(); - } - return (float)value; - } - private double GetDouble() => EnsureNumberValue().AsDouble(); - - private bool GetBoolean() => (bool)EnsureValue()!; - - internal override void WriteTo(Utf8JsonWriter writer) - { - switch (_kind) - { - case JsonValueKind.Null: - case JsonValueKind.String: - writer.WriteStringValue((string?)_value); - break; - case JsonValueKind.Number: - ((Number)_value!).WriteTo(writer); - break; - case JsonValueKind.True: - case JsonValueKind.False: - writer.WriteBooleanValue((bool)_value!); - break; - case JsonValueKind.Object: - writer.WriteStartObject(); - foreach (var property in EnsureObject()) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } - writer.WriteEndObject(); - break; - case JsonValueKind.Array: - writer.WriteStartArray(); - foreach (var item in EnsureArray()) - { - item.WriteTo(writer); - } - writer.WriteEndArray(); - break; - } - } - - private Dictionary EnsureObject() - { - if (_kind != JsonValueKind.Object) - { - throw new InvalidOperationException($"Expected kind to be object but was {_kind} instead"); - } - - Debug.Assert(_objectRepresentation != null); - return _objectRepresentation!; - } - - private JsonData? GetPropertyValue(string propertyName) - { - if (EnsureObject().TryGetValue(propertyName, out JsonData element)) - { - return element; - } - - return null; - } - - /// - /// Used by the dynamic meta object to fetch properties. We can't use GetPropertyValue because when the underlying - /// value is an array, we want `.Length` to mean "the length of the array" and not "treat the array as an object - /// and get the Length property", and we also want the return type to be "int" and not a JsonData wrapping the int. - /// - /// The name of the property to get the value of. - /// - private object? GetDynamicPropertyValue(string propertyName) - { - if (_kind == JsonValueKind.Array && propertyName == nameof(Length)) - { - return Length; - } - - if (_kind == JsonValueKind.Object) - { - return GetPropertyValue(propertyName); - } - - throw new InvalidOperationException($"Cannot get property on JSON element with kind {_kind}."); - } - - 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 an unsupported index type: {index}"); - } - - private JsonData SetValue(string propertyName, object value) - { - if (!(value is JsonData json)) - { - json = new JsonData(value); - } - - EnsureObject()[propertyName] = json; - return json; - } - - private List EnsureArray() - { - if (_kind != JsonValueKind.Array) - { - throw new InvalidOperationException($"Expected kind to be array but was {_kind} instead"); - } - - Debug.Assert(_arrayRepresentation != null); - 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 an unsupported index type: {index}"); - } - - private JsonData GetValueAt(int index) - { - return EnsureArray()[index]; - } - - private JsonData SetValueAt(int index, object value) - { - if (!(value is JsonData json)) - { - json = new JsonData(value); - } - - EnsureArray()[index] = json; - return json; - } - - private object? EnsureValue() - { - if (_kind == JsonValueKind.Object || _kind == JsonValueKind.Array) - { - throw new InvalidOperationException($"Expected kind to be value but was {_kind} instead"); - } - - return _value; - } - - private Number EnsureNumberValue() - { - if (_kind != JsonValueKind.Number) - { - throw new InvalidOperationException($"Expected kind to be number but was {_kind} instead"); - } - - return (Number)EnsureValue()!; - } - - /// - DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) => new MetaObject(parameter, this); - - private IEnumerable GetDynamicEnumerable() - { - if (_kind == JsonValueKind.Array) - { - return EnsureArray(); - } - - return EnsureObject(); - } - - private string DebuggerDisplay => ToJsonString(); - - private struct Number - { - public Number(in JsonElement element) - { - _hasDouble = element.TryGetDouble(out _double); - _hasLong = element.TryGetInt64(out _long); - } - - public Number(long l) - { - _long = l; - _hasLong = true; - _double = default; - _hasDouble = false; - } - - private long _long; - private bool _hasLong; - private double _double; - private bool _hasDouble; - - public Number(double d) - { - _long = default; - _hasLong = false; - _double = d; - _hasDouble = true; - } - - public void WriteTo(Utf8JsonWriter writer) - { - if (_hasDouble) - { - writer.WriteNumberValue(_double); - } - else - { - writer.WriteNumberValue(_long); - } - } - - public long AsLong() - { - if (!_hasLong) - { - throw new FormatException(); - } - return _long; - } - - public double AsDouble() - { - return _double; - } - } - - private class MetaObject : DynamicMetaObject - { - 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); - - private static readonly MethodInfo SetValueMethod = typeof(JsonData).GetMethod(nameof(SetValue), BindingFlags.NonPublic | BindingFlags.Instance); - - private static readonly MethodInfo GetViaIndexerMethod = typeof(JsonData).GetMethod(nameof(GetViaIndexer), BindingFlags.NonPublic | BindingFlags.Instance); - - 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(); - - internal MetaObject(Expression parameter, IDynamicMetaObjectProvider value) : base(parameter, BindingRestrictions.Empty, value) - { - } - - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) - { - var targetObject = Expression.Convert(Expression, LimitType); - - var arguments = new Expression[] { Expression.Constant(binder.Name) }; - var getPropertyCall = Expression.Call(targetObject, GetDynamicValueMethod, arguments); - - var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); - return new DynamicMetaObject(getPropertyCall, restrictions); - } - - public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) - { - var targetObject = Expression.Convert(Expression, LimitType); - 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(getViaIndexerCall, 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)) - { - convertCall = Expression.Call(targetObject, GetDynamicEnumerableMethod); - return new DynamicMetaObject(convertCall, restrictions); - } - - if (CastFromOperators.TryGetValue(binder.Type, out MethodInfo? castOperator)) - { - convertCall = Expression.Call(castOperator, targetObject); - return new DynamicMetaObject(convertCall, restrictions); - } - - convertCall = Expression.Call(targetObject, nameof(To), new Type[] { binder.Type }); - return new DynamicMetaObject(convertCall, restrictions); - } - - public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) - { - Expression targetObject = Expression.Convert(Expression, LimitType); - var arguments = new Expression[2] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) }; - - Expression setPropertyCall = Expression.Call(targetObject, SetValueMethod, arguments); - BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); - 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, SetViaIndexerMethod, arguments); - - var restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType); - return new DynamicMetaObject(setCall, restrictions); - } - - 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 - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly JsonData _jsonData; - - public JsonDataDebuggerProxy(JsonData jsonData) - { - _jsonData = jsonData; - } - - [DebuggerDisplay("{Value.DebuggerDisplay,nq}", Name = "{Name,nq}")] - internal class PropertyMember - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public string? Name { get; set; } - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public JsonData? Value { get; set; } - } - - [DebuggerDisplay("{Value,nq}")] - internal class SingleMember - { - public object? Value { get; set; } - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public object Members - { - get - { - if (_jsonData.Kind != JsonValueKind.Array && - _jsonData.Kind != JsonValueKind.Object) - return new SingleMember() { Value = _jsonData.ToJsonString() }; - - return BuildMembers().ToArray(); - } - } - - private IEnumerable BuildMembers() - { - if (_jsonData.Kind == JsonValueKind.Object) - { - foreach (var property in _jsonData.Properties) - { - 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; - } - } - } - } - - /// - /// The default serialization behavior for is not the behavior we want, we want to use - /// the underlying JSON value that wraps, instead of using the default behavior for - /// POCOs. - /// - private class JsonConverter : JsonConverter - { - public override JsonData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var document = JsonDocument.ParseValue(ref reader); - return new JsonData(document); - } - - public override void Write(Utf8JsonWriter writer, JsonData value, JsonSerializerOptions options) - { - value.WriteTo(writer); - } - } - } -} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonChange.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonChange.cs new file mode 100644 index 0000000000000..623437280be92 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonChange.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text; +using System.Text.Json; + +namespace Azure.Core.Dynamic +{ + internal struct MutableJsonChange + { + public string Path { get; set; } + + public int Index { get; set; } + + public object? Value { get; set; } + + /// + /// The change invalidates the existing node's JsonElement + /// due to changes in JsonValueKind or path structure. + /// If this is true, Value holds a new JsonElement. + /// + public bool ReplacesJsonElement { get; set; } + + internal Utf8JsonReader GetReader() + { + if (!ReplacesJsonElement) + { + // This change doesn't represent a new node, so we shouldn't need a new reader. + throw new InvalidOperationException("Unable to get Utf8JsonReader for this change."); + } + + return MutableJsonElement.GetReaderForElement(AsJsonElement()); + } + + internal JsonElement AsJsonElement() + { + if (Value is JsonElement) + { + return (JsonElement)Value; + } + + byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(Value); + return JsonDocument.Parse(bytes).RootElement; + } + + public override string ToString() + { + return $"Path={Path}; Value={Value}; ReplacesJsonElement={ReplacesJsonElement}"; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.ChangeTracker.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.ChangeTracker.cs new file mode 100644 index 0000000000000..fbdfc1fc926ce --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.ChangeTracker.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Azure.Core.Dynamic +{ + public partial class MutableJsonDocument + { + internal class ChangeTracker + { + private List? _changes; + + internal bool HasChanges => _changes != null && _changes.Count > 0; + + internal bool AncestorChanged(string path, int highWaterMark) + { + if (_changes == null) + { + return false; + } + + bool changed = false; + + // Check for changes to ancestor elements + while (!changed && path.Length > 0) + { + path = PopProperty(path); + changed = TryGetChange(path, highWaterMark, out MutableJsonChange change); + } + + return changed; + } + + internal bool DescendantChanged(string path, int highWaterMark) + { + if (_changes == null) + { + return false; + } + + bool changed = false; + + for (int i = _changes!.Count - 1; i > highWaterMark; i--) + { + var c = _changes[i]; + if (c.Path.StartsWith(path, StringComparison.Ordinal)) + { + return true; + } + } + + return changed; + } + + internal bool TryGetChange(ReadOnlySpan path, out MutableJsonChange change) + { + if (_changes == null) + { + change = default; + return false; + } + + // TODO: re-enable optimizations + // (System.Text.Unicode.Utf8 isn't available to us here) + var pathStr = Encoding.UTF8.GetString(path.ToArray()); + + //Span utf16 = stackalloc char[path.Length]; + //OperationStatus status = System.Text.Unicode.Utf8.ToUtf16(path, utf16, out _, out int written, false, true); + //if (status != OperationStatus.Done) + //{ throw new NotImplementedException(); } // TODO: needs to allocate from the pool + //utf16 = utf16.Slice(0, written); + + for (int i = _changes.Count - 1; i >= 0; i--) + { + var c = _changes[i]; + if (c.Path == pathStr) + //if (change.Property.AsSpan().SequenceEqual(utf16)) + { + change = c; + return true; + } + } + + change = default; + return false; + } + + internal bool TryGetChange(string path, in int lastAppliedChange, out MutableJsonChange change) + { + if (_changes == null) + { + change = default; + return false; + } + + for (int i = _changes!.Count - 1; i > lastAppliedChange; i--) + { + var c = _changes[i]; + if (c.Path == path) + { + change = c; + return true; + } + } + + change = default; + return false; + } + + internal int AddChange(string path, object? value, bool replaceJsonElement = false) + { + if (_changes == null) + { + _changes = new List(); + } + + int index = _changes.Count; + + _changes.Add(new MutableJsonChange() + { + Path = path, + Value = value, + Index = index, + ReplacesJsonElement = replaceJsonElement + }); + + return index; + } + + internal static string PushIndex(string path, int index) + { + return PushProperty(path, $"{index}"); + } + + internal static string PopIndex(string path) + { + return PopProperty(path); + } + + internal static string PushProperty(string path, string value) + { + if (path.Length == 0) + { + return value; + } + return $"{path}.{value}"; + } + + internal static string PushProperty(string path, ReadOnlySpan value) + { + string propertyName = BinaryData.FromBytes(value.ToArray()).ToString(); + if (path.Length == 0) + { + return propertyName; + } + return $"{path}.{propertyName}"; + } + + internal static string PopProperty(string path) + { + int lastDelimiter = path.LastIndexOf('.'); + if (lastDelimiter == -1) + { + return string.Empty; + } + return path.Substring(0, lastDelimiter); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.WriteTo.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.WriteTo.cs new file mode 100644 index 0000000000000..f3c6d5ddd9dcb --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.WriteTo.cs @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Text.Json; + +namespace Azure.Core.Dynamic +{ + public partial class MutableJsonDocument + { + internal void WriteRootElementTo(Utf8JsonWriter writer) + { + string path = string.Empty; + + Utf8JsonReader reader; + + // Check for changes at the root. + bool changed = Changes.TryGetChange(path, -1, out MutableJsonChange change); + if (changed) + { + reader = change.GetReader(); + } + else + { + reader = new Utf8JsonReader(_original.Span); + } + + WriteElement(path, -1, ref reader, writer); + + writer.Flush(); + } + + internal void WriteElement(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + writer.WriteStartObject(); + WriteObjectProperties(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.StartArray: + writer.WriteStartArray(); + WriteArrayValues(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.String: + WriteString(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.Number: + WriteNumber(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.True: + case JsonTokenType.False: + WriteBoolean(path, highWaterMark, reader.TokenType, ref reader, writer); + break; + case JsonTokenType.Null: + WriteNull(path, highWaterMark, ref reader, writer); + break; + } + } + } + + private void WriteArrayValues(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + int index = 0; + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + path = ChangeTracker.PushIndex(path, index); + WriteObject(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.StartArray: + path = ChangeTracker.PushIndex(path, index); + writer.WriteStartArray(); + WriteArrayValues(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.String: + path = ChangeTracker.PushIndex(path, index); + WriteString(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.Number: + path = ChangeTracker.PushIndex(path, index); + WriteNumber(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.True: + case JsonTokenType.False: + path = ChangeTracker.PushIndex(path, index); + WriteBoolean(path, highWaterMark, reader.TokenType, ref reader, writer); + break; + case JsonTokenType.Null: + path = ChangeTracker.PushIndex(path, index); + WriteNull(path, highWaterMark, ref reader, writer); + break; + case JsonTokenType.EndArray: + writer.WriteEndArray(); + return; + } + + path = ChangeTracker.PopIndex(path); + index++; + } + } + + private void WriteObjectProperties(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + WriteObject(path, highWaterMark, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.StartArray: + writer.WriteStartArray(); + WriteArrayValues(path, highWaterMark, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.PropertyName: + path = ChangeTracker.PushProperty(path, reader.ValueSpan); + + writer.WritePropertyName(reader.ValueSpan); + Debug.WriteLine($"Path: {path}, TokenStartIndex: {reader.TokenStartIndex}"); + continue; + case JsonTokenType.String: + WriteString(path, highWaterMark, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.Number: + WriteNumber(path, highWaterMark, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.True: + case JsonTokenType.False: + WriteBoolean(path, highWaterMark, reader.TokenType, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.Null: + WriteNull(path, highWaterMark, ref reader, writer); + path = ChangeTracker.PopProperty(path); + continue; + case JsonTokenType.EndObject: + writer.WriteEndObject(); + return; + } + } + } + + private void WriteObject(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + if (Changes.TryGetChange(path, highWaterMark, out MutableJsonChange change)) + { + WriteStructuralChange(path, change, ref reader, writer); + return; + } + + writer.WriteStartObject(); + WriteObjectProperties(path, highWaterMark, ref reader, writer); + } + + private void WriteStructuralChange(string path, MutableJsonChange change, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + Utf8JsonReader changedElementReader = change.GetReader(); + WriteElement(path, change.Index, ref changedElementReader, writer); + + // Skip this element in the original json buffer. + reader.Skip(); + } + + private void WriteString(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + if (Changes.TryGetChange(path, highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + WriteStructuralChange(path, change, ref reader, writer); + return; + } + + writer.WriteStringValue((string)change.Value!); + return; + } + + writer.WriteStringValue(reader.ValueSpan); + return; + } + + private void WriteNumber(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + if (Changes.TryGetChange(path, highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + WriteStructuralChange(path, change, ref reader, writer); + return; + } + + switch (change.Value) + { + case long l: + writer.WriteNumberValue(l); + return; + case int i: + writer.WriteNumberValue(i); + return; + case double d: + writer.WriteNumberValue(d); + return; + case float f: + writer.WriteNumberValue(f); + return; + case JsonElement element: + if (element.TryGetInt64(out long el)) + { + writer.WriteNumberValue(el); + return; + } + if (element.TryGetDouble(out double ed)) + { + writer.WriteNumberValue(ed); + return; + } + throw new InvalidOperationException("Change doesn't store a number value."); + default: + throw new InvalidOperationException("Change doesn't store a number value."); + } + } + + if (reader.TryGetInt64(out long longValue)) + { + writer.WriteNumberValue(longValue); + return; + } + + if (reader.TryGetDouble(out double doubleValue)) + { + writer.WriteNumberValue(doubleValue); + return; + } + + throw new InvalidOperationException("Change doesn't store a number value."); + } + + private void WriteBoolean(string path, int highWaterMark, JsonTokenType token, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + if (Changes.TryGetChange(path, highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + WriteStructuralChange(path, change, ref reader, writer); + return; + } + + writer.WriteBooleanValue((bool)change.Value!); + return; + } + + writer.WriteBooleanValue(value: token == JsonTokenType.True); + } + + private void WriteNull(string path, int highWaterMark, ref Utf8JsonReader reader, Utf8JsonWriter writer) + { + if (Changes.TryGetChange(path, highWaterMark, out MutableJsonChange change) && change.ReplacesJsonElement) + { + WriteStructuralChange(path, change, ref reader, writer); + return; + } + + writer.WriteNullValue(); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.cs new file mode 100644 index 0000000000000..5dad3101fa7a6 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonDocument.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Buffers; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Azure.Core.Dynamic +{ + /// + /// A mutable representation of a JSON value. + /// + [JsonConverter(typeof(JsonConverter))] + public partial class MutableJsonDocument + { + internal static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new JsonSerializerOptions(); + + private readonly Memory _original; + private readonly JsonElement _originalElement; + + internal ChangeTracker Changes { get; } = new(); + + /// + /// Gets the root element of this JSON document. + /// + public MutableJsonElement RootElement + { + get + { + if (Changes.TryGetChange(string.Empty, -1, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + return new MutableJsonElement(this, change.AsJsonElement(), string.Empty, change.Index); + } + } + + return new MutableJsonElement(this, _originalElement, string.Empty); + } + } + + /// + /// Writes the document to the provided stream as a JSON value. + /// + /// + /// + /// + public void WriteTo(Stream stream, StandardFormat format = default) + { + // this is so we can add JSON Patch in the future + if (format != default) + { + throw new ArgumentOutOfRangeException(nameof(format)); + } + + Utf8JsonWriter writer = new Utf8JsonWriter(stream); + if (!Changes.HasChanges) + { + Write(stream, _original.Span); + stream.Flush(); + return; + } + + WriteRootElementTo(writer); + } + + internal void WriteTo(Utf8JsonWriter writer) + { + if (!Changes.HasChanges) + { + _originalElement.WriteTo(writer); + return; + } + + WriteRootElementTo(writer); + } + + private static void Write(Stream stream, ReadOnlySpan buffer) + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(sharedBuffer); + stream.Write(sharedBuffer, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + /// + /// Parses a UTF-8 encoded string representing a single JSON value into a . + /// + /// A UTF-8 encoded string representing a JSON value. + /// A representation of the value. + public static MutableJsonDocument Parse(BinaryData utf8Json) + { + var doc = JsonDocument.Parse(utf8Json); + return new MutableJsonDocument(doc, utf8Json.ToArray().AsMemory()); + } + + /// + /// Parses test representing a single JSON value into a . + /// + /// The JSON string. + /// A representation of the value. + public static MutableJsonDocument Parse(string json) + { + byte[] utf8 = Encoding.UTF8.GetBytes(json); + Memory jsonMemory = utf8.AsMemory(); + return new MutableJsonDocument(JsonDocument.Parse(jsonMemory), jsonMemory); + } + + internal MutableJsonDocument(JsonDocument jsonDocument, Memory utf8Json) : this(jsonDocument.RootElement) + { + _original = utf8Json; + _originalElement = jsonDocument.RootElement; + } + + /// + /// Creates a new JsonData object which represents the given object. + /// + /// The value to convert. + internal MutableJsonDocument(object? value) : this(value, DefaultJsonSerializerOptions) + { + } + + /// + /// Creates a new JsonData object which represents the given object. + /// + /// The value to convert. + /// Options to control the conversion behavior. + /// The type of the value to convert. + internal MutableJsonDocument(object? value, JsonSerializerOptions options, Type? type = null) + { + if (value is JsonDocument) + throw new InvalidOperationException("Calling wrong constructor."); + + Type inputType = type ?? (value == null ? typeof(object) : value.GetType()); + _original = JsonSerializer.SerializeToUtf8Bytes(value, inputType, options); + _originalElement = JsonDocument.Parse(_original).RootElement; + } + + private class JsonConverter : JsonConverter + { + public override MutableJsonDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return new MutableJsonDocument(document); + } + + public override void Write(Utf8JsonWriter writer, MutableJsonDocument value, JsonSerializerOptions options) + { + value.WriteTo(writer); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.ArrayEnumerator.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.ArrayEnumerator.cs new file mode 100644 index 0000000000000..d62c77ac4ce18 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.ArrayEnumerator.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace Azure.Core.Dynamic +{ + public partial struct MutableJsonElement + { + /// + /// An enumerable and enumerator for the contents of a mutable JSON array. + /// + [DebuggerDisplay("{Current,nq}")] + public struct ArrayEnumerator : IEnumerable, IEnumerator + { + private readonly MutableJsonElement _element; + private readonly int _length; + private int _index; + + internal ArrayEnumerator(MutableJsonElement element) + { + Debug.Assert(element.ValueKind == JsonValueKind.Array); + + _element = element; + _length = element._element.GetArrayLength(); + _index = -1; + } + + /// + public MutableJsonElement Current + { + get + { + if (_index < 0) + { + return default; + } + + return _element.GetIndexElement(_index); + } + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An value that can be used to iterate + /// through the array. + /// + public ArrayEnumerator GetEnumerator() + { + ArrayEnumerator enumerator = this; + enumerator._index = -1; + return enumerator; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public void Reset() + { + _index = -1; + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + _index++; + + return _index < _length; + } + + /// + public void Dispose() + { + _index = _length; + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.cs b/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.cs new file mode 100644 index 0000000000000..2162815df73fc --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/MutableJsonElement.cs @@ -0,0 +1,565 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace Azure.Core.Dynamic +{ + /// + /// A mutable representation of a JSON element. + /// + public partial struct MutableJsonElement + { + private readonly MutableJsonDocument _root; + private readonly JsonElement _element; + private readonly string _path; + private readonly int _highWaterMark; + + private readonly MutableJsonDocument.ChangeTracker Changes => _root.Changes; + + internal MutableJsonElement(MutableJsonDocument root, JsonElement element, string path, int highWaterMark = -1) + { + _element = element; + _root = root; + _path = path; + _highWaterMark = highWaterMark; + } + + /// + /// Gets the type of the current JSON value. + /// +#pragma warning disable AZC0014 // Avoid using banned types in public API + public JsonValueKind ValueKind +#pragma warning restore AZC0014 // Avoid using banned types in public API + { + get + { + JsonElement element = GetJsonElement(); + return element.ValueKind; + } + } + + /// + /// Gets the MutableJsonElement for the value of the property with the specified name. + /// + public MutableJsonElement GetProperty(string name) + { + if (!TryGetProperty(name, out MutableJsonElement value)) + { + throw new InvalidOperationException($"{_path} does not containe property called {name}"); + } + + return value; + } + + /// + /// Looks for a property named propertyName in the current object, returning a value that indicates whether or not such a property exists. When the property exists, its value is assigned to the value argument. + /// + /// + /// + /// + public bool TryGetProperty(string name, out MutableJsonElement value) + { + EnsureValid(); + + EnsureObject(); + + bool hasProperty = _element.TryGetProperty(name, out JsonElement element); + if (!hasProperty) + { + value = default; + return false; + } + + var path = MutableJsonDocument.ChangeTracker.PushProperty(_path, name); + if (Changes.TryGetChange(path, _highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + value = new MutableJsonElement(_root, change.AsJsonElement(), path, change.Index); + return true; + } + } + + value = new MutableJsonElement(_root, element, path, _highWaterMark); + return true; + } + + internal MutableJsonElement GetIndexElement(int index) + { + EnsureValid(); + + EnsureArray(); + + var path = MutableJsonDocument.ChangeTracker.PushIndex(_path, index); + + if (Changes.TryGetChange(path, _highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + return new MutableJsonElement(_root, change.AsJsonElement(), path, change.Index); + } + } + + return new MutableJsonElement(_root, _element[index], path, _highWaterMark); + } + + /// + /// Gets the current JSON number as a double. + /// + /// + /// + public double GetDouble() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case double d: + return d; + case JsonElement element: + return element.GetDouble(); + default: + throw new InvalidOperationException($"Element at {_path} is not a double."); + } + } + + return _element.GetDouble(); + } + + /// + /// Gets the current JSON number as an int. + /// + /// + public int GetInt32() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case int i: + return i; + case JsonElement element: + return element.GetInt32(); + default: + throw new InvalidOperationException($"Element at {_path} is not an Int32."); + } + } + + return _element.GetInt32(); + } + + /// + /// Gets the current JSON number as a long. + /// + /// + public long GetInt64() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case long l: + return l; + case JsonElement element: + return element.GetInt64(); + default: + throw new InvalidOperationException($"Element at {_path} is not an Int32."); + } + } + + return _element.GetInt64(); + } + + /// + /// Gets the current JSON number as a float. + /// + /// + public float GetSingle() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case float f: + return f; + case JsonElement element: + return element.GetSingle(); + default: + throw new InvalidOperationException($"Element at {_path} is not an Int32."); + } + } + + return _element.GetSingle(); + } + + /// + /// Gets the value of the element as a string. + /// + /// + /// + public string? GetString() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case string s: + return s; + case JsonElement element: + return element.GetString(); + default: + if (change.Value == null) + { + return null; + } + throw new InvalidOperationException($"Element at {_path} is not a string."); + } + } + + return _element.GetString(); + } + + /// + /// Gets the value of the element as a bool. + /// + /// + /// + public bool GetBoolean() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + switch (change.Value) + { + case bool b: + return b; + case JsonElement element: + return element.GetBoolean(); + default: + throw new InvalidOperationException($"Element at {_path} is not a bool."); + } + } + + return _element.GetBoolean(); + } + + /// + /// Get an enumerator to enumerate the values in the JSON array represented by this MutableJsonElement. + /// + /// + public ArrayEnumerator EnumerateArray() + { + EnsureValid(); + + EnsureArray(); + + return new ArrayEnumerator(this); + } + + /// + /// Set the value of the property with the specified name to the passed-in value. If the property is not already present, it will be created. + /// + /// + /// + public MutableJsonElement SetProperty(string name, object value) + { + if (TryGetProperty(name, out MutableJsonElement element)) + { + element.Set(value); + return this; + } + +#if !NET6_0_OR_GREATER + // Earlier versions of JsonSerializer.Serialize include "RootElement" + // as a property when called on JsonDocument. + if (value is JsonDocument doc) + { + value = doc.RootElement; + } +#endif + + // If it's not already there, we'll add a change to this element's JsonElement instead. + Dictionary dict = JsonSerializer.Deserialize>(GetRawBytes())!; + dict[name] = value; + + byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(dict); + JsonElement newElement = JsonDocument.Parse(bytes).RootElement; + + int index = Changes.AddChange(_path, newElement, true); + + // Make sure the object reference is stored to ensure reference semantics + string path = MutableJsonDocument.ChangeTracker.PushProperty(_path, name); + Changes.AddChange(path, value, true); + + // Element has changed, return the new valid one. + return new MutableJsonElement(_root, newElement, _path, index); + } + + /// + /// Remove the property with the specified name from the current MutableJsonElement. + /// + /// + /// + public void RemoveProperty(string name) + { + EnsureValid(); + + EnsureObject(); + + if (!_element.TryGetProperty(name, out _)) + { + throw new InvalidOperationException($"Object does not have property: {name}."); + } + + Dictionary dict = JsonSerializer.Deserialize>(GetRawBytes())!; + dict.Remove(name); + + byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(dict); + JsonElement newElement = JsonDocument.Parse(bytes).RootElement; + + Changes.AddChange(_path, newElement, true); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(double value) + { + EnsureValid(); + + Changes.AddChange(_path, value, _element.ValueKind != JsonValueKind.Number); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(int value) + { + EnsureValid(); + + Changes.AddChange(_path, value, _element.ValueKind != JsonValueKind.Number); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(long value) + { + EnsureValid(); + + Changes.AddChange(_path, value, _element.ValueKind != JsonValueKind.Number); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(float value) + { + EnsureValid(); + + Changes.AddChange(_path, value, _element.ValueKind != JsonValueKind.Number); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(string value) + { + EnsureValid(); + + Changes.AddChange(_path, value, _element.ValueKind != JsonValueKind.String); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(bool value) + { + EnsureValid(); + + Changes.AddChange(_path, value, + !(_element.ValueKind == JsonValueKind.True || + _element.ValueKind == JsonValueKind.False)); + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(object value) + { + EnsureValid(); + + switch (value) + { + case int i: + Set(i); + break; + case double d: + Set(d); + break; + case string s: + Set(s); + break; + case bool b: + Set(b); + break; + case long l: + Set(l); + break; + case float f: + Set(f); + break; + case MutableJsonElement e: + Set(e); + break; + case MutableJsonDocument d: + Set(d.RootElement); + break; + case JsonDocument d: + Set(d.RootElement); + break; + default: + Changes.AddChange(_path, value, true); + break; + } + } + + /// + /// Sets the value of this element to the passed-in value. + /// + /// + public void Set(MutableJsonElement value) + { + EnsureValid(); + + value.EnsureValid(); + + JsonElement element = value._element; + + if (Changes.TryGetChange(value._path, value._highWaterMark, out MutableJsonChange change)) + { + if (change.ReplacesJsonElement) + { + element = change.AsJsonElement(); + } + } + + Changes.AddChange(_path, element, true); + } + + internal void WriteTo(Utf8JsonWriter writer) + { + Utf8JsonReader reader = GetReaderForElement(_element); + _root.WriteElement(_path, _highWaterMark, ref reader, writer); + } + + /// + public override string ToString() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + if (change.Value == null) + return "null"; + + return change.Value.ToString()!; + } + + // Account for changes to descendants of this element as well + if (Changes.DescendantChanged(_path, _highWaterMark)) + { + return Encoding.UTF8.GetString(GetRawBytes()); + } + + return _element.ToString(); + } + + internal JsonElement GetJsonElement() + { + EnsureValid(); + + if (Changes.TryGetChange(_path, _highWaterMark, out MutableJsonChange change)) + { + return change.AsJsonElement(); + } + + // Account for changes to descendants of this element as well + if (Changes.DescendantChanged(_path, _highWaterMark)) + { + JsonDocument document = JsonDocument.Parse(GetRawBytes()); + return document.RootElement; + } + + return _element; + } + + private byte[] GetRawBytes() + { + Utf8JsonReader reader = GetReaderForElement(_element); + + using MemoryStream changedElementStream = new(); + Utf8JsonWriter changedElementWriter = new(changedElementStream); + _root.WriteElement(_path, _highWaterMark, ref reader, changedElementWriter); + changedElementWriter.Flush(); + + return changedElementStream.ToArray(); + } + + internal static Utf8JsonReader GetReaderForElement(JsonElement element) + { + using MemoryStream stream = new(); + Utf8JsonWriter writer = new(stream); + element.WriteTo(writer); + writer.Flush(); + return new Utf8JsonReader(stream.GetBuffer().AsSpan().Slice(0, (int)stream.Position)); + } + + private void EnsureObject() + { + if (_element.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"Expected an 'Object' type but was {_element.ValueKind}."); + } + } + + private void EnsureArray() + { + if (_element.ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException($"Expected an 'Array' type but was {_element.ValueKind}."); + } + } + + private void EnsureValid() + { + if (Changes.AncestorChanged(_path, _highWaterMark)) + { + throw new InvalidOperationException("An ancestor node of this element has unapplied changes. Please re-request this property from the RootElement."); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs b/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs index a0f33f09c81f9..6fad36ddded16 100644 --- a/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs +++ b/sdk/core/Azure.Core.Experimental/src/Properties/AssemblyInfo.cs @@ -5,5 +5,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Azure.Core.Experimental.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] +[assembly: InternalsVisibleTo("Azure.Core.Experimental.Perf.Benchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] [assembly: SuppressMessage("Usage", "AZC0014:Avoid using banned types in public API", Justification = "Azure.Core.Experimental ships adapters for System.Text.Json", Scope = "type", Target = "~T:Azure.Core.Dynamic.DynamicData")] diff --git a/sdk/core/Azure.Core.Experimental/tests/DynamicJsonEnumerableTests.cs b/sdk/core/Azure.Core.Experimental/tests/DynamicJsonEnumerableTests.cs new file mode 100644 index 0000000000000..70ee1b8bf7393 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/DynamicJsonEnumerableTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Azure.Core.Experimental.Tests +{ + internal class DynamicJsonEnumerableTests + { + [Test] + public void CanConvertToIntEnumerable() + { + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); + + IEnumerable enumerable = (IEnumerable)jsonData; + int expected = 0; + foreach (int i in enumerable) + { + Assert.AreEqual(expected++, i); + } + } + + [Test] + [Ignore("BinaryOperation fails to bind in net461.")] + public void CanConvertToIntEnumerableWithChanges_AddAssign() + { + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); + + for (int i = 0; i < 4; i++) + { + jsonData[i] += 1; + } + + IEnumerable enumerable = (IEnumerable)jsonData; + int expected = 1; + foreach (int i in enumerable) + { + Assert.AreEqual(expected++, i); + } + } + + [Test] + public void CanConvertToIntEnumerableWithChanges() + { + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); + + for (int i = 0; i < 4; i++) + { + jsonData[i] = i + 1; + } + + IEnumerable enumerable = (IEnumerable)jsonData; + int expected = 1; + foreach (int i in enumerable) + { + Assert.AreEqual(expected++, i); + } + } + + [Test] + [Ignore("Needs further investigation.")] + public void CanConvertToEnumerable() + { + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); + + IEnumerable enumerable = (IEnumerable)jsonData; + + int expected = 0; + foreach (int i in enumerable) + { + Assert.AreEqual(expected++, i); + } + } + + [Test] + public void CanForEachOverIntArray() + { + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); + int expected = 0; + foreach (int i in jsonData) + { + Assert.AreEqual(expected++, i); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/DynamicJsonTests.cs b/sdk/core/Azure.Core.Experimental/tests/DynamicJsonTests.cs new file mode 100644 index 0000000000000..c97cfef5d3a43 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/DynamicJsonTests.cs @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Dynamic; +using NUnit.Framework; + +namespace Azure.Core.Experimental.Tests +{ + public class DynamicJsonTests + { + [Test] + public void CanGetIntProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + int value = jsonData.Foo; + + Assert.AreEqual(1, value); + } + + [Test] + public void CanGetNestedIntProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : { + ""Bar"" : 1 + } + }"); + + int value = jsonData.Foo.Bar; + + Assert.AreEqual(1, value); + } + + [Test] + public void CanSetIntProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + jsonData.Foo = 2; + + Assert.AreEqual(2, (int)jsonData.Foo); + } + + [Test] + public void CanSetNestedIntProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : { + ""Bar"" : 1 + } + }"); + + jsonData.Foo.Bar = 2; + + Assert.AreEqual(2, (int)jsonData.Foo.Bar); + } + + [Test] + public void CanGetArrayValue() + { + dynamic jsonData = GetDynamicJson(@"[0, 1, 2]"); + + Assert.AreEqual(0, (int)jsonData[0]); + Assert.AreEqual(1, (int)jsonData[1]); + Assert.AreEqual(2, (int)jsonData[2]); + } + + [Test] + public void CanGetNestedArrayValue() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"": [0, 1, 2] + }"); + + Assert.AreEqual(0, (int)jsonData.Foo[0]); + Assert.AreEqual(1, (int)jsonData.Foo[1]); + Assert.AreEqual(2, (int)jsonData.Foo[2]); + } + + [Test] + public void CanGetPropertyViaIndexer() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + Assert.AreEqual(1, (int)jsonData["Foo"]); + } + + [Test] + public void CanGetNestedPropertyViaIndexer() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : { + ""Bar"" : 1 + } + }"); + + Assert.AreEqual(1, (int)jsonData.Foo["Bar"]); + } + + [Test] + public void CanSetArrayValues() + { + dynamic jsonData = GetDynamicJson(@"[0, 1, 2]"); + + jsonData[0] = 4; + jsonData[1] = 5; + jsonData[2] = 6; + + Assert.AreEqual(4, (int)jsonData[0]); + Assert.AreEqual(5, (int)jsonData[1]); + Assert.AreEqual(6, (int)jsonData[2]); + } + + [Test] + public void CanSetNestedArrayValues() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"": [0, 1, 2] + }"); + + jsonData.Foo[0] = 4; + jsonData.Foo[1] = 5; + jsonData.Foo[2] = 6; + + Assert.AreEqual(4, (int)jsonData.Foo[0]); + Assert.AreEqual(5, (int)jsonData.Foo[1]); + Assert.AreEqual(6, (int)jsonData.Foo[2]); + } + + [Test] + public void CanSetArrayValuesToDifferentTypes() + { + dynamic jsonData = GetDynamicJson(@"[0, 1, 2, 3]"); + + jsonData[1] = 4; + jsonData[2] = true; + jsonData[3] = "string"; + + Assert.AreEqual(0, (int)jsonData[0]); + Assert.AreEqual(4, (int)jsonData[1]); + Assert.AreEqual(true, (bool)jsonData[2]); + Assert.AreEqual("string", (string)jsonData[3]); + } + + [Test] + public void CanSetNestedArrayValuesToDifferentTypes() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"": [0, 1, 2, 3] + }"); + + jsonData.Foo[1] = 4; + jsonData.Foo[2] = true; + jsonData.Foo[3] = "string"; + + Assert.AreEqual(0, (int)jsonData.Foo[0]); + Assert.AreEqual(4, (int)jsonData.Foo[1]); + Assert.AreEqual(true, (bool)jsonData.Foo[2]); + Assert.AreEqual("string", (string)jsonData.Foo[3]); + } + + [Test] + public void CanGetNullPropertyValue() + { + dynamic jsonData = GetDynamicJson(@"{ ""Foo"" : null }"); + + Assert.IsNull((CustomType)jsonData.Foo); + Assert.IsNull((int?)jsonData.Foo); + } + + [Test] + public void CanGetNullArrayValue() + { + dynamic jsonData = GetDynamicJson(@"[ null ]"); + + Assert.IsNull((CustomType)jsonData[0]); + Assert.IsNull((int?)jsonData[0]); + } + + [Test] + public void CanSetPropertyValueToNull() + { + dynamic jsonData = GetDynamicJson(@"{ ""Foo"" : null }"); + + jsonData.Foo = null; + + Assert.IsNull((CustomType)jsonData.Foo); + Assert.IsNull((int?)jsonData.Foo); + } + + [Test] + public void CanSetArrayValueToNull() + { + dynamic jsonData = GetDynamicJson(@"[0]"); + + jsonData[0] = null; + + Assert.IsNull((CustomType)jsonData[0]); + Assert.IsNull((int?)jsonData[0]); + } + + [Test] + public void CanSetPropertyViaIndexer() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + jsonData["Foo"] = 4; + + Assert.AreEqual(4, (int)jsonData["Foo"]); + } + + [Test] + public void CanSetNestedPropertyViaIndexer() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : { + ""Bar"" : 1 + } + }"); + + jsonData["Foo"]["Bar"] = 4; + + Assert.AreEqual(4, (int)jsonData.Foo["Bar"]); + } + + [Test] + public void CanAddNewProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + jsonData.Bar = 2; + + Assert.AreEqual(1, (int)jsonData.Foo); + Assert.AreEqual(2, (int)jsonData.Bar); + } + + [Test] + public void CanMakeChangesAndAddNewProperty() + { + dynamic jsonData = GetDynamicJson(@" + { + ""Foo"" : 1 + }"); + + Assert.AreEqual(1, (int)jsonData.Foo); + + jsonData.Foo = "hi"; + + Assert.AreEqual("hi", (string)jsonData.Foo); + + jsonData.Bar = 2; + + Assert.AreEqual("hi", (string)jsonData.Foo); + Assert.AreEqual(2, (int)jsonData.Bar); + } + + #region Helpers + internal static dynamic GetDynamicJson(string json) + { + return new BinaryData(json).ToDynamic(); + } + + internal class CustomType + { + } +#endregion + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs index eb977062bc874..932e1e2fd5dc9 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataDynamicMutableTests.cs @@ -6,46 +6,46 @@ using Azure.Core.GeoJson; using NUnit.Framework; -namespace Azure.Core.Tests +namespace Azure.Core.Experimental.Tests { public class JsonDataDynamicMutableTests { [Test] public void ArrayItemsCanBeAssigned() { - var json = JsonData.Parse("[0, 1, 2, 3]"); - dynamic jsonData = json; - jsonData[1] = 2; - jsonData[2] = null; - jsonData[3] = "string"; + dynamic json = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); - Assert.AreEqual(jsonData.ToString(), "[0,2,null,\"string\"]"); + json[1] = 2; + json[2] = null; + json[3] = "string"; + + Assert.AreEqual("[0,2,null,\"string\"]", json.ToString()); } [Test] public void ExistingObjectPropertiesCanBeAssigned() { - var json = JsonData.Parse("{\"a\":1}"); - dynamic jsonData = json; - jsonData.a = "2"; + dynamic json = DynamicJsonTests.GetDynamicJson("{\"a\":1}"); + + json.a = "2"; - Assert.AreEqual(json.ToString(), "{\"a\":\"2\"}"); + Assert.AreEqual("{\"a\":\"2\"}", json.ToString()); } [TestCaseSource(nameof(PrimitiveValues))] public void NewObjectPropertiesCanBeAssignedWithPrimitive(T value, string expected) { - var json = JsonData.Parse("{}"); - dynamic jsonData = json; - jsonData.a = value; + dynamic json = DynamicJsonTests.GetDynamicJson("{}"); - Assert.AreEqual(json.ToString(), "{\"a\":" + expected + "}"); + json.a = value; + + Assert.AreEqual("{\"a\":" + expected + "}", json.ToString()); } [TestCaseSource(nameof(PrimitiveValues))] public void PrimitiveValuesCanBeParsedDirectly(T value, string expected) { - dynamic json = JsonData.Parse(expected); + dynamic json = DynamicJsonTests.GetDynamicJson(expected); Assert.AreEqual(value, (T)json); } @@ -53,42 +53,42 @@ public void PrimitiveValuesCanBeParsedDirectly(T value, string expected) [Test] public void NewObjectPropertiesCanBeAssignedWithArrays() { - var json = JsonData.Parse("{}"); - dynamic jsonData = json; - jsonData.a = new JsonData(new object[] { 1, 2, null, "string" }); + dynamic json = DynamicJsonTests.GetDynamicJson("{}"); + + json.a = new MutableJsonDocument(new object[] { 1, 2, null, "string" }); - Assert.AreEqual(json.ToString(), "{\"a\":[1,2,null,\"string\"]}"); + Assert.AreEqual("{\"a\":[1,2,null,\"string\"]}", json.ToString()); } [Test] public void NewObjectPropertiesCanBeAssignedWithObject() { - var json = JsonData.Parse("{}"); - dynamic jsonData = json; - jsonData.a = JsonData.Parse("{}"); - jsonData.a.b = 2; + dynamic json = DynamicJsonTests.GetDynamicJson("{}"); - Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); + json.a = DynamicJsonTests.GetDynamicJson("{}"); + json.a.b = 2; + + Assert.AreEqual("{\"a\":{\"b\":2}}", json.ToString()); } [Test] public void NewObjectPropertiesCanBeAssignedWithObjectIndirectly() { - var json = JsonData.Parse("{}"); - dynamic jsonData = json; - dynamic anotherJson = JsonData.Parse("{}"); - jsonData.a = anotherJson; + dynamic json = DynamicJsonTests.GetDynamicJson("{}"); + dynamic anotherJson = DynamicJsonTests.GetDynamicJson("{}"); + + json.a = anotherJson; anotherJson.b = 2; - Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":2}}"); + Assert.AreEqual("{\"a\":{\"b\":2}}", json.ToString()); } [Test] public void NewObjectPropertiesCanBeAssignedWithSerializedObject() { - var json = JsonData.Parse("{}"); - dynamic jsonData = json; - jsonData.a = new JsonData(new GeoPoint(1, 2)); + dynamic json = DynamicJsonTests.GetDynamicJson("{}"); + + json.a = new MutableJsonDocument(new GeoPoint(1, 2)); Assert.AreEqual("{\"a\":{\"type\":\"Point\",\"coordinates\":[1,2]}}", json.ToString()); } @@ -96,14 +96,14 @@ public void NewObjectPropertiesCanBeAssignedWithSerializedObject() [TestCaseSource(nameof(PrimitiveValues))] public void CanModifyNestedProperties(T value, string expected) { - var json = JsonData.Parse("{\"a\":{\"b\":2}}"); - dynamic jsonData = json; - jsonData.a.b = value; + dynamic json = DynamicJsonTests.GetDynamicJson("{\"a\":{\"b\":2}}"); + + json.a.b = value; - Assert.AreEqual(json.ToString(), "{\"a\":{\"b\":" + expected + "}}"); - Assert.AreEqual(value, (T)jsonData.a.b); + Assert.AreEqual("{\"a\":{\"b\":" + expected + "}}", json.ToString()); + Assert.AreEqual(value, (T)json.a.b); - dynamic reparsedJson = JsonData.Parse(json.ToString()); + dynamic reparsedJson = DynamicJsonTests.GetDynamicJson(json.ToString()); Assert.AreEqual(value, (T)reparsedJson.a.b); } @@ -116,10 +116,10 @@ public static IEnumerable PrimitiveValues() 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"}; + yield return new object[] {1.1F, "1.1"}; #else yield return new object[] {1.1D, "1.1000000000000001"}; - yield return new object[] {1.1F, "1.1000000238418579"}; + yield return new object[] {1.1F, "1.10000002" }; #endif yield return new object[] {true, "true"}; yield return new object[] {false, "false"}; diff --git a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs index de0dd47650c33..31539583b8797 100644 --- a/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/JsonDataTests.cs @@ -2,29 +2,28 @@ // Licensed under the MIT License. using System; -using System.Linq; using System.Text.Json; using Azure.Core.Dynamic; using NUnit.Framework; -namespace Azure.Core.Tests +namespace Azure.Core.Experimental.Tests { public class JsonDataTests { - [Test] - public void CanCreateFromJson() - { - var jsonData = JsonData.Parse("\"string\""); + //[Test] + //public void CanCreateFromJson() + //{ + // var jsonData = DynamicJsonTests.GetDynamicJson("\"string\""); - Assert.AreEqual("\"string\"", jsonData.ToJsonString()); - } + // Assert.AreEqual("\"string\"", jsonData.ToJsonString()); + //} - [Test] - public void CanCreateFromNull() - { - var jsonData = new JsonData(null); - Assert.AreEqual(JsonValueKind.Null, jsonData.Kind); - } + //[Test] + //public void CanCreateFromNull() + //{ + // var jsonData = new JsonData(null); + // Assert.AreEqual(JsonValueKind.Null, jsonData.Kind); + //} [Test] public void DynamicCanConvertToString() => Assert.AreEqual("string", JsonAsType("\"string\"")); @@ -53,7 +52,7 @@ public void CanCreateFromNull() [Test] public void DynamicCanConvertToIEnumerableDynamic() { - dynamic jsonData = JsonData.Parse("[1, null, \"s\"]"); + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[1, null, \"s\"]"); int i = 0; foreach (var dynamicItem in jsonData) { @@ -81,7 +80,7 @@ public void DynamicCanConvertToIEnumerableDynamic() [Test] public void DynamicCanConvertToIEnumerableInt() { - dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); + dynamic jsonData = DynamicJsonTests.GetDynamicJson("[0, 1, 2, 3]"); int i = 0; foreach (int dynamicItem in jsonData) { @@ -92,209 +91,198 @@ public void DynamicCanConvertToIEnumerableInt() Assert.AreEqual(4, i); } - [Test] - public void DynamicArrayHasLength() - { - dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); - Assert.AreEqual(4, jsonData.Length); - } - - [Test] - public void DynamicArrayFor() - { - dynamic jsonData = JsonData.Parse("[0, 1, 2, 3]"); - for (int i = 0; i < jsonData.Length; i++) - { - Assert.AreEqual(i, (int)jsonData[i]); - } - } - [Test] public void CanAccessProperties() { - dynamic jsonData = JsonData.Parse("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }"); + dynamic jsonData = DynamicJsonTests.GetDynamicJson(@" + { + ""primitive"" : ""Hello"", + ""nested"" : { + ""nestedPrimitive"": true + } + }"); Assert.AreEqual("Hello", (string)jsonData.primitive); Assert.AreEqual(true, (bool)jsonData.nested.nestedPrimitive); } - [Test] - public void CanReadIntsAsFloatingPoints() - { - var json = JsonData.Parse("5"); - 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 = JsonData.Parse("5.5"); - 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 = JsonData.Parse("34028234663852885981170418348451692544000"); - 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 = JsonData.Parse("-34028234663852885981170418348451692544000"); - 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 = JsonData.Parse("3402823466385288598"); - 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 = JsonData.Parse("-3402823466385288598"); - 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 = JsonData.Parse("[1,3]"); - dynamic jsonData = json; - Assert.Throws(() => _ = (int)json); - Assert.Throws(() => _ = (int)jsonData); - } - - [Test] - public void RoundtripObjects() - { - var model = new SampleModel("Hello World", 5); - var roundtripped = new JsonData(model).To(); - - Assert.AreEqual(model, roundtripped); - } - - [Test] - public void EqualsProvidesValueEqualityPrimitives() - { - Assert.AreEqual(new JsonData(1), new JsonData(1)); - Assert.AreEqual(new JsonData(true), new JsonData(true)); - Assert.AreEqual(new JsonData(false), new JsonData(false)); - Assert.AreEqual(new JsonData("hello"), new JsonData("hello")); - Assert.AreEqual(new JsonData(null), new JsonData(null)); - } - - [Test] - public void EqualsHandlesStringsSpecial() - { - Assert.IsTrue((new JsonData("test").Equals("test"))); - Assert.IsTrue((new JsonData("test").Equals(new JsonData("test")))); - } - - [Test] - public void EqualsForObjectsAndArrays() - { - JsonData obj1 = new JsonData(new { foo = "bar" }); - JsonData obj2 = new JsonData(new { foo = "bar" }); - - JsonData arr1 = new JsonData(new[] { "bar" }); - JsonData arr2 = new JsonData(new[] { "bar" }); - - // 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 EqualsAndNull() - { - Assert.AreNotEqual(new JsonData(null), null); - Assert.AreNotEqual(null, new JsonData(null)); - } - - [Test] - public void OperatorEqualsForString() - { - Assert.IsTrue(new JsonData("foo") == "foo"); - Assert.IsTrue("foo" == new JsonData("foo")); - Assert.IsFalse(new JsonData("foo") != "foo"); - Assert.IsFalse("foo" != new JsonData("foo")); - - Assert.IsFalse(new JsonData("bar") == "foo"); - Assert.IsFalse("foo" == new JsonData("bar")); - Assert.IsTrue(new JsonData("bar") != "foo"); - Assert.IsTrue("foo" != new JsonData("bar")); - } - - [Test] - public void JsonDataInPOCOsWorks() - { - JsonData orig = new JsonData(new - { - property = new JsonData("hello") - }); - - void validate(JsonData d) - { - Assert.AreEqual(JsonValueKind.Object, d.Kind); - Assert.AreEqual(d.Properties.Count(), 1); - Assert.AreEqual(d.Get("property"), "hello"); - } - - validate(orig); - - JsonData roundTrip = JsonSerializer.Deserialize(JsonSerializer.Serialize(orig, orig.GetType())); - - validate(roundTrip); - } + //[Test] + //public void CanReadIntsAsFloatingPoints() + //{ + // var json = JsonData.Parse("5"); + // 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 = JsonData.Parse("5.5"); + // 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 = JsonData.Parse("34028234663852885981170418348451692544000"); + // 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 = JsonData.Parse("-34028234663852885981170418348451692544000"); + // 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 = JsonData.Parse("3402823466385288598"); + // 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 = JsonData.Parse("-3402823466385288598"); + // 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 = JsonData.Parse("[1,3]"); + // dynamic jsonData = json; + // Assert.Throws(() => _ = (int)json); + // Assert.Throws(() => _ = (int)jsonData); + //} + + //[Test] + //public void RoundtripObjects() + //{ + // var model = new SampleModel("Hello World", 5); + // var roundtripped = new JsonData(model).To(); + + // Assert.AreEqual(model, roundtripped); + //} + + //[Test] + //public void EqualsProvidesValueEqualityPrimitives() + //{ + // Assert.AreEqual(new JsonData(1), new JsonData(1)); + // Assert.AreEqual(new JsonData(true), new JsonData(true)); + // Assert.AreEqual(new JsonData(false), new JsonData(false)); + // Assert.AreEqual(new JsonData("hello"), new JsonData("hello")); + // Assert.AreEqual(new JsonData(null), new JsonData(null)); + //} + + //[Test] + //public void EqualsHandlesStringsSpecial() + //{ + // Assert.IsTrue((new JsonData("test").Equals("test"))); + // Assert.IsTrue((new JsonData("test").Equals(new JsonData("test")))); + //} + + //[Test] + //public void EqualsForObjectsAndArrays() + //{ + // JsonData obj1 = new JsonData(new { foo = "bar" }); + // JsonData obj2 = new JsonData(new { foo = "bar" }); + + // JsonData arr1 = new JsonData(new[] { "bar" }); + // JsonData arr2 = new JsonData(new[] { "bar" }); + + // // 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 EqualsAndNull() + //{ + // Assert.AreNotEqual(new JsonData(null), null); + // Assert.AreNotEqual(null, new JsonData(null)); + //} + + //[Test] + //public void OperatorEqualsForString() + //{ + // Assert.IsTrue(new JsonData("foo") == "foo"); + // Assert.IsTrue("foo" == new JsonData("foo")); + // Assert.IsFalse(new JsonData("foo") != "foo"); + // Assert.IsFalse("foo" != new JsonData("foo")); + + // Assert.IsFalse(new JsonData("bar") == "foo"); + // Assert.IsFalse("foo" == new JsonData("bar")); + // Assert.IsTrue(new JsonData("bar") != "foo"); + // Assert.IsTrue("foo" != new JsonData("bar")); + //} + + ////[Test] + ////public void JsonDataInPOCOsWorks() + ////{ + //// JsonData orig = new JsonData(new + //// { + //// property = new JsonData("hello") + //// }); + + //// void validate(JsonData d) + //// { + //// Assert.AreEqual(JsonValueKind.Object, d.Kind); + //// Assert.AreEqual(d.Properties.Count(), 1); + //// Assert.AreEqual(d.Get("property"), "hello"); + //// } + + //// validate(orig); + + //// JsonData roundTrip = JsonSerializer.Deserialize(JsonSerializer.Serialize(orig, orig.GetType())); + + //// validate(roundTrip); + ////} private T JsonAsType(string json) { - dynamic jsonData = JsonData.Parse(json); + dynamic jsonData = DynamicJsonTests.GetDynamicJson(json); return (T) jsonData; } diff --git a/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentTests.cs b/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentTests.cs new file mode 100644 index 0000000000000..e8ecad83c6c53 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentTests.cs @@ -0,0 +1,974 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text.Json; +using Azure.Core.Dynamic; +using NUnit.Framework; + +namespace Azure.Core.Experimental.Tests +{ + internal class MutableJsonDocumentTests + { + [Test] + public void CanGetProperty() + { + string json = @" + { + ""Baz"" : { + ""A"" : 3.0 + }, + ""Foo"" : 1.2, + ""Bar"" : ""Hi!"", + ""Qux"" : false + }"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetDouble()); + Assert.AreEqual("Hi!", jd.RootElement.GetProperty("Bar").GetString()); + Assert.AreEqual(3.0, jd.RootElement.GetProperty("Baz").GetProperty("A").GetDouble()); + Assert.AreEqual(false, jd.RootElement.GetProperty("Qux").GetBoolean()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(json), + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanSetProperty() + { + string json = @" + { + ""Baz"" : { + ""A"" : 3 + }, + ""Foo"" : 1, + ""Bar"" : ""Hi!"", + ""Qux"" : false + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").Set(2); + jd.RootElement.GetProperty("Bar").Set("Hello"); + jd.RootElement.GetProperty("Baz").GetProperty("A").Set(5); + jd.RootElement.GetProperty("Qux").Set(true); + + Assert.AreEqual(2, jd.RootElement.GetProperty("Foo").GetInt32()); + Assert.AreEqual("Hello", jd.RootElement.GetProperty("Bar").GetString()); + Assert.AreEqual(5, jd.RootElement.GetProperty("Baz").GetProperty("A").GetInt32()); + Assert.AreEqual(true, jd.RootElement.GetProperty("Qux").GetBoolean()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Baz"" : { + ""A"" : 5 + }, + ""Foo"" : 2, + ""Bar"" : ""Hello"", + ""Qux"" : true + }"), + jsonString); + } + + [Test] + public void CanSetPropertyMultipleTimes() + { + string json = @" + { + ""Baz"" : { + ""A"" : 3 + }, + ""Foo"" : 1, + ""Bar"" : ""Hi!"" + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").Set(2); + jd.RootElement.GetProperty("Foo").Set(3); + + // Last write wins + Assert.AreEqual(3, jd.RootElement.GetProperty("Foo").GetInt32()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Baz"" : { + ""A"" : 3 + }, + ""Foo"" : 3, + ""Bar"" : ""Hi!"" + }"), + jsonString); + } + + [Test] + public void CanSetPropertyToMutableJsonDocument() + { + string json = @" + { + ""Foo"" : ""Hello"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + MutableJsonDocument anotherMDoc = MutableJsonDocument.Parse(@"{ ""Baz"": ""hi"" }"); + + mdoc.RootElement.GetProperty("Foo").Set(anotherMDoc); + + Assert.AreEqual("hi", mdoc.RootElement.GetProperty("Foo").GetProperty("Baz").GetString()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : { + ""Baz"" : ""hi"" + } + }"), + jsonString); + } + + [Test] + public void CanSetPropertyToJsonDocument() + { + string json = @" + { + ""Foo"" : ""Hello"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + JsonDocument doc = JsonDocument.Parse(@"{ ""Baz"": ""hi"" }"); + + mdoc.RootElement.GetProperty("Foo").Set(doc); + + Assert.AreEqual("hi", mdoc.RootElement.GetProperty("Foo").GetProperty("Baz").GetString()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : { + ""Baz"" : ""hi"" + } + }"), + jsonString); + } + + [Test] + public void CanAddPropertyToRootObject() + { + string json = @" + { + ""Foo"" : 1.2 + }"; + + var jd = MutableJsonDocument.Parse(json); + + // Has same semantics as Dictionary + // https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.item?view=net-7.0#property-value + jd.RootElement.SetProperty("Bar", "hi"); + + // Assert: + + // 1. Old property is present. + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetDouble()); + + // 2. New property is present. + Assert.IsNotNull(jd.RootElement.GetProperty("Bar")); + Assert.AreEqual("hi", jd.RootElement.GetProperty("Bar").GetString()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1.2, doc.RootElement.GetProperty("Foo").GetDouble()); + Assert.AreEqual("hi", doc.RootElement.GetProperty("Bar").GetString()); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : 1.2, + ""Bar"" : ""hi"" + }"), jsonString); + } + + [Test] + public void CanAddPropertyToObject() + { + string json = @" + { + ""Foo"" : { + ""A"": 1.2 + } + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").SetProperty("B", "hi"); + + // Assert: + + // 1. Old property is present. + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetProperty("A").GetDouble()); + + // 2. New property is present. + Assert.IsNotNull(jd.RootElement.GetProperty("Foo").GetProperty("B")); + Assert.AreEqual("hi", jd.RootElement.GetProperty("Foo").GetProperty("B").GetString()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1.2, doc.RootElement.GetProperty("Foo").GetProperty("A").GetDouble()); + Assert.AreEqual("hi", doc.RootElement.GetProperty("Foo").GetProperty("B").GetString()); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : { + ""A"": 1.2, + ""B"": ""hi"" + } + }"), jsonString); + } + + [Test] + public void CanAddMutableJsonDocumentProperty() + { + string json = @" + { + ""Foo"" : ""Hello"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + MutableJsonDocument anotherMDoc = MutableJsonDocument.Parse(@"{ ""Baz"": ""hi"" }"); + + mdoc.RootElement.SetProperty("A", anotherMDoc); + + Assert.AreEqual("hi", mdoc.RootElement.GetProperty("A").GetProperty("Baz").GetString()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : ""Hello"", + ""A"" : { + ""Baz"" : ""hi"" + } + }"), + jsonString); + } + + [Test] + public void CanAddJsonDocumentProperty() + { + string json = @" + { + ""Foo"" : ""Hello"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + JsonDocument doc = JsonDocument.Parse(@"{ ""Baz"": ""hi"" }"); + + mdoc.RootElement.SetProperty("A", doc); + + Assert.AreEqual("hi", mdoc.RootElement.GetProperty("A").GetProperty("Baz").GetString()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : ""Hello"", + ""A"" : { + ""Baz"" : ""hi"" + } + }"), + jsonString); + } + + [Test] + public void CanRemovePropertyFromRootObject() + { + string json = @" + { + ""Foo"" : 1.2, + ""Bar"" : ""Hi!"" + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.RemoveProperty("Bar"); + + // Assert: + + // 1. Old property is present. + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetDouble()); + + // 2. New property not present. + Assert.IsFalse(jd.RootElement.TryGetProperty("Bar", out var _)); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1.2, doc.RootElement.GetProperty("Foo").GetDouble()); + Assert.IsFalse(doc.RootElement.TryGetProperty("Bar", out JsonElement _)); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : 1.2 + }"), jsonString); + } + + [Test] + public void CanRemovePropertyFromObject() + { + string json = @" + { + ""Foo"" : { + ""A"": 1.2, + ""B"": ""hi"" + } + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").RemoveProperty("B"); + + // Assert: + + // 1. Old property is present. + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetProperty("A").GetDouble()); + + // 2. New property is absent. + Assert.IsFalse(jd.RootElement.GetProperty("Foo").TryGetProperty("B", out var _)); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1.2, doc.RootElement.GetProperty("Foo").GetProperty("A").GetDouble()); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : { + ""A"": 1.2 + } + }"), jsonString); + } + + [Test] + public void CanReplaceObjectWithAnonymousType() + { + string json = @" + { + ""Baz"" : { + ""A"" : 3.0 + }, + ""Foo"" : 1.2 + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Baz").Set(new { B = 5.5 }); + + // Assert: + + // 1. Old property is present. + Assert.AreEqual(1.2, jd.RootElement.GetProperty("Foo").GetDouble()); + + // 2. Object structure has been rewritten + Assert.IsFalse(jd.RootElement.GetProperty("Baz").TryGetProperty("A", out var _)); + Assert.AreEqual(5.5, jd.RootElement.GetProperty("Baz").GetProperty("B").GetDouble()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + BazB baz = JsonSerializer.Deserialize(jsonString); + Assert.AreEqual(1.2, baz.Foo); + Assert.AreEqual(5.5, baz.Baz.B); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Baz"" : { + ""B"" : 5.5 + }, + ""Foo"" : 1.2 + }"), jsonString); + } + + private class BazA + { + public double Foo { get; set; } + public A_ Baz { get; set; } + } + + private class BazB + { + public double Foo { get; set; } + public B_ Baz { get; set; } + } + + private class A_ + { + public double A { get; set; } + } + + private class B_ + { + public double B { get; set; } + } + + [Test] + public void CanGetArrayElement() + { + string json = @" + { + ""Foo"" : [ 1, 2, 3 ] + }"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual(1, jd.RootElement.GetProperty("Foo").GetIndexElement(0).GetInt32()); + Assert.AreEqual(2, jd.RootElement.GetProperty("Foo").GetIndexElement(1).GetInt32()); + Assert.AreEqual(3, jd.RootElement.GetProperty("Foo").GetIndexElement(2).GetInt32()); + } + + [Test] + public void CanSetArrayElement() + { + string json = @" + { + ""Foo"" : [ 1, 2, 3 ] + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").GetIndexElement(0).Set(5); + jd.RootElement.GetProperty("Foo").GetIndexElement(1).Set(6); + jd.RootElement.GetProperty("Foo").GetIndexElement(2).Set(7); + + Assert.AreEqual(5, jd.RootElement.GetProperty("Foo").GetIndexElement(0).GetInt32()); + Assert.AreEqual(6, jd.RootElement.GetProperty("Foo").GetIndexElement(1).GetInt32()); + Assert.AreEqual(7, jd.RootElement.GetProperty("Foo").GetIndexElement(2).GetInt32()); + } + + [Test] + public void CanSetArrayElement_WriteTo() + { + string json = @" + { + ""Foo"" : [ 1, 2, 3 ] + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").GetIndexElement(0).Set(5); + jd.RootElement.GetProperty("Foo").GetIndexElement(1).Set(6); + jd.RootElement.GetProperty("Foo").GetIndexElement(2).Set(7); + + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : [ 5, 6, 7 ] + }"), + jsonString); + } + + [Test] + public void CanSetArrayElementMultipleTimes() + { + string json = @" + { + ""Foo"" : [ 1, 2, 3 ] + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").GetIndexElement(0).Set(5); + jd.RootElement.GetProperty("Foo").GetIndexElement(0).Set(6); + + Assert.AreEqual(6, jd.RootElement.GetProperty("Foo").GetIndexElement(0).GetInt32()); + } + + [Test] + public void CanChangeArrayElementType() + { + string json = @" + { + ""Foo"" : [ 1, 2, 3 ] + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").GetIndexElement(0).Set(5); + jd.RootElement.GetProperty("Foo").GetIndexElement(1).Set("string"); + jd.RootElement.GetProperty("Foo").GetIndexElement(2).Set(true); + + Assert.AreEqual(5, jd.RootElement.GetProperty("Foo").GetIndexElement(0).GetInt32()); + Assert.AreEqual("string", jd.RootElement.GetProperty("Foo").GetIndexElement(1).GetString()); + Assert.AreEqual(true, jd.RootElement.GetProperty("Foo").GetIndexElement(2).GetBoolean()); + + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(5, doc.RootElement.GetProperty("Foo")[0].GetInt32()); + Assert.AreEqual("string", doc.RootElement.GetProperty("Foo")[1].GetString()); + Assert.AreEqual(true, doc.RootElement.GetProperty("Foo")[2].GetBoolean()); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Foo"" : [ 5, ""string"", true ] + }"), + jsonString); + } + + [Test] + public void ChangeToDocumentAppearsInElementReference() + { + // This tests reference semantics. + + string json = @"[ { ""Foo"" : 4 } ]"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + // a is a reference to the 0th element; a's path is "0" + MutableJsonElement a = mdoc.RootElement.GetIndexElement(0); + + // resets json to equivalent of "[ 5 ]" + mdoc.RootElement.GetIndexElement(0).Set(5); + + Assert.AreEqual(5, mdoc.RootElement.GetIndexElement(0).GetInt32()); + + // a's path is "0" so a.GetInt32() should return 5. + Assert.AreEqual(5, a.GetInt32()); + + // The following should throw because json[0] is now 5 and not an object. + Assert.Throws(() => a.GetProperty("Foo").Set(6)); + + Assert.AreEqual(5, mdoc.RootElement.GetIndexElement(0).GetInt32()); + + // Setting json[0] back to 'a' makes it 5 again. + mdoc.RootElement.GetIndexElement(0).Set(a); + + Assert.AreEqual(5, mdoc.RootElement.GetIndexElement(0).GetInt32()); + + // Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual(5, doc.RootElement[0].GetInt32()); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ 5 ]"), jsonString); + } + + [Test] + public void ChangeToChildDocumentAppearsInParentDocument() + { + // This tests reference semantics. + + MutableJsonDocument mdoc = MutableJsonDocument.Parse("{}"); + MutableJsonDocument child = MutableJsonDocument.Parse("{}"); + + mdoc.RootElement.SetProperty("A", child); + + child.RootElement.SetProperty("B", 2); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace( + @"{ ""A"" : { ""B"" : 2 } }"), + jsonString); + } + + [Test] + public void ChangeToAddedElementReferenceAppearsInDocument() + { + // This tests reference semantics. + + string json = @"[ { ""Foo"" : {} } ]"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + // a is a reference to the 0th element; a's path is "0" + MutableJsonElement a = mdoc.RootElement.GetIndexElement(0); + + a.GetProperty("Foo").SetProperty("Bar", 5); + + Assert.AreEqual(5, a.GetProperty("Foo").GetProperty("Bar").GetInt32()); + Assert.AreEqual(5, mdoc.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("Bar").GetInt32()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace( + @"[ { + ""Foo"" : { + ""Bar"" : 5 + } + } ]"), jsonString); + } + + [Test] + public void ChangeToElementReferenceAppearsInDocument() + { + // This tests reference semantics. + + string json = @"[ { ""Foo"" : 4 } ]"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + // a is a reference to the 0th element; a's path is "0" + MutableJsonElement a = mdoc.RootElement.GetIndexElement(0); + + a.GetProperty("Foo").Set(new + { + Bar = 5 + }); + + Assert.AreEqual(5, a.GetProperty("Foo").GetProperty("Bar").GetInt32()); + Assert.AreEqual(5, mdoc.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("Bar").GetInt32()); + + MutableJsonDocumentWriteToTests.WriteToAndParse(mdoc, out string jsonString); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace( + @"[ { + ""Foo"" : { + ""Bar"" : 5 + } + } ]"), jsonString); + } + + [Test] + public void CanInvalidateElement() + { + string json = @"[ + { + ""Foo"" : { + ""A"": 6 + } + } ]"; + + var jd = MutableJsonDocument.Parse(json); + + // a's path points to "0.Foo.A" + var a = jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("A"); + + // resets json to equivalent of "[ 5 ]" + jd.RootElement.GetIndexElement(0).Set(5); + + Assert.AreEqual(5, jd.RootElement.GetIndexElement(0).GetInt32()); + + // a's path points to "0.Foo.A" so a.GetInt32() should throw since this + // in an invalid path. + Assert.Throws(() => a.GetInt32()); + + // Setting json[0] to a should throw as well, as the element doesn't point + // to a valid path in the mutated JSON tree. + Assert.Throws(() => jd.RootElement.GetIndexElement(0).Set(a)); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(5, doc.RootElement[0].GetInt32()); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ 5 ]"), jsonString); + } + + [Test] + public void CanAccessPropertyInChangedStructure() + { + string json = @"[ + { + ""Foo"" : { + ""A"": 6 + } + } ]"; + + var jd = MutableJsonDocument.Parse(json); + + // a's path points to "0.Foo.A" + var a = jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("A"); + + // resets json to equivalent of "[ 5 ]" + jd.RootElement.GetIndexElement(0).Set(5); + + Assert.AreEqual(5, jd.RootElement.GetIndexElement(0).GetInt32()); + + // a's path points to "0.Foo.A" so a.GetInt32() should throw since this + // in an invalid path. + Assert.Throws(() => a.GetInt32()); + + // Setting json[0] to `a` should throw as well, as the element doesn't point + // to a valid path in the mutated JSON tree. + Assert.Throws(() => jd.RootElement.GetIndexElement(0).Set(a)); + + // Reset json[0] to an object + jd.RootElement.GetIndexElement(0).Set(new + { + Foo = new + { + A = 7 + } + }); + + // We should be able to get the value of A without being tripped up + // by earlier changes. + int aValue = jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("A").GetInt32(); + Assert.AreEqual(7, aValue); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ + { + ""Foo"" : { + ""A"": 7 + } + } ]"), jsonString); + } + + [Test] + public void CanAccessChangesInDifferentBranches() + { + string json = @"[ + { + ""Foo"" : { + ""A"": 6 + } + }, + { + ""Bar"" : ""hi"" + }]"; + + var jd = MutableJsonDocument.Parse(json); + + // resets json to equivalent of "[ 5, ... ]" + jd.RootElement.GetIndexElement(0).Set(5); + + Assert.AreEqual(5, jd.RootElement.GetIndexElement(0).GetInt32()); + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(1).GetProperty("Bar").GetString()); + + // Make a structural change to json[0] but not json[1] + jd.RootElement.GetIndexElement(0).Set(new + { + Foo = new + { + A = 7 + } + }); + + // We should be able to get the value of A without being tripped up by earlier changes. + // We should also be able to get the value of json[1] without it having been invalidated. + Assert.AreEqual(7, jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetProperty("A").GetInt32()); + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(1).GetProperty("Bar").GetString()); + + // Now change json[1] + jd.RootElement.GetIndexElement(1).GetProperty("Bar").Set("new"); + Assert.AreEqual("new", jd.RootElement.GetIndexElement(1).GetProperty("Bar").GetString()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ + { + ""Foo"" : { + ""A"": 7 + } + }, + { + ""Bar"" : ""new"" + }]"), jsonString); + } + + [Test] + public void PriorChangeToReplacedPropertyIsIgnored() + { + string json = @"{ ""ArrayProperty"": [ + { + ""Foo"" : { + ""A"": 6 + } + } ], + ""Bar"" : ""hi"" }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).GetProperty("Foo").GetProperty("A").Set(8); + + Assert.AreEqual(8, jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).GetProperty("Foo").GetProperty("A").GetInt32()); + + // resets json to equivalent of "[ 5 ]" + jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).Set(5); + + Assert.AreEqual(5, jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).GetInt32()); + + // Reset json[0] to an object + jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).Set(new + { + Foo = new + { + A = 7 + } + }); + + // We should be able to get the value of A without being tripped up + // by earlier changes. + int aValue = jd.RootElement.GetProperty("ArrayProperty").GetIndexElement(0).GetProperty("Foo").GetProperty("A").GetInt32(); + Assert.AreEqual(7, aValue); + + // 3. Type round-trips correctly. + using MemoryStream stream = new(); + jd.WriteTo(stream); + stream.Position = 0; + string jsonString = BinaryData.FromStream(stream).ToString(); + JsonDocument doc = JsonDocument.Parse(jsonString); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"{ + ""ArrayProperty"": [ + { + ""Foo"" : { + ""A"": 7 + } + } ], + ""Bar"" : ""hi"" }"), jsonString); + } + + [Test] + public void CanSetProperty_StringToNumber() + { + string json = @"[ { ""Foo"" : ""hi"" } ]"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetString()); + + jd.RootElement.GetIndexElement(0).GetProperty("Foo").Set(1.2); + + Assert.AreEqual(1.2, jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetDouble()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1.2, doc.RootElement[0].GetProperty("Foo").GetDouble()); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ { ""Foo"" : 1.2 } ]"), jsonString); + } + + [Test] + public void CanSetProperty_StringToBool() + { + string json = @"[ { ""Foo"" : ""hi"" } ]"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetString()); + + jd.RootElement.GetIndexElement(0).GetProperty("Foo").Set(false); + + Assert.IsFalse(jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetBoolean()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.IsFalse(doc.RootElement[0].GetProperty("Foo").GetBoolean()); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ { ""Foo"" : false } ]"), jsonString); + } + + [Test] + public void CanSetProperty_StringToObject() + { + string json = @"{ ""Foo"" : ""hi"" }"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual("hi", jd.RootElement.GetProperty("Foo").GetString()); + + jd.RootElement.GetProperty("Foo").Set(new + { + Bar = 6 + }); + + Assert.AreEqual(6, jd.RootElement.GetProperty("Foo").GetProperty("Bar").GetInt32()); + + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(6, doc.RootElement.GetProperty("Foo").GetProperty("Bar").GetInt32()); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace( + @"{ ""Foo"" : {""Bar"" : 6 } }"), + jsonString); + } + + [Test] + public void CanSetProperty_StringToArray() + { + string json = @"[ { ""Foo"" : ""hi"" } ]"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetString()); + + jd.RootElement.GetIndexElement(0).GetProperty("Foo").Set(new int[] { 1, 2, 3 }); + + Assert.AreEqual(1, jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetIndexElement(0).GetInt32()); + Assert.AreEqual(2, jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetIndexElement(1).GetInt32()); + Assert.AreEqual(3, jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetIndexElement(2).GetInt32()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(1, doc.RootElement[0].GetProperty("Foo")[0].GetInt32()); + Assert.AreEqual(2, doc.RootElement[0].GetProperty("Foo")[1].GetInt32()); + Assert.AreEqual(3, doc.RootElement[0].GetProperty("Foo")[2].GetInt32()); + + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ { ""Foo"" : [1, 2, 3] }]"), jsonString); + } + + [Test] + public void CanSetProperty_StringToNull() + { + string json = @"[ { ""Foo"" : ""hi"" } ]"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual("hi", jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetString()); + + jd.RootElement.GetIndexElement(0).GetProperty("Foo").Set(null); + + Assert.IsNull(jd.RootElement.GetIndexElement(0).GetProperty("Foo").GetString()); + + // 3. Type round-trips correctly. + JsonDocument doc = MutableJsonDocumentWriteToTests.WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(JsonValueKind.Null, doc.RootElement[0].GetProperty("Foo").ValueKind); + Assert.AreEqual(MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@"[ { ""Foo"" : null } ]"), jsonString); + } + + [Test] + public void CanSerialize() + { + string json = @" + { + ""Foo"" : ""Hello"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(mdoc); + + JsonDocument doc = JsonDocument.Parse(bytes); + + Assert.AreEqual("Hello", doc.RootElement.GetProperty("Foo").GetString()); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentWriteToTests.cs b/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentWriteToTests.cs new file mode 100644 index 0000000000000..c8bca1f73c43d --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/MutableJsonDocumentWriteToTests.cs @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Text.Json; +using Azure.Core.Dynamic; +using NUnit.Framework; + +namespace Azure.Core.Experimental.Tests +{ + internal class MutableJsonDocumentWriteToTests + { + // TODO: Add tests for both with and without changes. + + [Test] + public void CanWriteBoolean() + { + string jsonTrue = @"true"; + string jsonFalse = @"false"; + + MutableJsonDocument jdTrue = MutableJsonDocument.Parse(jsonTrue); + MutableJsonDocument jdFalse = MutableJsonDocument.Parse(jsonFalse); + + WriteToAndParse(jdTrue, out string jsonTrueString); + WriteToAndParse(jdFalse, out string jsonFalseString); + + Assert.AreEqual(RemoveWhiteSpace(jsonTrue), RemoveWhiteSpace(jsonTrueString)); + Assert.AreEqual(RemoveWhiteSpace(jsonFalse), RemoveWhiteSpace(jsonFalseString)); + } + + [Test] + public void CanWriteString() + { + string json = @"""Hi!"""; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteBooleanObjectProperty() + { + string json = @" + { + ""Foo"" : true, + ""Bar"" : false + }"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteBooleanObjectPropertyWithChangesToOtherBranches() + { + string json = @" + { + ""Foo"" : true, + ""Bar"" : 1 + }"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Bar").Set(2); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(@" + { + ""Foo"" : true, + ""Bar"" : 2 + }"), + RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteBooleanObjectPropertyWithChangesToBool() + { + string json = @" + { + ""Foo"" : true + }"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Foo").Set(false); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(@" + { + ""Foo"" : false + }"), + RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteObject() + { + string json = @" + { + ""StringProperty"" : ""Hi!"", + ""IntProperty"" : 16, + ""DoubleProperty"" : 16.56, + ""ObjectProperty"" : { + ""StringProperty"" : ""Nested"", + ""IntProperty"" : 22, + ""DoubleProperty"" : 22.22 + }, + ""ArrayProperty"" : [ + { + ""StringProperty"" : ""First"", + ""IntProperty"" : 1, + ""DoubleProperty"" : 1.1 + }, + { + ""StringProperty"" : ""Second"", + ""IntProperty"" : 2, + ""DoubleProperty"" : 2.2 + }, + { + ""StringProperty"" : ""Third"", + ""IntProperty"" : 3, + ""DoubleProperty"" : 3.3 + } + ] + }"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + TestClass testClass = JsonSerializer.Deserialize(jsonString); + Assert.AreEqual(jd.RootElement.GetProperty("StringProperty").GetString(), testClass.StringProperty); + Assert.AreEqual(jd.RootElement.GetProperty("IntProperty").GetInt32(), testClass.IntProperty); + Assert.AreEqual(jd.RootElement.GetProperty("DoubleProperty").GetDouble(), testClass.DoubleProperty); + Assert.AreEqual(jd.RootElement.GetProperty("ObjectProperty").GetProperty("StringProperty").GetString(), testClass.ObjectProperty.StringProperty); + Assert.AreEqual(jd.RootElement.GetProperty("ObjectProperty").GetProperty("IntProperty").GetInt32(), testClass.ObjectProperty.IntProperty); + Assert.AreEqual(jd.RootElement.GetProperty("ObjectProperty").GetProperty("DoubleProperty").GetDouble(), testClass.ObjectProperty.DoubleProperty); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteInt() + { + string json = @"16"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteDouble() + { + string json = @"16.56"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteNumberArray() + { + string json = @"[ 1, 2.2, 3, -4]"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + [Test] + public void CanWriteStringArray() + { + string json = @"[ ""one"", ""two"", ""three""]"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + WriteToAndParse(jd, out string jsonString); + + Assert.AreEqual(RemoveWhiteSpace(json), RemoveWhiteSpace(jsonString)); + } + + #region Helpers + private class TestClass + { + public string StringProperty { get; set; } + public int IntProperty { get; set; } + public double DoubleProperty { get; set; } + public TestClass ObjectProperty { get; set; } + public TestClass[] ArrayProperty { get; set; } + } + + internal static string RemoveWhiteSpace(string value) + { + return value.Replace(" ", "").Replace("\r", "").Replace("\n", ""); + } + + internal static JsonDocument WriteToAndParse(MutableJsonDocument data, out string json) + { + using MemoryStream stream = new(); + data.WriteTo(stream); + stream.Position = 0; + json = BinaryData.FromStream(stream).ToString(); + return JsonDocument.Parse(json); + } + + #endregion + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/MutableJsonElementTests.cs b/sdk/core/Azure.Core.Experimental/tests/MutableJsonElementTests.cs new file mode 100644 index 0000000000000..2a12e6dc99639 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/MutableJsonElementTests.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Text.Json; +using Azure.Core.Dynamic; +using NUnit.Framework; + +namespace Azure.Core.Experimental.Tests +{ + internal class MutableJsonElementTests + { + [Test] + public void ToStringWorksWithNoChanges() + { + string json = @" + { + ""Bar"" : ""Hi!"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(json), + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(mdoc.RootElement.ToString())); + } + + [Test] + public void ToStringWorksWithChanges() + { + string json = @" + { + ""Bar"" : ""Hi!"" + }"; + + MutableJsonDocument mdoc = MutableJsonDocument.Parse(json); + mdoc.RootElement.GetProperty("Bar").Set(null); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Bar"" : null + }"), + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(mdoc.RootElement.ToString())); + } + + [Test] + public void ChangesToElementAppearInToString() + { + string json = @" + { + ""Bar"" : ""Hi!"" + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Bar").Set("hello"); + + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Bar"" : ""hello"" + }"), + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(jd.RootElement.ToString())); + } + + [Test] + public void ChangesToElementAppearInJsonElement() + { + string json = @" + { + ""Bar"" : ""Hi!"" + }"; + + var jd = MutableJsonDocument.Parse(json); + + jd.RootElement.GetProperty("Bar").Set("hello"); + + JsonElement barElement = jd.RootElement.GetProperty("Bar").GetJsonElement(); + Assert.AreEqual("hello", barElement.GetString()); + + JsonElement rootElement = jd.RootElement.GetJsonElement(); + Assert.AreEqual( + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(@" + { + ""Bar"" : ""hello"" + }"), + MutableJsonDocumentWriteToTests.RemoveWhiteSpace(rootElement.ToString())); + } + + [Test] + public void CanGetNullElement() + { + string json = @" + { + ""Bar"" : null + }"; + + var jd = MutableJsonDocument.Parse(json); + + MutableJsonElement bar = jd.RootElement.GetProperty("Bar"); + + Assert.AreEqual(JsonValueKind.Null, bar.ValueKind); + } + + [Test] + public void ValueKindReflectsChanges() + { + string json = @" + { + ""Bar"" : ""Hi!"" + }"; + + var jd = MutableJsonDocument.Parse(json); + + Assert.AreEqual(JsonValueKind.String, jd.RootElement.GetProperty("Bar").ValueKind); + + jd.RootElement.GetProperty("Bar").Set(1.2); + + Assert.AreEqual(JsonValueKind.Number, jd.RootElement.GetProperty("Bar").ValueKind); + + jd.RootElement.GetProperty("Bar").Set(null); + + Assert.AreEqual(JsonValueKind.Null, jd.RootElement.GetProperty("Bar").ValueKind); + } + + [Test] + public void CanEnumerateArray() + { + string json = @"[0, 1, 2, 3]"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + MutableJsonElement.ArrayEnumerator enumerator = jd.RootElement.EnumerateArray(); + + int expected = 0; + foreach (MutableJsonElement el in enumerator) + { + Assert.AreEqual(expected++, el.GetInt32()); + } + } + + [Test] + public void CanEnumerateArrayWithChanges() + { + string json = @"[0, 1, 2, 3]"; + + MutableJsonDocument jd = MutableJsonDocument.Parse(json); + + for (int i = 0; i < 4; i++) + { + jd.RootElement.GetIndexElement(i).Set(i + 1); + } + + MutableJsonElement.ArrayEnumerator enumerator = jd.RootElement.EnumerateArray(); + + int expected = 1; + foreach (MutableJsonElement el in enumerator) + { + Assert.AreEqual(expected++, el.GetInt32()); + } + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs index 9576d4513ad52..608d2bb1bd03e 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/DynamicReadingBenchmark.cs @@ -7,7 +7,7 @@ using BenchmarkDotNet.Attributes; using Newtonsoft.Json.Linq; -namespace Azure.Data.AppConfiguration.Performance +namespace Azure.Core.Experimental.Perf.Benchmarks { [MemoryDiagnoser] [InProcess] diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs index 586af969e4502..c31c4c26ba10f 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/FirstReadBenchmark.cs @@ -6,7 +6,7 @@ using Azure.Core.Dynamic; using BenchmarkDotNet.Attributes; -namespace Azure.Data.AppConfiguration.Performance +namespace Azure.Core.Experimental.Perf.Benchmarks { [MemoryDiagnoser] public class FirstReadBenchmark diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/JsonSamples.cs b/sdk/core/Azure.Core.Experimental/tests/perf/JsonSamples.cs new file mode 100644 index 0000000000000..ff204d3d73cac --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/perf/JsonSamples.cs @@ -0,0 +1,141 @@ +// 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.Perf.Benchmarks +{ + internal class JsonSamples + { + // From https://learn.microsoft.com/rest/api/cognitiveservices-textanalytics/3.1preview4/sentiment/sentiment?tabs=HTTP#examples + private static BinaryData _documentSentiment = BinaryData.FromString( + @" + { + ""documents"": [ + { + ""confidenceScores"": { + ""negative"": 0, + ""neutral"": 0, + ""positive"": 1 + }, + ""id"": ""1"", + ""sentences"": [ + { + ""targets"": [ + { + ""confidenceScores"": { + ""negative"": 0, + ""positive"": 1 + }, + ""length"": 10, + ""offset"": 6, + ""relations"": [ + { + ""ref"": ""#/documents/0/sentences/0/assessments/0"", + ""relationType"": ""assessment"" + } + ], + ""sentiment"": ""positive"", + ""text"": ""atmosphere"" + } + ], + ""confidenceScores"": { + ""negative"": 0, + ""neutral"": 0, + ""positive"": 1 + }, + ""length"": 17, + ""offset"": 0, + ""assessments"": [ + { + ""confidenceScores"": { + ""negative"": 0, + ""positive"": 1 + }, + ""isNegated"": false, + ""length"": 5, + ""offset"": 0, + ""sentiment"": ""positive"", + ""text"": ""great"" + } + ], + ""sentiment"": ""positive"", + ""text"": ""Great atmosphere."" + }, + { + ""targets"": [ + { + ""confidenceScores"": { + ""negative"": 0.01, + ""positive"": 0.99 + }, + ""length"": 11, + ""offset"": 37, + ""relations"": [ + { + ""ref"": ""#/documents/0/sentences/1/assessments/0"", + ""relationType"": ""assessment"" + } + ], + ""sentiment"": ""positive"", + ""text"": ""restaurants"" + }, + { + ""confidenceScores"": { + ""negative"": 0.01, + ""positive"": 0.99 + }, + ""length"": 6, + ""offset"": 50, + ""relations"": [ + { + ""ref"": ""#/documents/0/sentences/1/assessments/0"", + ""relationType"": ""assessment"" + } + ], + ""sentiment"": ""positive"", + ""text"": ""hotels"" + } + ], + ""confidenceScores"": { + ""negative"": 0.01, + ""neutral"": 0.86, + ""positive"": 0.13 + }, + ""length"": 52, + ""offset"": 18, + ""assessments"": [ + { + ""confidenceScores"": { + ""negative"": 0.01, + ""positive"": 0.99 + }, + ""isNegated"": false, + ""length"": 15, + ""offset"": 18, + ""sentiment"": ""positive"", + ""text"": ""Close to plenty"" + } + ], + ""sentiment"": ""neutral"", + ""text"": ""Close to plenty of restaurants, hotels, and transit!"" + } + ], + ""sentiment"": ""positive"", + ""warnings"": [] + } + ], + ""errors"": [], + ""modelVersion"": ""2020-04-01"" +}"); + + /// + /// An example of a large Json response. + /// + public static BinaryData DocumentSentiment => _documentSentiment; + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/MutableJsonDataBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/MutableJsonDataBenchmark.cs new file mode 100644 index 0000000000000..d754b54383cd7 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/perf/MutableJsonDataBenchmark.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Text.Json; +using Azure.Core.Dynamic; +using BenchmarkDotNet.Attributes; + +namespace Azure.Core.Experimental.Perf.Benchmarks +{ + [MemoryDiagnoser] + public class MutableJsonDataBenchmark + { + [Benchmark(Baseline = true)] + public void DocumentSentiment_WriteTo_JsonDocument() + { + var document = JsonDocument.Parse(JsonSamples.DocumentSentiment); + + MemoryStream stream = new(); + Utf8JsonWriter writer = new Utf8JsonWriter(stream); + document.WriteTo(writer); + } + + [Benchmark] + public void DocumentSentiment_WriteTo_JsonData_NoChanges() + { + MutableJsonDocument jsonData = MutableJsonDocument.Parse(JsonSamples.DocumentSentiment); + + MemoryStream stream = new(); + jsonData.WriteTo(stream); + } + + [Benchmark] + public void DocumentSentiment_WriteTo_JsonData_ChangeValue() + { + MutableJsonDocument jsonData = MutableJsonDocument.Parse(JsonSamples.DocumentSentiment); + + // Make a small change + jsonData.RootElement.GetProperty("documents").GetIndexElement(0).GetProperty("sentences").GetIndexElement(1).GetProperty("sentiment").Set("positive"); + + MemoryStream stream = new(); + jsonData.WriteTo(stream); + } + + [Benchmark] + public void DocumentSentiment_WriteTo_JsonData_ChangeStructure() + { + MutableJsonDocument jsonData = MutableJsonDocument.Parse(JsonSamples.DocumentSentiment); + + // Make a small change + jsonData.RootElement.GetProperty("documents").GetIndexElement(0).GetProperty("sentences").GetIndexElement(1).GetProperty("sentiment").Set("positive"); + + MemoryStream stream = new(); + jsonData.WriteTo(stream); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/ParseAndReadLargePayloadBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/ParseAndReadLargePayloadBenchmark.cs new file mode 100644 index 0000000000000..3e1dff58171df --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/perf/ParseAndReadLargePayloadBenchmark.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using Azure.Core.Dynamic; +using BenchmarkDotNet.Attributes; + +namespace Azure.Core.Experimental.Perf.Benchmarks +{ + [MemoryDiagnoser] + public class ParseAndReadLargePayloadBenchmark + { + [Benchmark(Baseline = true)] + public string ReadJsonElement() + { + // This should return the string "neutral". + var document = JsonDocument.Parse(JsonSamples.DocumentSentiment); + return document.RootElement.GetProperty("documents")[0].GetProperty("sentences")[1].GetProperty("sentiment").GetString(); + } + + [Benchmark] + public string ReadJsonData() + { + var json = JsonSamples.DocumentSentiment.ToDynamic(); + return json.documents[0].sentences[1].sentiment; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/Program.cs b/sdk/core/Azure.Core.Experimental/tests/perf/Program.cs index 22abac135b7b1..4fd26cc86ea99 100644 --- a/sdk/core/Azure.Core.Experimental/tests/perf/Program.cs +++ b/sdk/core/Azure.Core.Experimental/tests/perf/Program.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Azure.Core.Experimental.Perf.Benchmarks; using BenchmarkDotNet.Running; namespace Azure.Core.Experimental.Performance @@ -10,6 +12,17 @@ public class Program public static void Main(string[] args) { BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + + //DebugBenchmark(); + } + + private static void DebugBenchmark() + { + ReadLargePayloadBenchmark b = new(); + + string value = b.ReadJsonElement(); + + value = b.ReadJsonData(); } } } diff --git a/sdk/core/Azure.Core.Experimental/tests/perf/ReadLargePayloadBenchmark.cs b/sdk/core/Azure.Core.Experimental/tests/perf/ReadLargePayloadBenchmark.cs new file mode 100644 index 0000000000000..61bce3092c8bd --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/perf/ReadLargePayloadBenchmark.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using Azure.Core.Dynamic; +using BenchmarkDotNet.Attributes; + +namespace Azure.Core.Experimental.Perf.Benchmarks +{ + [MemoryDiagnoser] + public class ReadLargePayloadBenchmark + { + private dynamic _json = JsonSamples.DocumentSentiment.ToDynamic(); + private JsonDocument _document = JsonDocument.Parse(JsonSamples.DocumentSentiment); + + [Benchmark(Baseline = true)] + public string ReadJsonElement() + { + // This should return the string "neutral". + return _document.RootElement.GetProperty("documents")[0].GetProperty("sentences")[1].GetProperty("sentiment").GetString(); + } + + [Benchmark] + public string ReadJsonData() + { + return _json.documents[0].sentences[1].sentiment; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs index e789fbdc8f572..33257ed355fa5 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataArrayTests.cs @@ -241,28 +241,14 @@ public void CanSetObjectMemberViaArrayIndex() public void CanGetArrayLength() { dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - Assert.AreEqual(3, data.Length); + Assert.AreEqual(3, ((int[])data).Length); } [Test] public void CanGetArrayPropertyLength() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.AreEqual(3, data.value.Length); - } - - [Test] - public void CannotSetArrayLength() - { - dynamic data = JsonDataTestHelpers.CreateFromJson("[1, 2, 3]"); - Assert.Throws(() => { data.Length = 5; }); - } - - [Test] - public void CannotSetArrayPropertyLength() - { - dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": [1, 2, 3] }"); - Assert.Throws(() => { data.value.Length = 5; }); + Assert.AreEqual(3, ((int[])data.value).Length); } [Test] diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs index 5be4a0bf5b3eb..94c0076d691eb 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataLeafTests.cs @@ -57,7 +57,7 @@ public void CanConvertIntLeafPropertyToLong() public void CannotConvertIntLeafToString() { dynamic data = JsonDataTestHelpers.CreateFromJson("5"); - Assert.Throws( + Assert.Throws( () => { var s = (string)data; } ); } @@ -66,7 +66,7 @@ public void CannotConvertIntLeafToString() public void CannotConvertIntLeafPropertyToString() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.Throws( + Assert.Throws( () => { var s = (string)data.value; } ); } @@ -75,7 +75,7 @@ public void CannotConvertIntLeafPropertyToString() public void CannotConvertIntLeafToBoolean() { dynamic data = JsonDataTestHelpers.CreateFromJson("5"); - Assert.Throws( + Assert.Throws( () => { var b = (bool)data; } ); } @@ -84,7 +84,7 @@ public void CannotConvertIntLeafToBoolean() public void CannotConvertIntLeafPropertyToBoolean() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""value"": 5 }"); - Assert.Throws( + Assert.Throws( () => { var b = (bool)data.value; } ); } diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs index 1150ee6318056..c2a1d90afe765 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataObjectTests.cs @@ -115,6 +115,7 @@ public void CannotSetArrayIndexOnObject() #endregion [Test] + [Ignore("To be implemented.")] public void CanEnumerateObject() { dynamic data = JsonDataTestHelpers.CreateFromJson(@"{ ""first"": 1, ""second"": 2 }"); diff --git a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs index 7400251ff6dae..bbce70b13b274 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicMutableTests.cs @@ -108,10 +108,10 @@ public static IEnumerable PrimitiveValues() 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"}; + yield return new object[] {1.1F, "1.1"}; #else yield return new object[] { 1.1D, "1.1000000000000001" }; - yield return new object[] { 1.1F, "1.1000000238418579" }; + yield return new object[] { 1.1F, "1.10000002" }; #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 index 37a9506afbe07..66dd7ce451e6d 100644 --- a/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/public/JsonDataPublicTests.cs @@ -92,16 +92,17 @@ public void DynamicCanConvertToIEnumerableInt() public void DynamicArrayHasLength() { dynamic jsonData = new BinaryData("[0, 1, 2, 3]").ToDynamic(); - Assert.AreEqual(4, jsonData.Length); + Assert.AreEqual(4, ((int[])jsonData).Length); } [Test] - public void DynamicArrayFor() + public void DynamicArrayForEach() { dynamic jsonData = new BinaryData("[0, 1, 2, 3]").ToDynamic(); - for (int i = 0; i < jsonData.Length; i++) + int expected = 0; + foreach (int i in jsonData) { - Assert.AreEqual(i, (int)jsonData[i]); + Assert.AreEqual(expected++, i); } } @@ -115,12 +116,13 @@ public void CanAccessProperties() } [Test] + [Ignore("To be implemented.")] public void CanTestPropertyForNull() { dynamic jsonData = new BinaryData("{ \"primitive\":\"Hello\", \"nested\": { \"nestedPrimitive\":true } }").ToDynamic(); - Assert.IsNull(jsonData.OptionalInt); - Assert.IsNull(jsonData.OptionalString); + Assert.IsNull((int?)jsonData.OptionalInt); + Assert.IsNull((string)jsonData.OptionalString); Assert.AreEqual("Hello", (string)jsonData.primitive); } @@ -186,14 +188,20 @@ public void ReadingFloatingPointAsIntThrows() } [Test] + [Ignore("Decide how to handle this case.")] public void FloatOverflowThrows() { var json = new BinaryData("34028234663852885981170418348451692544000").ToDynamic(); + + JsonDocument doc = JsonDocument.Parse("34028234663852885981170418348451692544000"); + float f = doc.RootElement.GetSingle(); + dynamic jsonData = json; - Assert.Throws(() => _ = (float)json); - Assert.Throws(() => _ = (float)jsonData); Assert.AreEqual(34028234663852885981170418348451692544000d, (double)jsonData); Assert.AreEqual(34028234663852885981170418348451692544000d, (double)json); + Assert.Throws(() => _ = (float)34028234663852885981170418348451692544000d); + Assert.Throws(() => _ = (float)json); + Assert.Throws(() => _ = (float)jsonData); } [Test] @@ -215,6 +223,7 @@ public void CanAccessJsonPropertiesWithDotnetIllegalCharacters() } [Test] + [Ignore("Decide how to handle this case.")] public void FloatUnderflowThrows() { var json = new BinaryData("-34028234663852885981170418348451692544000").ToDynamic(); @@ -230,8 +239,8 @@ public void IntOverflowThrows() { var json = new BinaryData("3402823466385288598").ToDynamic(); dynamic jsonData = json; - Assert.Throws(() => _ = (int)json); - Assert.Throws(() => _ = (int)jsonData); + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); Assert.AreEqual(3402823466385288598L, (long)jsonData); Assert.AreEqual(3402823466385288598L, (long)json); Assert.AreEqual(3402823466385288598D, (double)jsonData); @@ -245,8 +254,8 @@ public void IntUnderflowThrows() { var json = new BinaryData("-3402823466385288598").ToDynamic(); dynamic jsonData = json; - Assert.Throws(() => _ = (int)json); - Assert.Throws(() => _ = (int)jsonData); + Assert.Throws(() => _ = (int)json); + Assert.Throws(() => _ = (int)jsonData); Assert.AreEqual(-3402823466385288598L, (long)jsonData); Assert.AreEqual(-3402823466385288598L, (long)json); Assert.AreEqual(-3402823466385288598D, (double)jsonData); @@ -302,24 +311,7 @@ public void CanCastToIEnumerableOfT() } [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] + [Ignore("To be implemented.")] public void EqualsHandlesStringsSpecial() { dynamic json = new BinaryData("\"test\"").ToDynamic(); @@ -329,6 +321,7 @@ public void EqualsHandlesStringsSpecial() } [Test] + [Ignore("To be implemented.")] public void EqualsForObjectsAndArrays() { dynamic obj1 = new BinaryData(new { foo = "bar" }).ToDynamic(); @@ -338,8 +331,8 @@ public void EqualsForObjectsAndArrays() dynamic arr2 = new BinaryData(new[] { "bar" }).ToDynamic(); // For objects and arrays, Equals provides reference equality. - Assert.AreEqual(obj1, obj1); - Assert.AreEqual(arr1, arr1); + Assert.AreEqual(obj1, obj2); + Assert.AreEqual(arr1, arr2); Assert.AreNotEqual(obj1, obj2); Assert.AreNotEqual(arr1, arr2); @@ -477,7 +470,7 @@ public async Task CanWriteToStream() using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream)) { - DynamicData.WriteTo(writer, json); + DynamicData.WriteTo(stream, json); } // Assert