diff --git a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicArray.cs b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicArray.cs index cee0d2fd1f9..7c9032302c6 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicArray.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicArray.cs @@ -25,20 +25,8 @@ public class JsonDynamicArray : DynamicObject, IEnumerable public object? this[int index] { - get - { - var value = GetValue(index); - if (value is JsonDynamicValue jsonDynamicValue) - { - return jsonDynamicValue.JsonValue; - } - - return value; - } - set - { - SetValue(index, value); - } + get => GetValue(index); + set => SetValue(index, value); } public bool Remove(JsonNode? item) @@ -57,14 +45,7 @@ public void RemoveAt(int index) public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result) { - var value = GetValue((int)indexes[0]); - if (value is JsonDynamicValue jsonDynamicValue) - { - result = jsonDynamicValue.Value; - return true; - } - - result = value; + result = GetValue((int)indexes[0]); return true; } @@ -116,7 +97,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, return null; } - public void SetValue(int index, object? value, object? nodeValue = null) + public void SetValue(int index, object? value) { if (value is null) { @@ -127,8 +108,7 @@ public void SetValue(int index, object? value, object? nodeValue = null) if (value is not JsonNode) { - var jsonNode = JNode.FromObject(value); - SetValue(index, jsonNode, value); + value = JNode.FromObject(value); } if (value is JsonObject jsonObject) @@ -148,7 +128,7 @@ public void SetValue(int index, object? value, object? nodeValue = null) if (value is JsonValue jsonValue) { _jsonArray[index] = jsonValue; - _dictionary[index] = new JsonDynamicValue(jsonValue, nodeValue); + _dictionary[index] = new JsonDynamicValue(jsonValue); return; } } @@ -183,14 +163,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) return false; } - var value = GetValue(index); - if (value is JsonDynamicValue jsonDynamicValue) - { - result = jsonDynamicValue.Value; - return true; - } - - result = value; + result = GetValue(index); return true; } diff --git a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicObject.cs b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicObject.cs index 2d3b5e5b182..432285a9b97 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicObject.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicObject.cs @@ -16,31 +16,27 @@ public class JsonDynamicObject : DynamicObject private readonly Dictionary _dictionary = []; - public JsonDynamicObject() => _jsonObject = []; + public JsonDynamicObject() + { + _jsonObject = []; + } - public JsonDynamicObject(JsonObject jsonObject) => _jsonObject = jsonObject; + public JsonDynamicObject(JsonObject jsonObject) + { + _jsonObject = jsonObject; + } public int Count => _jsonObject.Count; - public void Merge(JsonNode? content, JsonMergeSettings? settings = null) => + public void Merge(JsonNode? content, JsonMergeSettings? settings = null) + { _jsonObject.Merge(content, settings); + } public object? this[string key] { - get - { - var value = GetValue(key); - if (value is JsonDynamicValue jsonDynamicValue) - { - return jsonDynamicValue.JsonValue; - } - - return value; - } - set - { - SetValue(key, value); - } + get => GetValue(key); + set => SetValue(key, value); } public override bool TryGetMember(GetMemberBinder binder, out object? result) @@ -57,14 +53,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) return true; } - var value = GetValue(binder.Name); - if (value is JsonDynamicValue jsonDynamicValue) - { - result = jsonDynamicValue.Value; - return true; - } - - result = value; + result = GetValue(binder.Name); return true; } @@ -124,7 +113,7 @@ public bool Remove(string key) return null; } - public void SetValue(string key, object? value, object? nodeValue = null) + public void SetValue(string key, object? value) { if (value is null) { @@ -135,8 +124,7 @@ public void SetValue(string key, object? value, object? nodeValue = null) if (value is not JsonNode) { - var jsonNode = JNode.FromObject(value); - SetValue(key, jsonNode, value); + value = JNode.FromObject(value); } if (value is JsonObject jsonObject) @@ -156,7 +144,7 @@ public void SetValue(string key, object? value, object? nodeValue = null) if (value is JsonValue jsonValue) { _jsonObject[key] = jsonValue; - _dictionary[key] = new JsonDynamicValue(jsonValue, nodeValue); + _dictionary[key] = new JsonDynamicValue(jsonValue); return; } } diff --git a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicValue.cs b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicValue.cs index fe665c0ca12..3257e3bd7a9 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicValue.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/JsonDynamicValue.cs @@ -1,35 +1,405 @@ +using System.Collections.Frozen; +using System.Dynamic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text.Json.Nodes; namespace System.Text.Json.Dynamic; #nullable enable -public class JsonDynamicValue +public class JsonDynamicValue : DynamicObject, IConvertible { - private object? _value; - private bool _hasValue; - - public JsonDynamicValue(JsonValue? jsonValue) => JsonValue = jsonValue; - - public JsonDynamicValue(JsonValue? jsonValue, object? value) + public JsonDynamicValue(JsonValue? jsonValue) { JsonValue = jsonValue; - _value = value; } public JsonValue? JsonValue { get; } - public object? Value + public override DynamicMetaObject GetMetaObject(Expression parameter) + { + return new JsonDynamicMetaObject(parameter, this); + } + + public override string ToString() + { + return ToString(null, null); + } + + public string ToString(string format) + { + return ToString(format, CultureInfo.CurrentCulture); + } + + public string ToString(IFormatProvider? formatProvider) + { + return ToString(null, formatProvider); + } + + public string ToString(string? format, IFormatProvider? formatProvider) { - get + if (JsonValue == null) + { + return string.Empty; + } + + var value = JsonValue.GetObjectValue(); + if (value is IFormattable formattable) { - if (!_hasValue) + return formattable.ToString(format, formatProvider); + } + else + { + return value?.ToString() ?? string.Empty; + } + } + + TypeCode IConvertible.GetTypeCode() + { + if (JsonValue == null) + { + return TypeCode.Empty; + } + + return TypeCode.Object; + } + + bool IConvertible.ToBoolean(IFormatProvider? provider) + { + return (bool)this; + } + + byte IConvertible.ToByte(IFormatProvider? provider) + { + return (byte)this; + } + + char IConvertible.ToChar(IFormatProvider? provider) + { + return (char)this; + } + + DateTime IConvertible.ToDateTime(IFormatProvider? provider) + { + return (DateTime)this; + } + + decimal IConvertible.ToDecimal(IFormatProvider? provider) + { + return (decimal)this; + } + + double IConvertible.ToDouble(IFormatProvider? provider) + { + return (double)this; + } + + short IConvertible.ToInt16(IFormatProvider? provider) + { + return (short)this; + } + + int IConvertible.ToInt32(IFormatProvider? provider) + { + return (int)this; + } + + long IConvertible.ToInt64(IFormatProvider? provider) + { + return (long)this; + } + + sbyte IConvertible.ToSByte(IFormatProvider? provider) + { + return (sbyte)this; + } + + float IConvertible.ToSingle(IFormatProvider? provider) + { + return (float)this; + } + + string IConvertible.ToString(IFormatProvider? provider) + { + return ToString(provider); + } + + object IConvertible.ToType(Type conversionType, IFormatProvider? provider) + { + return JsonValue?.ToObject(conversionType) + ?? throw new InvalidOperationException($"Cannot convert {this} to {conversionType}"); + } + + ushort IConvertible.ToUInt16(IFormatProvider? provider) + { + return (ushort)this; + } + + uint IConvertible.ToUInt32(IFormatProvider? provider) + { + return (uint)this; + } + + ulong IConvertible.ToUInt64(IFormatProvider? provider) + { + return (ulong)this; + } + + public static explicit operator bool(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Boolean"); + } + + public static explicit operator bool?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator byte(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Byte"); + } + + public static explicit operator byte?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator char(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Char"); + } + + public static explicit operator char?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator DateTime(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to DateTime"); + } + + public static explicit operator DateTime?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator DateTimeOffset(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to DateTimeOffset"); + } + + public static explicit operator DateTimeOffset?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator decimal(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Decimal"); + } + + public static explicit operator decimal?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator double(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Double"); + } + + public static explicit operator double?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator Guid(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Guid"); + } + + public static explicit operator Guid?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator short(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Int16"); + } + + public static explicit operator short?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator int(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Int32"); + } + + public static explicit operator int?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator long(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Int64"); + } + + public static explicit operator long?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator sbyte(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to SByte"); + } + + public static explicit operator sbyte?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator float(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to Float"); + } + + public static explicit operator float?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator string?(JsonDynamicValue value) + { + return value?.ToString(); + } + + public static explicit operator ushort(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to UInt32"); + } + + public static explicit operator ushort?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator uint(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to UInt32"); + } + + public static explicit operator uint?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator ulong(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue() + ?? throw new InvalidCastException($"Cannot convert {value} to UInt64"); + } + + public static explicit operator ulong?(JsonDynamicValue value) + { + return value?.JsonValue?.GetValue(); + } + + public static explicit operator byte[]?(JsonDynamicValue value) + { + if (value?.JsonValue.GetObjectValue() is string str) + { + return Convert.FromBase64String(str); + } + + throw new InvalidCastException($"Cannot convert {value} to Byte array"); + } + + public static explicit operator TimeSpan(JsonDynamicValue value) + { + if (value?.JsonValue?.GetObjectValue() is string str) + { + return TimeSpan.Parse(str, CultureInfo.InvariantCulture); + } + + throw new InvalidCastException($"Cannot convert {value} to TimeSpan"); + } + + public static explicit operator TimeSpan?(JsonDynamicValue value) + { + var str = value?.JsonValue?.GetObjectValue() as string; + + return str is not null + ? TimeSpan.Parse(str, CultureInfo.InvariantCulture) + : null; + } + + public static explicit operator Uri?(JsonDynamicValue value) + { + return new(value?.JsonValue?.GetValue() ?? string.Empty); + } + + private sealed class JsonDynamicMetaObject : DynamicMetaObject + { + private static readonly FrozenDictionary _cachedReflectionInfo = typeof(JsonDynamicValue) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(method => method.Name == "op_Explicit") + .ToFrozenDictionary(method => method.ReturnType); + + public JsonDynamicMetaObject(Expression expression, JsonDynamicValue value) + : base(expression, BindingRestrictions.Empty, value) + { + } + + + // The 'BindConvert()' method is automatically invoked to handle type conversion when casting + // dynamic types to static types in C#. + // + // For example, when extracting a 'DateTime' value from a dynamically typed + // content item's field: + // + // dynamic contentItem = [...]; // Assume contentItem is initialized properly + // + // 'BindConvert()' is called implicitly to convert contentItem.Content.MyPart.MyField.Value + // to 'DateTime' with similar behavior to the following: + // var dateTimeValue = (DateTime)contentItem.Content.MyPart.MyField.Value; + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + var targetType = binder.Type; + + if (_cachedReflectionInfo.TryGetValue(targetType, out var castMethod)) { - _value = JsonValue?.GetObjectValue(); - _hasValue = true; + var convertExpression = Expression.Convert(Expression.Convert(Expression, typeof(JsonDynamicValue)), targetType, castMethod); + + return new DynamicMetaObject(convertExpression, BindingRestrictions.GetTypeRestriction(Expression, typeof(JsonDynamicValue))); } - return _value; + // Fallback to default behavior. + return base.BindConvert(binder); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentElement.cs b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentElement.cs index f648d15b8ea..7a499911f65 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentElement.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.Abstractions/ContentElement.cs @@ -13,6 +13,7 @@ public class ContentElement : IContent { private Dictionary _elements; private JsonDynamicObject _dynamicObject; + private JsonObject _data; protected ContentElement() : this([]) { @@ -27,7 +28,15 @@ protected ContentElement() : this([]) public dynamic Content => _dynamicObject ??= Data; [JsonIgnore] - internal JsonObject Data { get; set; } + internal JsonObject Data + { + get => _data; + set + { + _dynamicObject = null; + _data = value; + } + } [JsonIgnore] public ContentItem ContentItem { get; set; } diff --git a/test/OrchardCore.Tests/Data/ContentItemTests.cs b/test/OrchardCore.Tests/Data/ContentItemTests.cs index 860b260e025..f7dd3f35cd5 100644 --- a/test/OrchardCore.Tests/Data/ContentItemTests.cs +++ b/test/OrchardCore.Tests/Data/ContentItemTests.cs @@ -143,7 +143,7 @@ public void ContentShouldCanCallRemoveMethod() } [Fact] - public void ShouldDeserializeListContentPart() + public void ShouldDeserializeContentField() { var contentItem = CreateContentItemWithMyPart(); contentItem.Alter(x => x.Text = "test"); @@ -155,7 +155,108 @@ public void ShouldDeserializeListContentPart() var json = JConvert.SerializeObject(contentItem); - Assert.Contains(@"""MyPart"":{""Text"":""test"",""myField"":{""Value"":123}}", json); + var contentItem2 = JConvert.DeserializeObject(json); + + Assert.NotNull(contentItem2.Content.MyPart); + Assert.NotNull(contentItem2.Content.MyPart.myField); + Assert.Equal(123, (int)contentItem2.Content.MyPart.myField.Value); + } + + [Fact] + public void ContentShouldStoreDateTimeFields() + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate(); + contentItem.Alter(x => x.Text = "test"); + contentItem.Alter(x => + { + x.GetOrCreate("myField"); + x.Alter("myField", f => f.Value = new DateTime(2024, 1, 1, 10, 42, 0)); + }); + + var json = JConvert.SerializeObject(contentItem); + + Assert.Contains(@"""MyPart"":{""Text"":""test"",""myField"":{""Value"":""2024-01-01T10:42:00""}}", json); + } + + [Fact] + public void ShouldDeserializeDateTimeFields() + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate(); + contentItem.Alter(x => x.Text = "test"); + contentItem.Alter(x => + { + x.GetOrCreate("myField"); + x.Alter("myField", f => f.Value = new DateTime(2024, 1, 1, 10, 42, 0)); + }); + + var json = JConvert.SerializeObject(contentItem); + + var contentItem2 = JConvert.DeserializeObject(json); + + Assert.NotNull(contentItem2.Content.MyPart); + Assert.NotNull(contentItem2.Content.MyPart.myField); + Assert.Equal(new DateTime(2024, 1, 1, 10, 42, 0), (DateTime?)contentItem2.Content.MyPart.myField.Value); + } + + [Fact] + public void ContentShouldStoreUtcDateTimeFields() + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate(); + contentItem.Alter(x => x.Text = "test"); + contentItem.Alter(x => + { + x.GetOrCreate("myField"); + x.Alter("myField", f => f.Value = new DateTime(2024, 1, 1, 10, 42, 0, DateTimeKind.Utc)); + }); + + var json = JConvert.SerializeObject(contentItem); + + Assert.Contains(@"""MyPart"":{""Text"":""test"",""myField"":{""Value"":""2024-01-01T10:42:00Z""}}", json); + } + + [Fact] + public void ShouldDeserializeUtcDateTimeFields() + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate(); + contentItem.Alter(x => x.Text = "test"); + contentItem.Alter(x => + { + x.GetOrCreate("myField"); + x.Alter("myField", f => f.Value = new DateTime(2024, 1, 1, 10, 42, 0, DateTimeKind.Utc)); + }); + + var json = JConvert.SerializeObject(contentItem); + + var contentItem2 = JConvert.DeserializeObject(json); + + Assert.NotNull(contentItem2.Content.MyPart); + Assert.NotNull(contentItem2.Content.MyPart.myField); + Assert.Equal(new DateTime(2024, 1, 1, 10, 42, 0, DateTimeKind.Utc), (DateTime?)contentItem2.Content.MyPart.myField.Value); + } + + [Fact] + public void ShouldDeserializeTextFields() + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate(); + contentItem.Alter(x => x.Text = "test"); + contentItem.Alter(x => + { + x.GetOrCreate("myField"); + x.Alter("myField", f => f.Text = "This is a test field entry"); + }); + + var json = JConvert.SerializeObject(contentItem); + + var contentItem2 = JConvert.DeserializeObject(json); + + Assert.NotNull(contentItem2.Content.MyPart); + Assert.NotNull(contentItem2.Content.MyPart.myField); + Assert.Equal("This is a test field entry", (string)contentItem2.Content.MyPart.myField.Text); } private static ContentItem CreateContentItemWithMyPart(string text = "test") @@ -175,18 +276,28 @@ private static void AssertJsonEqual(JsonNode expected, JsonNode actual) } } - public class MyPart : ContentPart + public sealed class MyPart : ContentPart { public string Text { get; set; } } - public class MyField : ContentField + public sealed class MyField : ContentField { public int Value { get; set; } } - public class GetOnlyListPart : ContentPart + public sealed class MyDateTimeField : ContentField + { + public DateTime? Value { get; set; } + } + + public sealed class MyTextField : ContentField + { + public string Text { get; set; } + } + + public sealed class GetOnlyListPart : ContentPart { - public IList Texts { get; } = new List(); + public IList Texts { get; } = []; } } diff --git a/test/OrchardCore.Tests/Data/JsonDynamicTests.cs b/test/OrchardCore.Tests/Data/JsonDynamicTests.cs new file mode 100644 index 00000000000..5060531a1d8 --- /dev/null +++ b/test/OrchardCore.Tests/Data/JsonDynamicTests.cs @@ -0,0 +1,340 @@ +using System.Text.Json.Dynamic; +using System.Text.Json.Nodes; + +namespace OrchardCore.Tests.Data; + +public class JsonDynamicTests +{ + [Fact] + public void JsonDynamicValueMustConvertToBool() + { + var expectedValue = true; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (bool)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableBool() + { + bool? expectedValue = true; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (bool?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToByte() + { + byte expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (byte)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableBye() + { + byte? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (byte?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToChar() + { + var expectedValue = 'A'; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (char)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableChar() + { + char? expectedValue = 'B'; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (char?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToDateTime() + { + var expectedValue = DateTime.UtcNow; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (DateTime)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableDateTime() + { + DateTime? expectedValue = DateTime.UtcNow; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (DateTime?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToDateTimeOffset() + { + DateTimeOffset expectedValue = DateTimeOffset.UtcNow; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (DateTimeOffset)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullablDateTimeOffset() + { + DateTimeOffset? expectedValue = DateTimeOffset.UtcNow; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (DateTimeOffset?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToDecimal() + { + decimal expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (decimal)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableDecimal() + { + decimal? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (decimal?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToDouble() + { + double expectedValue = 42.42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (double)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableDouble() + { + double? expectedValue = 42.42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (double?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToGuid() + { + Guid expectedValue = Guid.NewGuid(); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (Guid)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableGuid() + { + Guid? expectedValue = Guid.NewGuid(); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (Guid?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToInt16() + { + short expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (short)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableInt16() + { + short? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (short?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToInt32() + { + int expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (int)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableInt32() + { + int? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (int?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToInt64() + { + long expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (long)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableInt64() + { + long? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (long?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToSByte() + { + sbyte expectedValue = -42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (sbyte)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableSByte() + { + sbyte? expectedValue = -42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (sbyte?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToSingle() + { + float expectedValue = 42.42F; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (float)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableSingle() + { + float? expectedValue = 42.42F; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (float?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToString() + { + var expectedValue = "A test string value"; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (string)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToUInt16() + { + ushort expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (ushort)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableUInt16() + { + ushort? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (ushort?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToUInt32() + { + uint expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (uint)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableUInt32() + { + uint? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (uint?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToUInt64() + { + ulong expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (ulong)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableUInt64() + { + ulong? expectedValue = 42; + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (ulong?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToByteArray() + { + var expectedValue = Encoding.UTF8.GetBytes("A string in a byte array"); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue)); + + Assert.Equal(expectedValue, (byte[])myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToTimeSpan() + { + var expectedValue = TimeSpan.FromSeconds(42); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue.ToString())); + + Assert.Equal(expectedValue, (TimeSpan)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToNullableTimeSpan() + { + var expectedValue = TimeSpan.FromSeconds(42); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue.ToString())); + + Assert.Equal(expectedValue, (TimeSpan?)myDynamic); + } + + [Fact] + public void JsonDynamicValueMustConvertToUri() + { + var expectedValue = new Uri("https://www.orchardcore.net"); + dynamic myDynamic = new JsonDynamicValue(JsonValue.Create(expectedValue.ToString())); + + Assert.Equal(expectedValue, (Uri)myDynamic); + } +}